教程

狂神说java官网

B站视频链接

线程简介

程序Precess是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

进程Thread则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。

通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的的单位。

在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程。

main()称之为主线程,为系统的入口,用于执行整个程序。

在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。

对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。

线程会带来额外的开销,如cpu调度时间,并发控制开销。

每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

线程实现

线程创建

继承Thread类

步骤:继承Thread类;重写run()方法;调用start开启线程。

public class TestThread extends Thread {
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 5; i++) {
System.out.println("测试线程---------" + i);
}
}

public static void main(String[] args) {
// main线程,主线程
// 创建一个线程对象
TestThread testThread1 = new TestThread();

// 调用start方法开启线程
testThread1.start();

for (int i = 0; i < 100; i++) {
System.out.println("主线程-----------" + i);
}
}
}

可见两个线程交替运行。注:线程开启并不一定立即执行,由CPU调度执行。

// 一个多线程下载图片的样例,使用了commons io库。
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

public class TestThread2 extends Thread {

private String url;//图片地址
private String name;//文件名

public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}

@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载文件:" + name);
}

public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://img.lfalive.top/PT/HUDPT.jpg", "hudpt.jpg");
TestThread2 t2 = new TestThread2("https://img.lfalive.top/PT/hitpt.JPG", "hitpt.jpg");
TestThread2 t3 = new TestThread2("https://img.lfalive.top/PT/byrpt.JPG", "byrpt.jpg");

t1.start();
t2.start();
t3.start();
}
}


class WebDownloader {
//下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
}
}

实现Runnable接口

步骤:定义MyRunnable类实现Runnable接口;实现run()方法,编写线程执行体;创建线程对象,调用start()方法启动线程。

public class TestThread3 implements Runnable {
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("测试线程---------" + i);
}
}

public static void main(String[] args) {
// main线程,主线程

// 创建一个Runnable接口的实现类对象
TestThread3 t1 = new TestThread3();

// 创建线程对象,通过线程对象来开启线程
new Thread(t1).start();

for (int i = 0; i < 20; i++) {
System.out.println("主线程-----------" + i);
}
}
}

多线程下载图片的样例也可用该方法实现。

不建议继承Thread类,因为需要避免OOP单继承局限性;建议使用Runnable接口,可以避免单继承局限性,灵活方便,方便同一个对象被多个线程使用,即”将任务和线程完全分离“。

并发问题

以售卖火车票为例,多个线程操作同一个资源的情况下,线程不安全,数据紊乱。

//模拟龟兔赛跑(并未考虑兔子的休息),多线程操作同一个资源
public class Race implements Runnable {
private static String winner;

@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//跑一步
System.out.println(Thread.currentThread().getName() + "--->跑了" + i + "步");
//比赛是否结束
if (gameOver(i)) {
break;
}
}
}

private boolean gameOver(int steps) {
//已有胜者
if (winner != null) {
return true;
}
//到终点
if (steps >= 100) {
winner = Thread.currentThread().getName();
System.out.println("Winner is " + winner);
return true;
}
return false;
}

public static void main(String[] args) {
Race race = new Race();
new Thread(race, "乌龟").start();
new Thread(race, "兔子").start();
}
}

winner变量即为公共资源。

实现Callable接口及线程池

Callable好处:可以定义返回值,可以抛出异常。线程池参考:

//使用Callable接口实现图片多线程下载
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;


public class TestCallable implements Callable<Boolean> {
private String url;//图片地址
private String name;//文件名

public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}

@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载文件:" + name);
return true;
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("https://img.lfalive.top/PT/HUDPT.jpg", "hudpt.jpg");
TestCallable t2 = new TestCallable("https://img.lfalive.top/PT/hitpt.JPG", "hitpt.jpg");
TestCallable t3 = new TestCallable("https://img.lfalive.top/PT/byrpt.JPG", "byrpt.jpg");

//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);

//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);

//获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();

