java并发编程-2

阅读更多

以前讨论了线程之间的互斥,这里讨论线程之间的通信。线程之间的通信即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唤醒指定的线程就可以实现多个线程之间的通信了,并且可以执行唤醒的顺序。

 

 

 

 

你可能感兴趣的:(lock,condition,并发,java,多线程)