1.为什么要线程通信
多个线程并发执行时,在默认情况下cpu是随机切换线程的,有时我们希望cpu按我们的规律执行
线程,此时就需要线程之间协调通信。
2.Object和Condition休眠唤醒方式
- Object的wait、notify、notifyAull
- Condition的await、signal、singalAll
Object和Condition休眠唤醒方式的区别
- object wait()必须在synchronized(同步锁)下使用
- Object wait()必须要通过nodify()方法进行唤醒
- condition await()必须和Lock(互斥锁/共享锁)配合使用
- condition await()必须通过signal()方法进行唤醒
2.1 Object的(wait、notify)的等待唤醒实现奇数偶数打印
public class OddEven {
private Integer num = 0;
private final Object obj = new Object();
/***
* 奇数打印方法,由奇数线程调用
*/
public void odd(){
synchronized (obj){
while (num<10){
if(num % 2 != 0){
System.out.println("奇数:num="+num);
num++;
obj.notify();//唤醒其他线程方法,唤醒偶数线程打印
}else {
try {
obj.wait();//等待偶数线程打印完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 偶数打印方法,由偶数线程调用
*/
public void even(){
synchronized (obj){
while (num<10){
if(num % 2 == 0){
System.out.println("偶数:num="+num);
num++;
obj.notify();//唤醒其他线程方法,唤醒奇数线程打印
}else {
try {
obj.wait();//等待奇数线程打印完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
final OddEven oddEven = new OddEven();
//1.开始奇数线程打印
Thread thread1 = new Thread(new Runnable() {
public void run() {
oddEven.odd();
}
});
//2.开启偶数线程打印
Thread thread2 = new Thread(new Runnable() {
public void run() {
oddEven.even();
}
});
thread1.start();
thread2.start();
}
}
2.2 Condition的等待唤醒(await、signal)实现奇数偶数打印
public class OddEven {
private Integer num = 0;
private Lock lock = new ReentrantLock();//注意不要设置ture
private Condition condition = lock.newCondition();
/***
* 奇数打印方法,由奇数线程调用
*/
public void odd(){
while (num<10){
lock.lock();
try {
if(num % 2 != 0){
System.out.println("奇数:num="+num);
num++;
condition.signal();//唤醒其他线程方法,唤醒偶数线程打印
}else {
try {
condition.await();//等待偶数线程打印完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
/**
* 偶数打印方法,由偶数线程调用
*/
public void even(){
while (num<10){
lock.lock();
try {
if(num % 2 == 0){
System.out.println("偶数:num="+num);
num++;
condition.signal();//唤醒其他线程方法,唤醒奇数线程打印
}else {
try {
condition.await();//等待奇数线程打印完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
final OddEven oddEven = new OddEven();
//1.开始奇数线程打印
Thread thread1 = new Thread(new Runnable() {
public void run() {
oddEven.odd();
}
});
//2.开启偶数线程打印
Thread thread2 = new Thread(new Runnable() {
public void run() {
oddEven.even();
}
});
thread1.start();
thread2.start();
}
}
3.CountDownLatch方式
- CountDownLatch是在java1.5被引入的,存在于java.util。concurrent包下。
- CountDownLatch能够使一个线程等待其他线程完成各自的工作后在执行
- CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量
CountDownLatch类创建时,会指定等待线程数量初始值,如图所示,等待三个线程,
要等待的线程是TA会调用await()方法,await()方法会判断当前值是否为0(等待线程数量初始值)
会进行阻塞,T1/T2/T3会分别启动,调用countDown()方法,会将计数器的初始值进行
减一操作,当计数器的值到0时,表示所有线程已经完成了任务,然后TA开始执行。
栗子:教练等待运动员到齐训练
public class CountDownLatchDemo {
private CountDownLatch countDownLatch = new CountDownLatch(3);//设置要等待的线程数量
/***
* 运动员方法
*/
public void racer(){
//1.获取运动员线程名称
String name = Thread.currentThread().getName();
//2.运动员开始准备
System.out.println(name+"运动员正在准备......");
try {
//3.准备过程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("运动员准备完毕!");
countDownLatch.countDown();
}
/***
* 教练方法
*/
public void coach(){
//1.获取教练线程名称
String name = Thread.currentThread().getName();
//2.教练等待所有运动员准备完毕:打印等待信息
System.out.println(name+"教练等待所有运动员准备......");
//3.调用CountDownLatch的await()方法等待其他线程执行完毕
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.所有运动员已就绪,教练开始训练,打印训练信息
System.out.println("所有运动员已就绪"+name+"开始训练!");
}
public static void main(String[] args) {
//1.创建CountDownLatchDemo实例
final CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo();
//2.创建三个运动员
Thread thread1 = new Thread(new Runnable() {
public void run() {
countDownLatchDemo.racer();
}
});
Thread thread2 = new Thread(new Runnable() {
public void run() {
countDownLatchDemo.racer();
}
});
Thread thread3 = new Thread(new Runnable() {
public void run() {
countDownLatchDemo.racer();
}
});
//3.创建教练
Thread thread4 = new Thread(new Runnable() {
public void run() {
countDownLatchDemo.coach();
}
});
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
打印结果:
Thread-0运动员正在准备......
Thread-1运动员正在准备......
Thread-2运动员正在准备......
Thread-3教练等待所有运动员准备......
运动员准备完毕!
运动员准备完毕!
运动员准备完毕!
所有运动员已就绪Thread-3开始训练!
4.CyclicBarrier
- CyclicBarrier是在java1.5被引入的,存在于java.util.concurrent包下。
- CyclicBarrier实现让一组线程等待至某个状态之后再全部同时执行。
- CyclicBarrier底层是基于ReentrantLock和Condition实现
三个线程同时启动案例:
public class CyclicBarrierDemo {
private CyclicBarrier cyclicBarrier = new CyclicBarrier(3);//3代表三个线程同时启动
public void startThread(){
//1.打印线程准备启动
String name = Thread.currentThread().getName();
System.out.println(name+"正在准备......");
//2.调用cyclicBarrier的await()方法
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
//3.打印线程启动完毕信息
System.out.println(name+"已经启动完毕:"+new Date().getTime());
}
public static void main(String[] args) {
final CyclicBarrierDemo cyclicBarrierDemo = new CyclicBarrierDemo();
Thread thread1 = new Thread(new Runnable() {
public void run() {
cyclicBarrierDemo.startThread();
}
});
Thread thread2 = new Thread(new Runnable() {
public void run() {
cyclicBarrierDemo.startThread();
}
});
Thread thread3 = new Thread(new Runnable() {
public void run() {
cyclicBarrierDemo.startThread();
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
打印结果:
Thread-0正在准备......
Thread-1正在准备......
Thread-2正在准备......
Thread-2已经启动完毕:1569399711592
Thread-0已经启动完毕:1569399711592
Thread-1已经启动完毕:1569399711592
5.Semaphore方式
Semaphore在java1.5被引入的,存在于java.util.concurrent包下,
用于控制对某组资源的访问权限
演示:工人使用机器工作8个工人使用3台机器工作,机器为互斥资源(即每次只能一个人使用)
public class SemaphoreDemo {
static class Work implements Runnable{
private int workerNum;//工人的工号
private Semaphore semaphore;//机器数
public Work(int workerNum,Semaphore semaphore){
this.workerNum = workerNum;
this.semaphore = semaphore;
}
public void run() {
try {
//1.工人要获取机器
semaphore.acquire();
//2.打印工人获取到机器,开始工作
String name = Thread.currentThread().getName();
System.out.println(name+"获取到机器开始工作...");
//3.线程睡眠1000毫秒,模拟工人使用机器过程
Thread.sleep(1000);
//4.使用完毕,释放机器,打印工人使用完毕,释放机器
semaphore.release();
System.out.println(name+"使用完毕,释放机器!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int wokers = 8;//工人数
Semaphore semaphore = new Semaphore(3);//机器数
for(int i=0;i<wokers;i++){
new Thread(
new Work(i,semaphore)
).start();
}
}
}
6.小结
wait和notify区别
- wait和notify都是object中的方法
- wait和notify执行前线程都必须获得对象锁
- wait的作用是使当前线程进行等待
- notify的作用是通知其他等待当前线程的对象锁的线程