System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);

//关闭服务
ser.shutdown();
}
}


class WebDownloader {
//下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常");
}
}
}

背景:经常创建销毁使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。

好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理:核心池的大小corePoolSize,最大线程数maximumPoolSize,线程没有任务时最多保持多长时间后会终止keepAliveTime

相关API:ExecutorsExecutorService

newFixedThreadPool()方法创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。ExecutorService也可以和Runnable配合使用,如execute(Runnable)submit(Runnable)submit(Callable)等。

FutureTask实现了Future接口,可以把FutureTask交给Executor执行;也可以通ExecutorService.submit()方法返回一个FutureTask,然后执行FutureTask.get()方法。通过返回的Future对象,我们可以检查提交的任务是否执行完毕。如果任务执行完成,future.get()方法会返回Callable任务的执行结果。注意,future.get()方法会产生阻塞。

调用shutdown()方法之后,ExecutorService不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。如果我们想立即关闭,可以调用ExecutorService.shutdownNow()方法。这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。

静态代理

真实对象和代理对象都要实现同一个接口,代理对象要代理真实角色。

好处:代理对象可以做很多真实对象做不了的事情,真实对象可以专注做自己的事情。

public class StaticProxy {
public static void main(String[] args) {
You you = new You("张三");//谁要结婚
new WeddingCompany(you).HappyMarry();//交给婚庆公司安排
}
}

interface Marry {
void HappyMarry();
}

//真实角色:去结婚
class You implements Marry {
private String name;

public You(String name) {
this.name = name;
}

@Override
public void HappyMarry() {
System.out.println(this.name + "结婚了,恭喜!");
}
}

//代理角色:帮target结婚
class WeddingCompany implements Marry {
private Marry target;

public WeddingCompany(Marry target) {
this.target = target;
}

@Override
public void HappyMarry() {
before();
this.target.HappyMarry();//这就是真实对象
after();
}

private void after() {
System.out.println("事后,结尾款。");
}

private void before() {
System.out.println("事前,布置会场。");
}
}

Lambda表达式

任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。对于函数式接口,可以通过lambda表达式来创建该接口的对象。

//简化过程
public class TestLambda1 {
//3.静态内部类
static class Like2 implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda2");
}
}

public static void main(String[] args) {
ILike like = new Like();
like.lambda();

like = new Like2();
like.lambda();

//4.局部内部类
class Like3 implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}

like = new Like3();
like.lambda();

//5.匿名内部类
like = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4");
}
};
like.lambda();

//6.用lambda简化
like = () -> System.out.println("I like lambda5");
like.lambda();
}
}

//1.定义一个函数式接口
interface ILike {
void lambda();
}

//2.实现类
class Like implements ILike {
@Override
public void lambda() {
System.out.println("I like lambda");
}
}

//带参数的lambda表达式
public class TestLambda2 {
public static void main(String[] args) {
ILove love = (a, b) -> System.out.println(a + " love " + b);
love.love(2, 5);
}
}

interface ILove {
void love(int a, int b);
}

线程状态

线程方法

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interpret() 中断线程,别用这个方式
boolean isAlive() 测试线程是否处于活动状态

停止线程

不推荐使用JDK提供的stop(),destroy()方法,已废弃。推荐让线程自己停下来。简直使用一个标志位进行终止变量,当flag=false,则终止线程运行。

public class TestStop implements Runnable {
private boolean flag = true;

@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("running" + i++);
}
}

public void stop() {
this.flag = false;
}

public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main" + i);
if (i == 900) {
testStop.stop();
System.out.println("time to stop");
}
}
}
}

线程休眠

sleep存在异常InterruptException,sleep时间达到后线程进入就绪状态,可以模拟网络延时、倒计时等。

每个对象都有一个锁,sleep不会释放锁。

