目录
一、Object的wait()、notify()、notifyAll()方法
二、Condition的await()、signal()、signalAll()方法
线程间的通信方式常用的有如下几种:
Object的wait()、notify()、notifyAll()方法;
Condition的await()、signal()、signalAll()方法;
CountDownLatch:用于某个线程A等待若干个其他线程执行完之后,它本身才会执行;
CyclicBarrier :一组线程等待至某个状态后再全部同时执行;
Semaphore:用于控制对某组资源的访问权限;
一、Object的wait()、notify()、notifyAll()方法
我们通过一个例子来理解这种线程间的通讯:开启两个线程,一个用来打印10以内的奇数,一个用来打印10以内的偶数。
代码如下:
public class ThreadComunicationDemo01 {
private int i = 0;//要打印得数
private Object obj = new Object();
//奇数打印方法,由奇数线程调用
public void odd(){
//1.判断i是否小于10
while(i < 10){
synchronized(obj){
if(i % 2 == 1){
System.out.println(Thread.currentThread().getName() +"奇数:" + i);
i++;
//notify()方法和wait()必须放在synchronized同步代码块或方法中
//否则会报IllegalMonitorStateException异常
obj.notify();//唤醒偶数线程打印
}else {
try {
obj.wait();//等待偶数线程打印完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//偶数打印方法,由偶数线程调用
public void even(){
//1.判断i是否小于10
while(i < 10){
synchronized (obj){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() +"偶数:" + i);
i++;
//notify()方法和wait()必须放在synchronized同步代码块或方法中
//否则会报IllegalMonitorStateException异常
obj.notify();//唤醒奇数线程打印
}else {
try {
obj.wait();//等待奇数线程打印完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
final ThreadComunicationDemo01 threadComunicationDemo01 = new ThreadComunicationDemo01();
//1.开启奇数打印线程
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo01.odd();
}
});
//1.开启偶数打印线程
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo01.even();
}
});
thread1.start();
thread2.start();
}
}
输出结果:
Thread-1偶数:0
Thread-0奇数:1
Thread-1偶数:2
Thread-0奇数:3
Thread-1偶数:4
Thread-0奇数:5
Thread-1偶数:6
Thread-0奇数:7
Thread-1偶数:8
Thread-0奇数:9
二、Condition的await()、signal()、signalAll()方法
还是上面的例子我们用Condition来实现:
public class ThreadComunicationDemo02 {
private int i = 0;//要打印得数
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//奇数打印方法,由奇数线程调用
public void odd(){
//1.判断i是否小于10
while(i < 10){
lock.lock();
try {
if(i % 2 == 1){
System.out.println(Thread.currentThread().getName() + "奇数:" + i);
i++;
condition.signal();//唤醒偶数线程打印
}else {
try {
condition.await();//等待偶数线程打印完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
lock.unlock();
}
}
}
//偶数打印方法,由偶数线程调用
public void even(){
//1.判断i是否小于10
while(i < 10){
lock.lock();
try{
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + "偶数:" + i);
i++;
condition.signal();//唤醒奇数线程打印
}else {
try {
condition.await();//等待奇数线程打印完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
final ThreadComunicationDemo02 threadComunicationDemo01 = new ThreadComunicationDemo02();
//1.开启奇数打印线程
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo01.odd();
}
});
//1.开启偶数打印线程
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo01.even();
}
});
thread1.start();
thread2.start();
}
}
输出结果:
Thread-1偶数:0
Thread-0奇数:1
Thread-1偶数:2
Thread-0奇数:3
Thread-1偶数:4
Thread-0奇数:5
Thread-1偶数:6
Thread-0奇数:7
Thread-1偶数:8
Thread-0奇数:9
可以看到通过Object的的wait()、notify()方法和通过Condition的await()、signal()方法,实现的效果是一样的。那么他们二者有什么差别吗?通过上面的代码,我们总结如下:
- Object的wait()必须在synchronized(同步锁)下使用;
- Object的wait()必须要通过notify()方法进行唤醒;
- Condition的await()必须和Lock(互斥锁/共享锁)配合使用;
- Codition的await()必须通过signal()方法进行唤醒;
三、CountDownLatch
CountDownLatch是在jdk1.5被引入的,存在于java.util.concurrent包下。CountDownLatch能够使一个线程等待其他线程都执行完后再进行本身的执行操作,它的这一功能是通过一个计数器来实现的,这个计数器的初始值一般就是“其他线程的数量”。我们通过一个图来理解一下CountDownLatch的执行原理:
如上图所示:假如一共有四个线程T1、T2、T3、TA,现在我们要求线程TA需要在其他的三个线程T1、T2、T3都执行完了以后才能执行。那初始时,我们设置CountDownLatch计数器的值为3,然后让线程TA调用CountDownLatch的await()方法,这个时候线程TA就会一直阻塞在哪里,在阻塞的过程中他会不断的去检查计数器的数量,如果不为0,那么它就一直阻塞在哪里。我们让其他三个线程正常执行,并且每个线程在执行完自身的操作后让其调用CountDownLatch的countDown()方法,每调用一次这个方法CountDownLatch的计数器数量就会减1。所以当T1、T2、T3这三个线程搜执行完后,countDown()这个方法就被调用了3次,CountDownLatch的计数器就会减为0,此时,被阻塞的线程TA检测到计数器的值为0后就会被唤醒,执行其本身的操作。
我们继续通过一个例子来说明CountDownLatch的使用:假设有三个运动员线程和一个教练线程,教练线程必须要等到三个运动员线程都准备好才能开始自身线程的训练工作。
代码如下:
public class ThreadComunicationDemo03 {
private CountDownLatch countDownLatch = new CountDownLatch(3);//设置要等待得运动员是3个
//运动员方法,由运动员线程调用
public void racer(){
//1.获取运动员名称(线程名)
String racerName = Thread.currentThread().getName();
//2.运动员开始准备:打印准备信息
System.out.println(racerName + "运动员正在准备。。。");
//3.让线程睡眠1000毫秒,表示运动员在准备
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.运动员准备完毕:打印准备完毕信息,同时计数-1
System.out.println(racerName + "运动员准备完毕!");
countDownLatch.countDown();
}
//教练方法,由教练线程调用
public void coach(){
//1.获取教练线程得名称
String coachName = Thread.currentThread().getName();
//2.教练等待所有运动员准备完毕,打印等待信息
System.out.println(coachName + "教练等待运动员准备。。。");
//3.调用countDownLatch得await()方法,等待其他运动员准备线程执行完毕
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.所有运动员准备就绪,教练开始训练:打印训练信息
System.out.println("所有运动员已经就绪," + coachName + "教练开始训练");
}
public static void main(String[] args) {
//1.创建TThreadComunicationDemo03实例
final ThreadComunicationDemo03 threadComunicationDemo03 = new ThreadComunicationDemo03();
//2.创建三个线程对象,调用ThreadComunicationDemo03的racer方法
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo03.racer();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo03.racer();
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo03.racer();
}
});
//3.创建一个线程对象,调用ThreadComunicationDemo03的coach方法
Thread coachThread = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo03.coach();
}
},"教练");
coachThread.start();
thread1.start();
thread2.start();
thread3.start();
}
}
输出结果:
hread-0运动员正在准备。。。
Thread-2运动员正在准备。。。
Thread-1运动员正在准备。。。
教练教练等待运动员准备。。。
Thread-2运动员准备完毕!
Thread-1运动员准备完毕!
Thread-0运动员准备完毕!
所有运动员已经就绪,教练教练开始训练
四、CyclicBarrier
CyclicBarrier也是在jdk1.5引入的,在java.util.concurrent包下。CyclicBarrier可以实现让“一组”线程等待至某个状态后再全部“同时”执行。CyclicBarrier底层是基于ReentrantLock和Codition实现的,有兴趣的同学可以找来源码看看。
我们继续通过一个例子来说明CyclicBarrier的使用:我们开启三个线程,并且等到这三个线程都处于就绪状态后同时让这三个线程一起执行。
public class ThreadComunicationDemo04 {
//参数3是表示参与CyclicBarrier的线程数
private CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
public void startThread(){
//1.打印线程准备启动
String name = Thread.currentThread().getName();
System.out.println(name + "正在准备");
//2.调用CyclicBarriar的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 ThreadComunicationDemo04 threadComunicationDemo04 = new ThreadComunicationDemo04();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo04.startThread();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo04.startThread();
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo04.startThread();
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
输出结果:
Thread-0正在准备
Thread-1正在准备
Thread-2正在准备
Thread-2已经启动完毕:1582774772670
Thread-0已经启动完毕:1582774772670
Thread-1已经启动完毕:1582774772670
通过输出结果我们可以看到,这三个线程是同时执行的,都是在1582774772670这个时间点开始执行。
五、Semaphore
Semaphore也是在jdk1.5后引入的,同样也在java.util.concurrent包下。Semaphore主要是用于控制对某组资源的访问权限。这样说可能还是比较模糊,我们继续通过一个例子来说明Semaphore的使用:假设现在有8个工人3台机器,机器为互斥资源(即每次只能一个人使用),代码如下:
public class ThreadComunicationDemo05 {
//内部类,当然你也可以新建一个文件定义在外面
static class Work implements Runnable{
private int workerNum;//工人的工号
private Semaphore semaphore;//机器数
public Work(int workerNum,Semaphore semaphore){
this.semaphore = semaphore;
this.workerNum = workerNum;
}
@Override
public void run() {
try{
//1.工人要去获取机器
semaphore.acquire();
//2.打印工人获取到机器,开始工作
String workerName = Thread.currentThread().getName();
System.out.println(workerName + "获取到机器,开始工作。。。");
//3.线程睡眠1000毫秒,模拟工人使用机器的过程
Thread.sleep(1000);
//4.使用完毕,释放机器,打印工人使用完毕,释放机器
semaphore.release();
System.out.println(workerName + "使用完毕,释放机器");
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int workers = 8;//工人数是8个
Semaphore semaphore = new Semaphore(3);//机器数是3个
//i代表工人的工号,8个工人线程开启,并使用3台机器进行工作
for (int i = 0; i < workers; i++){
new Thread(new Work(i,semaphore)).start();
}
}
}
输出结果:
Thread-1获取到机器,开始工作。。。
Thread-0获取到机器,开始工作。。。
Thread-2获取到机器,开始工作。。。
Thread-1使用完毕,释放机器
Thread-4获取到机器,开始工作。。。
Thread-0使用完毕,释放机器
Thread-5获取到机器,开始工作。。。
Thread-2使用完毕,释放机器
Thread-3获取到机器,开始工作。。。
Thread-3使用完毕,释放机器
Thread-7获取到机器,开始工作。。。
Thread-6获取到机器,开始工作。。。
Thread-5使用完毕,释放机器
Thread-4使用完毕,释放机器
Thread-6使用完毕,释放机器
Thread-7使用完毕,释放机器
通过上面的输出结果我们可以看到,三个机器在八个工人手里正常的完成了工作,这就是Semaphore的作用所在。
就写先到这里吧!!!!!!!!!!!!!!!!