当使用多个线程来同时运行多个任务时,有时候需要对某项共享资源进行操作,怎样使得一个任务不会干涉另外一个任务呢?这时候就需要
使用锁来使得资源的访问变得互斥,也就是同时只能有一个任务对共享资源进程访问。
Java中能够通过Object的wait()和notify()方法来安全的访问共享资源。Java SE5的并发库还提供了await()和signal()方法的Condition对象来实
现资源的安全访问。
下面我们来了解一下一些常用的线程同步相关方法
notify()和notifyAll()的不同,notify():在众多等待同一个锁的任务中,只有一个被唤醒,所以如果使用notify()时,就必须保证被唤醒的是恰当
的任务。notify一般用于具有唤醒同步块的对象。notifyAll()是唤醒所有正在等待同一个锁的任务。
wait()方法的作用是将调用该方法的线程挂起,和sleep(),yield()这些方法不同的是,在调用wait方法后,该对象上的锁会被释放掉,也就是
在此声明:我已经做完所有能做的事,因此我在这里等待,但是我希望其他的synchronized操作在条件适合的情况下能够执行。
wait,notify,notifyAll,这些方法的是基类Object的一部分,而不是属于Thread的一部分,我们可能会感到奇怪,为什么针对线程的功能却
作为基类的一部分来实现,这是因为这些操作的锁也是所有对象的一部分,所以你可以在任何的同步控制方法或者同步控制块中调用wait()
notify(),notifyAll(),如果在非同步控制方法中调用这些方法时,程序能够编译通过,但运行时会抛出IllegalMonitorStateException异常,也
就是在调用这些方法的任务在调用这些方法前必须拥有对象锁。
下面我们通过一个例子来熟悉这些方法的使用。
现在有一个场景是:一辆车,需要多次进行涂蜡和抛光,在涂蜡之前必须进行抛光,在抛光之前必须进行涂蜡,刚开始肯定是从涂蜡开始
然后进行抛光,然后再涂蜡,以此循环。下面给出这个场景的代码:
public class MyTest{ public static volatile boolean isStop = false; public static void main(String[] args) throws InterruptedException{ Car car = new Car(); ExecutorService exec = Executors.newCachedThreadPool(); exec.submit(new Wax(car)); exec.submit(new Buff(car)); TimeUnit.SECONDS.sleep(5); isStop = true; exec.shutdownNow(); } } class Car{ private boolean waxOn= false;//涂蜡的标志位,ture表示正在涂蜡 /** * 涂蜡*/ public synchronized void wax(){ waxOn = true; notifyAll(); } /** * 抛光*/ public synchronized void buff(){ waxOn = false; notifyAll(); } /** * 等待涂蜡*/ public synchronized void waitForWax()throws InterruptedException{ while(waxOn == false){ wait(); } } /** * 等待抛光*/ public synchronized void waitForBuff()throws InterruptedException{ while(waxOn == true){ wait(); } } } class Wax implements Runnable{ Car car; public Wax(Car car){ this.car = car; } @Override public void run() { while(!MyTest.isStop){ try { car.waitForBuff(); TimeUnit.MILLISECONDS.sleep(200);//涂蜡所需时间 System.out.println("正在涂蜡"); car.wax(); } catch (InterruptedException e) { System.out.println("涂蜡完毕"); } } System.out.println("结束涂蜡"); } } class Buff implements Runnable{ Car car; public Buff(Car car){ this.car = car; } @Override public void run() { while(!MyTest.isStop){ try { car.waitForWax(); TimeUnit.MILLISECONDS.sleep(200);//抛光所需时间 System.out.println("正在抛光"); car.buff(); } catch (InterruptedException e) { System.out.println("抛光完毕"); } } System.out.println("结束抛光"); } }
上面的代码我们就实现了这个场景,最先开始提交涂蜡的任务,然后提交抛光的任务,确保任务是从涂蜡开始的。waxOn这个变量就是涂蜡
和抛光的标志为false时表示正在进行抛光,等待涂蜡,为true时表示正在进行涂蜡,等待抛光。涂蜡时,先调用waitForBuff()方法将抛光的线
程挂起,然后调用wax()方法进行涂蜡,在这个方法中将waxOn标志位设为true,涂蜡完毕后使用notifyAll()唤醒正在等待这辆车的抛光的任务。
在抛光时,先调用waitForWax()方法将涂蜡的线程挂起,然后调用buff()方法进行抛光,并将waxOn标志位设为false,抛光完毕后使用notifyAll()
方法唤醒正在等待这辆车的涂蜡任务。
让这个过程持续5秒,然后调用shutdownNow(),这个方法会调用由他控制的线程的interrupt()方法。至于为什么要使用一个while循环包围wait
方法,这是因为,可能由于其他原因,还有其他任务也在等待这辆车的同一个锁,而这个锁被唤醒时,可能会导致其他任务获取这个锁,从而
导致这个任务继续被挂起。