//倒计时器
public class TestSleep {
public static void main(String[] args) {
try {
countDown(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void countDown(int num) throws InterruptedException {
while (num > 0) {
System.out.println(num--);
Thread.sleep(1000);
}
}
}

线程礼让yield

让当前正在执行的线程暂停,但不阻塞。将线程从运行状态转为就绪状态,让CPU重新调度。礼让不一定成功,取决于CPU。

合并线程

Join()合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。

public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("vip-->" + i);
}
}

public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();

for (int i = 0; i < 300; i++) {
if (i == 200) {
thread.join();
}
System.out.println("main-->" + i);
}
}
}

join2

线程状态观测

线程状态处于以下几个状态之一:NEW、RUNNABLE、BLOCKED、WATTING、TIMED_WAITTING、TERMINATED

public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 2; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("-------------");
});

Thread.State state = thread.getState();
System.out.println(state);

thread.start();
state = thread.getState();
System.out.println(state);

while (state != Thread.State.TERMINATED) {
Thread.sleep(100);
state = thread.getState();
System.out.println(state);
}
}
}

线程优先级

线程的优先级用数字表示,通过getPriority()获取优先级,通过setPriority(int)改变优先级。默认最大优先级MAX_PRIORITY为10,最小MIN_PRIORITY为1,可修改。

public class TestPriority {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);

t1.setPriority(1);
t2.setPriority(4);
t3.setPriority(Thread.MAX_PRIORITY);

t1.start();
t2.start();
t3.start();
}
}

class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
}
}

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,都是看CPU的调度

守护线程

线程分为守护线程用户线程。虚拟机必须确保用户线程执行完毕,但不必等待守护线程执行完毕。守护线程,和后台记录操作日志、监控内存、垃圾回收等。

thread.setDaemon(true); //默认是false,表示用户线程,正常线程都是用户线程。

线程同步

并发:同一个对象多个线程同时操作。

线程同步其实就是一种等待机制,多个线程进入这个对象的等待池形成队列。

==>加入锁机制synchronized,一个线程获得对象的排它锁,独占资源,其他线程必须等待其使用完后释放锁。锁机制会引起一定程度的性能问题,以换取安全性。例如,如果一个优先级高的线程等一个优先级低的线程释放锁,会导致优先级倒置。

不安全的例子

//线程不安全的例子
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> list.add(Thread.currentThread().getName())).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}

最后集合大小小于10000,因为同时可能会有多个线程操作一个对象。

同步方法和同步块

synchronized关键字有两种用法,synchronized方法和synchronized块。

同步方法

public synchronized void method(int args) {}

每个对象都有一把锁,synchronized方法都必须获得调用该方法的对象的锁才能运行,线程一旦执行,就独占该锁,直到方法返回释放。缺点:将一个大的方法声明为synchronized会影响效率,锁得太多,浪费资源。

同步块

synchronized (Obj) {}

Obj称之为同步监视器,Obj可以是任何对象,推荐使用共享资源作为同步监视器。同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这对象本身。

public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}

不安全的例子,加上同步块后就安全了。

CopyOnWriteArrayList

CopyOnWriteArrayList,写数组的拷贝,支持高效率并发且是线程安全的,读操作无锁。所有可变操作都是通过对底层数组进行一次复制来实现。底层实现添加的原理是先copy出一个容器(可以简称副本),再在副本里进行修改,最后把副本的引用地址赋值给之前旧容器的地址。在修改数据期间,其他线程如果读取数据,是读取到旧容器里的数据。

参考:高并发编程之CopyOnWriteArrayList介绍

CopyOnWriteArrayList也是属于java.util.concurrent工具包的,Java的concurrent用法详解

public class TestJUC {
public static void main(String[] args){
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> list.add((Thread.currentThread().getName()))).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}

死锁

多个线程互相抱着对方需要的资源,形成僵持。

//两个人化妆,需要镜子和口红,都是抱着对方需要的资源,形成死锁。
public class DeadLock {
public static void main(String[] args) {
new Makeup(0, "Nina").start();
new Makeup(1, "Amelie").start();
}
}

