java线程互斥&同步(二)

       这一次,来探讨下线程间同步的问题,网上很多小伙伴讲到同步,自然而然会说synchronized,但是synchronized英文翻译的确是“同步的”,但此同步非彼同步,synchronized关键字其实实现的是对于一个共享资源的加锁操作,确保这个共享资源同一时间内只被一个对象(线程)进行操作,这是我们前一节所说的线程间互斥,所以synchronized并非实现了线程间同步。

 

       那我们说的线程间同步的含义是什么呢?更直观的翻译我觉得应该是线程间的协同工作,比方有两个线程任务A和B,当B必须等待A执行完成后才能执行,那么我们说这两个线程是同步的,与之相对的就是A/B同时运行,互补干涉,也即线程的异步。

 

        理解了上面的概念,那我们更深入一步,知道线程间同步是怎么回事,那么java是提供了哪些办法来实现线程间同步呢?

        我们先来看一下下面这些方法:sleep()、wait()、notify()、notifyAll()

        sleep():sleep()是线程类的方法,sleep() 允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。说白了 ,也就是把机会给其他线程,但是监控状态依然保持。重要的一点就是 当调用sleep()方法是 不会 释放对象锁的。

        wait():导致线程阻塞,并且该对象上的锁被释放。

        notify():释放对象的wait()方法而阻塞线程(但是也要当得到锁后才可以运行)但是这个释放是随机的,也就是不一定要释放那个线程。(因为调用同一资源的可能不是一个线程或者说是有多个阻塞的线程在等待,但是如果加了synchronized也只有一个线程,也有其他的线程在等待中,也就是阻塞)我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。

        notifyall():将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。

        概念性的东西,就不深入解释了,不明白的同学可以多查看相关资料。

        上面几个方法,跟我们所讲的线程同步关系最密切的是wait、notify、notifyall这三个方法,有了它们就可以很方便地控制线程的协同工作了。

 

        注意:Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内,否则会报java.lang.IllegalMonitorStateException错误。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

         

        下面我们来看一个经典的多线程同步问题,三线程打印ABC的问题:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。

 

package Tread_TB;

public class ThreadPrint implements Runnable{

	private String name;
	private Object prev;
	private Object thisone;
	
	public ThreadPrint(String name,Object prev,Object thisone) {
		// TODO Auto-generated method stub
		this.name = name;
		this.prev = prev;
		this.thisone = thisone;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		int count = 10;
		while(count > 0){
			synchronized (prev) {
				synchronized (thisone) {
					System.out.print(name);
					count--;
					thisone.notify();
				}
				try {
					prev.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public static void main(String[] args) {
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();
		ThreadPrint A = new ThreadPrint("A",c,a);
		ThreadPrint B = new ThreadPrint("B",a,b);
		ThreadPrint C = new ThreadPrint("C",b,c);
		
		new Thread(A).start();
		new Thread(B).start();
		new Thread(C).start();
	}
		
}

 

运行结果:ABCABCABCABCABCABCABCABCABCABC

达到了我们想要的效果,但是细心的同学可能会发现,多执行几次,有时候会出现ACBACBACB..这样的结果,也即C和B顺序颠倒,那我们仔细分析下代码,其实不难发现,当A任务在执行过程中,B和C任务都启动了,当A执行到thisone.notify();之前,B一直在synchronized (prev) {处等待a对象的锁,而此时C却在synchronized (prev) {处优先得到了b对象的锁,所以导致C先于B开始运行,之后释放c与b的对象锁,此时B拿到锁开始执行,所以就出现了ACBACBACB的结果,那么可以通过人为错开任务运行时间来防止C抢在B之前运行,代码如下:

package Tread_TB;

public class ThreadPrint implements Runnable{

	private String name;
	private Object prev;
	private Object thisone;
	
	public ThreadPrint(String name,Object prev,Object thisone) {
		// TODO Auto-generated method stub
		this.name = name;
		this.prev = prev;
		this.thisone = thisone;
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		int count = 10;
		while(count > 0){
			synchronized (prev) {
				synchronized (thisone) {
					System.out.print(name);
					count--;
					thisone.notify();
				}
				try {
					prev.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public static void main(String[] args) {
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();
		ThreadPrint A = new ThreadPrint("A",c,a);
		ThreadPrint B = new ThreadPrint("B",a,b);
		ThreadPrint C = new ThreadPrint("C",b,c);
		
		try {
			new Thread(A).start();
			Thread.sleep(1000);
			new Thread(B).start();
			Thread.sleep(1000);
			new Thread(C).start();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
		
}

 

猜你喜欢

转载自chaijuntao.iteye.com/blog/2218599