以前讨论了线程之间的互斥,这里讨论线程之间的通信。线程之间的通信即A线程唤醒正在阻塞的其他线程,使其继续执行。最传统的方式即wait,notify,先上例子。
这里要实现的效果是A线程输出一次“AAA",然后B线程输出一下“BBB”,由于输出AAA或者BBB不是原子性操作(即输出不是一下子就能完成的,这期间CPU可能跑到其他线程上去执行)所以显然线程之间的互斥是必须的,我们这里使用synchronized关键字来解决线程之间的互斥。
public Class TheadCommunication1{
public synchronized void out(String str){
//这样做的目的纯粹是为了让其出现错误,以突出synchronized的作用。
for(char c :str.toCharArray()){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print(c);
}
System.out.println();
}
public static void main(String[] args) {
final ThreadCommunication1 t = new ThreadCommunication1();
new Thread(new Runnable() {
public void run() {
for(int i=0;i<50;i++){
t.out("AAA");
}
}
}).start();;
new Thread(new Runnable() {
public void run() {
for(int i=0;i<50;i++){
t.out("BBB");
}
}
}).start();;
}
}
上面的代码可以实现线程之间的互斥,即当A线程执行out方法时B线程被阻塞,但是他没有实现线程之间的通信,即当A线程执行完out方法之后,接下来并不一定是B线程执行,可能A线程继续执行,也就是无法实现A B线程交替输出。所以必须使用线程之间的通信,下面使用了wait notify来实现
public class ThreadCommunication2 {
private boolean toggle = true;
public synchronized void outA() {
while(toggle){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(char c :"AAA".toCharArray()){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print(c);
}
System.out.println();
toggle = true;
notify();
}
public synchronized void outB( ) {
while(!toggle){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(char c :"BBB".toCharArray()){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print(c);
}
System.out.println();
toggle = false;
notify();
}
public static void main(String[] args) {
final ThreadCommunication2 t = new ThreadCommunication2();
new Thread(new Runnable() {
public void run() {
for(int i=0;i<50;i++){
t.outA();
}
}
}).start();;
new Thread(new Runnable() {
public void run() {
for(int i=0;i<50;i++){
t.outB();
}
}
}).start();;
}
}
这里要解释一点为何用while不用if,if只判断一次而while判断多次,用while的原因是为了避免虚假唤醒,即有些线程可能会在不满足条件的时候虚假唤醒,所以用while能够避免这种情况。
这样就可以实现两个线程轮换的运行了。这里要加一点wait notify的解释。
wait notify必须用在synchronized代码块里(或者是方法),不然就会报错,wait也是获得的监视器,所以必须在同步代码块里。wait获得的监视器必须是synchronized同步的对象的监视器,如果捕获的是不同对象的监视器就会报错。所以只能这样:
//这样是可以的,因为synchronized和wait都是捕获的this(也就是当前对象)的监视器
synchronized void method1(){
wait();
...
}
//这样也是可以的,都是捕获的lock的监视器
byte[] lock = new byte[0];//仅用于锁
void method2(){
synchronized(lock){
lock.wait();
...
}
}
但是除了这两种情况交叉起来就会报错
但是仍然不完美,因为notify是唤醒任意一个阻塞的线程,这里我们只有两个线程,所以唤醒的那个阻塞的线程一定是我们期待执行的线程,但是如果我们有3个线程呢?我们怎么指定唤醒哪个呢?所以这个时候就不能用synchronized wait notify这套机制了,原因就是无法指定唤醒哪个线程。
假设有这样的一个需求:有三个线程ABC,A执行后B执行,然后C执行再A执行,这个时候就要用lock,condition类了。lock和之前的lock的用法一样,但是lock可以产生很多condition,调用condition的await和signal方法就相当于是之前的wait notify方法。但是区别的时就是可以区分多个不同的condition以指定哪个condition被唤醒,也就时执行哪个线程。上例子
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadCommunication3 {
private int toggle = 1;
Lock lock = new ReentrantLock();
Condition cA= lock.newCondition();
Condition cB= lock.newCondition();
Condition cC= lock.newCondition();
public void outA() {
lock.lock();
try {
while(toggle != 1){
try {
cA.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(char c :"AAA".toCharArray()){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print(c);
}
System.out.println();
toggle = 2;
cB.signal();//唤醒B
} finally {
lock.unlock();
}
}
public void outB() {
lock.lock();
try {
while(toggle != 2){
try {
cB.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(char c :"BBB".toCharArray()){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print(c);
}
System.out.println();
toggle = 3;
cC.signal();//唤醒C
} finally {
lock.unlock();
}
}
public void outC() {
lock.lock();
try {
while(toggle != 3){
try {
cC.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(char c :"CCC".toCharArray()){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print(c);
}
System.out.println();
toggle = 1;
cA.signal();//唤醒B
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ThreadCommunication3 t = new ThreadCommunication3();
new Thread(new Runnable() {
public void run() {
for(int i=0;i<50;i++){
t.outA();
}
}
}).start();;
new Thread(new Runnable() {
public void run() {
for(int i=0;i<50;i++){
t.outB();
}
}
}).start();
new Thread(new Runnable() {
public void run() {
for(int i=0;i<50;i++){
t.outC();
}
}
}).start();;
}
}
通过condition唤醒指定的线程就可以实现多个线程之间的通信了,并且可以执行唤醒的顺序。