class Lipstick {
}

class Mirror {
}

class Makeup extends Thread {
static final Lipstick lipstick = new Lipstick();
static final Mirror mirror = new Mirror();
int choice;
String grilName;

Makeup(int choice, String grilName) {
this.choice = choice;
this.grilName = grilName;
}

@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {
System.out.println(this.grilName + " gets lipstick");
Thread.sleep(1000);
synchronized (mirror) {
System.out.println(this.grilName + " gets mirror");
}
}
} else {
synchronized (mirror) {
System.out.println(this.grilName + " gets mirror");
Thread.sleep(2000);
synchronized (lipstick) {
System.out.println(this.grilName + " gets lipstick");
}
}
}
}
}

产生死锁的四个必要条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

想办法打破其中的一个或多个条件就可以避免。

Lock(锁)

通过显式定义同步锁对象(Lock对象)来实现同步。java.util.concurrent.locks.Lock接口可以控制多个线程对共享资源进行访问。ReentrantLock 是可重入锁,实现了显式加锁、释放锁。

private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock();
try{
//保证线程安全的代码
} finally {
lock.unlock();
//如果同步代码有异常,要将unlock()写入finally块。
}
}

对比:Lock是显式,synchronized是隐式。Lock只有代码块锁,synchronized有代码块锁和方法锁。使用Lock锁,JVM将话费较少时间调度线程,性能更好,且具有更好的扩展性。一般优先使用Lock>同步代码块>同步方法

线程通信

java提供了几个方法解决线程之间的通信问题。均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException

方法名 作用
wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

生产者消费者问题中,仅有synchronized是不够的,synchronized可阻止并发更新同一个共享资源,但不能用来实现不同线程之间的消息传递。

管程法

生产者将生产号的数据放入缓冲区,消费者从缓冲区拿出数据。

//管程法——生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}

//生产者
class Producer extends Thread {
SynContainer container;

public Producer(SynContainer container) {
this.container = container;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
}
}
}

//消费者
class Consumer extends Thread {
SynContainer container;

public Consumer(SynContainer container) {
this.container = container;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.pop();
}
}
}

//产品
class Chicken {
int id;

public Chicken(int id) {
this.id = id;
}
}

//缓冲区
class SynContainer {
Chicken[] chickens = new Chicken[10]; //容器
int count = 0; //容器计数器

public synchronized void push(Chicken chicken) {
if (count == chickens.length) { //等待消费者消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产并通知消费者消费
chickens[count++] = chicken;
System.out.println("produced id = " + chicken.id + ", container " + count);
this.notifyAll();
}

public synchronized void pop() {
if (count == 0) { //等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费并通知生产者继续生产
System.out.println("consumed id = " + chickens[--count].id + ", container " + count);
this.notifyAll();
}
}

信号灯法

//信号灯法,标志位解决
public class TestPc2 {
public static void main(String[] args) {
TV tv = new TV();
new Actor(tv).start();
new Watcher(tv).start();
}
}

//生产者
class Actor extends Thread {
TV tv;

public Actor(TV tv) {
this.tv = tv;
}

@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
this.tv.act("dance");
} else {
this.tv.act("sing");
}
}
}
}

//消费者
class Watcher extends Thread {
TV tv;

public Watcher(TV tv) {
this.tv = tv;
}

@Override
public void run() {
for (int i = 0; i < 10; i++) {
this.tv.watch();
}
}
}

//产品
class TV {
String show;
boolean flag = true;//演员表演 T; 观众观看 F

public synchronized void act(String show) {
if (!flag) { //等观众看完
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Act " + show);
this.notifyAll();
this.show = show; //新节目
this.flag = !this.flag; //修改标志位
}

public synchronized void watch() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Watch " + show);
this.notifyAll();
this.flag = !this.flag;
}
}