多线程学习一
1.join
如图中所示,一个线程调用join后,进入阻塞状态。join的作用就是等待一个线程并暂停自己,直到等待的那个线程结束为止:
public static void main(String[] args) {
Thread A = new Thread(){
@Override
public void run() {
System.out.println("A start");
try {
sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("A end");
}
};
Thread B = new Thread(){
@Override
public void run() {
System.out.println("B start");
try {
A.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B end");
}
};
A.start();
B.start();
}
测试结果:
第一次:
A start
B start
A end
B end
第二次:
B start
A start
A end
B end
由于B调用了join,所以无论B是否先执行,都是等到A执行完毕后再执行到结束。
2.wait/notify
这两个是配合使用的,而且这两个方法是不Thread特有的,而是属于Object,java中所有类顶级父类都是Object,所以所有类都有这两个方法。他们出现的原因是,在对临界资源访问时需要加锁,但有时候需要放弃已有的锁,所以就出现了wait,让出当前的锁,让其他申请锁的线程得以执行,另外线程执行完之后需要调用notify,使之前让出的依旧持有锁从而可以运行。我们先看一个没有wait的例子
Object lock = new Object();
public static void main(String[] args) {
Thread A = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("A start");
synchronized (lock) {
System.out.println("A 1");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("A 2");
System.out.println("A 3");
}
System.out.println("A end");
}
});
Thread B = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("B start");
synchronized (lock) {
System.out.println("B 1");
System.out.println("B 2");
System.out.println("B 3");
}
System.out.println("B end");
}
});
A.start();
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
B.start();
}
运行结果
A start
A 1
B start
A 2
A 3
A end
B 1
B 2
B 3
B end
可见虽然B启动了,但临界区的锁被A持有,所以要等A临界区代码执行完毕后再执行B的邻接区代码,下面再看有wait的部分:
public static void main(String[] args) {
Object lock = new Object();
Thread A = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("A start");
synchronized (lock) {
System.out.println("A 1");
try {
lock.wait();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("A 2");
System.out.println("A 3");
}
System.out.println("A end");
}
});
Thread B = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("B start");
synchronized (lock) {
System.out.println("B 1");
System.out.println("B 2");
System.out.println("B 3");
lock.notify();
}
System.out.println("B end");
}
});
A.start();
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
B.start();
}
测试结果
A start
A 1
B start
B 1
B 2
B 3
B end
A 2
A 3
A end
可见A的确让出了锁资源,B得到执行,最后B唤醒A,A继续执行。如果B执行完之后没有调用notify,A是不会继续执行的,有疑问的可以试试。还有一个notifyAll方法,功能类似,是唤醒多个wait的线程
3.CountdownLatch
这是一个类似计数器的类,用于一个线程等待一定数量的线程都达到某种条件后才执行的场景,示例
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(){
@Override
public void run() {
System.out.println("A 开始等待");
try {
countDownLatch.await();
System.out.println("A 等待结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
IntStream.of(1,2,3).forEach(i->{
String name = "Thread"+i;
new Thread(){
@Override
public void run() {
System.out.println(name + "开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "执行完毕");
countDownLatch.countDown();
}
}.start();
});
}
结果:
A 开始等待
Thread2开始执行
Thread1开始执行
Thread3开始执行
Thread2执行完毕
Thread3执行完毕
Thread1执行完毕
A 等待结束
CountdownLatch的构造方法需要传入一个等待时数目,在需要等待的线程中调用await方法,之后每当一个线程执行完毕或在适当时候调用countDown,将计数器减一,减到0时,原来等待的线程就可以执行了
4.CyclicBarrier
这是一个类似于计数器的类,当一定数量的线程都达到某种状态时,这些线程才可以继续执行,示例:
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
Random random = new Random();
IntStream.of(1,2,3).forEach(i -> {
String name = "Thread" + i;
new Thread(){
@Override
public void run() {
setName(name);
System.out.println(getName() + "开始准备");
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"准备完毕");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("所有线程准备完毕" + getName() + "开始执行");
}
}.start();
});
}
运行结果:
Thread2开始准备
Thread1开始准备
Thread3开始准备
Thread2准备完毕
Thread3准备完毕
Thread1准备完毕
所有线程准备完毕Thread1开始执行
所有线程准备完毕Thread3开始执行
所有线程准备完毕Thread2开始执行
CyclicBarrier的构造函数需要传入等待时数量,然后每个线程在合适的时候调用await等待其他线程,当指定数量的线程都调用await后,所有线程一起开始执行。
CyclicBarrier构造还可以传入一个Runnable,当调用awit的线程达到一定数量时,会挑选一个线程率先执行这个Runnable.如下:
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,()->System.out.println(Thread.currentThread().getName() + "+++"));
Random random = new Random();
IntStream.of(1,2,3).forEach(i -> {
String name = "Thread" + i;
new Thread(){
@Override
public void run() {
setName(name);
System.out.println(getName() + "开始准备");
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"准备完毕");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("所有线程准备完毕" + getName() + "开始执行");
}
}.start();
});
}
Thread1开始准备
Thread3开始准备
Thread2开始准备
Thread1准备完毕
Thread2准备完毕
Thread3准备完毕
Thread3+++
所有线程准备完毕Thread1开始执行
所有线程准备完毕Thread3开始执行
所有线程准备完毕Thread2开始执行
5.Semaphore
类似于我们学过的进程同步的PV操作,看一个生产者消费者的例子:
public static void main(String[] args) throws InterruptedException {
Semaphore apple = new Semaphore(0);
Semaphore plate = new Semaphore(1);
Thread Producer = new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
try {
System.out.println("申请盘子");
plate.acquire();
System.out.println("获取盘子");
System.out.println("生成苹果");
apple.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
Thread Consumer = new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
try {
System.out.println("申请苹果");
apple.acquire();
System.out.println("获取苹果");
System.out.println("释放盘子");
plate.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
Consumer.start();
Thread.sleep(1000);
Producer.start();
}
申请苹果
申请盘子
获取盘子
生成苹果
获取苹果
释放盘子
用起来很简单,acquire申请一个资源,release释放一个,同样也可以选择调用重载方法,一次申请或释放多个,构造方法也可以传入一个布尔类型变量,表示是否公平分配(若为false,表示多个线程竞争一个资源时,谁等的时间长,谁优先)。另外acquire为阻塞方法,一个线程可能无限等待,所有有非阻塞的方法tryAcquire,调用availablePermits检测当前可用资源数。