线程通讯需要使用到的三个方法: wait()、notify()与notifyAll()。该三个方法不在Thread类中定义,而是在Object类中
wait() 方法:让当前线程挂起并放弃cpu使用权,同步资源,使别的线程可以访问并修改同步资源,而当前线程排队等待,直到被唤醒后才能再次获取cpu使用权和同步资源的访问。
notify()方法: 唤醒正在排队等待同步资源的线程中优先级最高者结束等待,得到cpu的使用权和对同步资源的访问。
notifyAll()方法:唤醒正在排队等待同步资源的所有线程结束等待,竞争资源。
注意:这三个方法只有在同步代码块或同步方法中使用,在其他地方使用会报错 java.lang.IllegalArgumentException 异常
案例分析:
1、创建三个线程(A,B,C),让该三个线程交替循环10次打印字母'A','B','C',最终的结果为:ABCABCABCABCABC...
继承Thread类的方式:
package org.xyz.java.thread.demo04;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 案例练习 - 使用继承Thread的方式
* 创建三个线程(A,B,C),让该三个线程交替循环10次打印字母'A','B','C',最终的结果为:ABCABCABCABCABC...
* @author kevin.chen
*
*/
public class PrintLock {
public static void main(String[] args) throws Exception {
At a = new At();
Bt b = new Bt();
Ct c = new Ct();
a.setName("甲");
b.setName("乙");
c.setName("丙");
a.start();
b.start();
c.start();
}
// 使用lock锁+condition的方式:
private static Lock lock = new ReentrantLock();
private static Condition aCond = lock.newCondition();
private static Condition bCond = lock.newCondition();
private static Condition cCond = lock.newCondition();
private static int count = 0;
static class At extends Thread {
@Override
public void run() {
for(int i = 0;i < 10; i++) {
try {
lock.lock();
try {
while(count % 3 != 0) { // 这里是不等条件
aCond.await(); // 相当于wait 让该线程等待
}
System.out.println(Thread.currentThread().getName() + ": A");
count ++;
Thread.sleep(100);
bCond.signal(); // 相当于notify 唤醒下一个线程
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
}
static class Bt extends Thread {
@Override
public void run() {
for(int i = 0;i < 10; i++) {
try {
lock.lock();
try {
while(count % 3 != 1) { // 这里是不等条件
bCond.await(); // 相当于wait 让该线程等待
}
System.out.println(Thread.currentThread().getName() + ": B");
count ++;
Thread.sleep(100);
cCond.signal(); // 相当于notify 唤醒下一个线程
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
}
static class Ct extends Thread {
@Override
public void run() {
for(int i = 0;i < 10; i++) {
try {
lock.lock();
try {
while(count % 3 != 2) { // 这里是不等条件
cCond.await(); // 相当于wait 让该线程等待
}
System.out.println(Thread.currentThread().getName() + ": C");
count ++;
Thread.sleep(100);
aCond.signal(); // 相当于notify 唤醒下一个线程
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
}
}
继承+同步的方式:该方式没有用到线程的通讯方法,属于变种(不推荐)
package org.xyz.java.thread.demo04;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 案例练习 - 使用继承Thread的方式
* 创建三个线程(A,B,C),让该三个线程交替循环10次打印字母'A','B','C',最终的结果为:ABCABCABCABCABC...
* @author kevin.chen
*
*/
public class PrintThread {
public static void main(String[] args) throws Exception {
At a = new At();
Bt b = new Bt();
Ct c = new Ct();
a.setName("甲");
b.setName("乙");
c.setName("丙");
a.start();
Thread.sleep(100); // 此处是为了保证第一次的顺序为ABC
b.start();
Thread.sleep(100);
c.start();
}
// // 使用synchronized同步的方式
// private static int count = 0;
//
// static class At extends Thread {
// @Override
// public void run() {
// for(int i = 0;i < 10; ) {
// synchronized(PrintThread.class) { // 这里必须使用同一类对象锁,不能使用this/At.class
// while(count % 3 == 0) {
// System.out.println(Thread.currentThread().getName() + ": A");
// count ++;
// i ++;
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
// }
// }
// }
//
// static class Bt extends Thread {
// @Override
// public void run() {
// for(int i = 0;i < 10;) {
// synchronized(PrintThread.class) { // 这里必须使用同一类对象锁,不能使用this/At.class
// while(count % 3 == 1) {
// System.out.println(Thread.currentThread().getName() + ": B");
// count ++;
// i++;
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
// }
// }
// }
//
// static class Ct extends Thread {
// @Override
// public void run() {
// for(int i = 0;i < 10;) {
// synchronized(PrintThread.class) { // 这里必须使用同一类对象锁,不能使用this/At.class
// while(count % 3 == 2) {
// System.out.println(Thread.currentThread().getName() + ": C");
// count ++;
// i++;
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
// }
// }
// }
// 使用lock锁的方式:
private static Lock lock = new ReentrantLock();
private static int count = 0;
static class At extends Thread {
@Override
public void run() {
for(int i = 0;i < 10;) { // 此处的i++ 操作一定要放在while循环中
try {
lock.lock();
while(count % 3 == 0) {
System.out.println(Thread.currentThread().getName() + ": A");
count ++;
i ++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
// 错误的写法,注意for循环和try的位置,错误的写法线程无法执行,这个问题记录下
// try {
// for(int i = 0;i < 10; ) { // 此处的i++ 操作一定要放在while循环中
// lock.lock();
// while(count % 3 == 0) {
// System.out.println(Thread.currentThread().getName() + ": A");
// count ++;
// i ++;
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
// } finally {
// lock.unlock();
// }
}
}
static class Bt extends Thread {
@Override
public void run() {
for(int i = 0;i < 10;) { // 此处的i++ 操作一定要放在while循环中
try {
lock.lock();
while(count % 3 == 1) {
System.out.println(Thread.currentThread().getName() + ": B");
count ++;
i ++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
}
static class Ct extends Thread {
@Override
public void run() {
for(int i = 0;i < 10;) { // 此处的i++ 操作一定要放在while循环中
try {
lock.lock();
while(count % 3 == 2) {
System.out.println(Thread.currentThread().getName() + ": C");
count ++;
i ++;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
}
}
实现Runnable的方式:
package org.xyz.java.thread.demo04;
/**
* 案例练习 - 使用实现Runnable的方式
* 创建三个线程(A,B,C),让该三个线程交替循环5次打印字母'A','B','C',最终的结果为:ABCABCABCABCABC...
* @author kevin.chen
*
*/
public class PrintRunnable {
public static void main(String[] args) throws Exception {
int count = 5;
Object a = new Object(); // a对象锁
Object b = new Object(); // b对象锁
Object c = new Object(); // c对象锁
Print p1 = new Print(count,c,a,"A");
Print p2 = new Print(count,a,b,"B");
Print p3 = new Print(count,b,c,"C");
Thread ta = new Thread(p1);
Thread tb = new Thread(p2);
Thread tc = new Thread(p3);
ta.setName("甲");
tb.setName("乙");
tc.setName("丙");
ta.start();
Thread.sleep(100); // 此处是为了保证第一次的顺序为ABC
tb.start();
Thread.sleep(100);
tc.start();
}
}
class Print implements Runnable {
private int count; // 循环次数
private Object prev; // 前一个对象
private Object self; // 自身对象
private String content; // 输出内容
public Print(int count,Object prev,Object self,String content) {
this.count = count;
this.prev = prev;
this.self = self;
this.content = content;
}
@Override
public void run() {
int num = count * 3;
while(num > 0) {
synchronized(prev) {
synchronized(self) {
// 唤醒其他线程
self.notifyAll();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + content);
// System.out.print(content);
num --;
}
try {
if(num == 0) { // 增加这个判断可以保证线程执行完之后能自动退出,如果没有该判断,程序是不会自动退出的
prev.notifyAll();
}else {
// 当前线程挂起等待
prev.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
甲:A
乙:B
丙:C
使用信号灯来实现
可以看到信号灯的变化情况如下:
初始(A=1,B=0,C=0)—>执行线程A时(A=1,B=0,C=0)—>执行线程B时(A=0,B=1,C=0)—>执行线程C时(A=0,B=0,C=1)—>再执行线程A(A=1,B=0,C=0)… 如此循环。
package org.xyz.java.thread.demo04;
import java.util.concurrent.Semaphore;
public class SemaphoreAbc {
// 初始化线程A的sA中的计数器为1,保证先执行
private static Semaphore sA = new Semaphore(1);
// 初始化线程B的sB中的计数器为0
private static Semaphore sB = new Semaphore(0);
// 初始化线程C的sC中的计数器为0
private static Semaphore sC = new Semaphore(0);
static class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
// 线程A 获取信号进行执行,将sA的计数器减1,当sA的计数器减为0时,该线程无法获取到信号,则线程挂起等待
sA.acquire();
System.out.println(Thread.currentThread().getName() + ": A");
// 线程A执行后,将sB的计数器加1
sB.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class ThreadB extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
// 线程B 获取信号进行执行,将sB的计数器减1,当sB的计数器减为0时,该线程无法获取到信号,则线程挂起等待
sB.acquire();
System.out.println(Thread.currentThread().getName() + ": B");
// 线程B执行后,将sC的计数器加1
sC.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class ThreadC extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
// 线程C 获取信号进行执行,将sC的计数器减1,当sC的计数器减为0时,该线程无法获取到信号,则线程挂起等待
sC.acquire();
System.out.println(Thread.currentThread().getName() + ": C");
// 线程C执行后,将sA的计数器加1
sA.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
ThreadC c = new ThreadC();
a.setName("甲");
b.setName("乙");
c.setName("丙");
a.start();
b.start();
c.start();
}
}