java多线程学习(三)

来源:互联网 发布:windows更新有必要吗 编辑:程序博客网 时间:2024/06/09 18:32

一 线程之间的协作

1 如何协作

(1)多线程的程序在工作的时候,使用Synchronized、Lock等可以满足线程之间访问共享资源的互斥操作。但很多时候,线程之间不只有互斥的关系,还有协作的关系。比如,有三个线程,t1,,t2,t3,t2需要在t1完成之后才能开始操作,t3需要在t2完成之后才能开始操作。这就是经典的“生产者-消费者”模式。

实现这种模式的方法,可以让t1开始运行,然后t2不断的循环判断t1是否完成,同理,t3循环判断t2是否完成。这种情况称为“忙等待”,即使线程只是在不断判断其他线程是否结束,没有干具体的工作,但线程还是消耗了CPU的时间片。因此,“忙等待”虽然是一种解决办法,但对整个程序的执行效率有很大影响。

(2)既然如此,有人就会想到使用sleep()方法,每隔一定时间判断一次,判断完后进入休眠状态,不会占用太多CPU的时间片。但这样做的问题,一是sleep的时间不好把握,因为程序员并不知道,上一个线程具体需要多久完成;二是线程由于sleep()方法进入阻塞时,不会释放锁,因此,其他线程也就无法使用可共享的资源(即使当前线程在sleep)。所以,使用sleep的方案,方法依然不好。

(3)java中为了完成线程中类似的配合,使用的是wait()方法和notify()或notifyAll()方法。需要注意的是,这三个方法不是Thread类的方法,而是Object类的方法。

wait()方法和notify()或notifyAll()方法只能在同步控制方法或者同步控制块里使用,因为线程在调用这些方法的时候,必须拥有“锁”。


2 wait()方法

wait()方法会使调用该方法的线程进入到阻塞状态,与sleep不同之处在于,使用wait()方法是线程进入阻塞状态时,锁也会被释放,这样其他线程就可以获得锁并访问共享资源了,由于在线程被notify()方法唤醒时,线程会再次尝试获得锁而不是直接继续执行,所以这种模式对共享资源来说是安全的。


由于wait()是Object类的函数,所以在一个任务中直接使用wait()相当于调用了this.wait(),即当前对象的wait()函数。同理,程序员也可以自己定义一个对象object,然后调用object.wait(),但需要注意的是,无论是使用哪种方式,都要保证,调用wait()方法时,必须获得其对应对象的锁:


Synchronized(this){while(!Thread.interrupted()){wait();}}


Object ob = new Object();Synchronized(ob){while(!Thread.interrupted()){ob.wait();}}



3 notify()和notifyAll()

(1)wait()方法使线程进入阻塞,对应的,notify()方法可以将阻塞的线程“唤醒”。和wait()方法相同,调用notify()方法之前,也必须获得其对应对象的锁:


Synchronized(this){while(!Thread.interrupted()){notifyAll();}}


Object ob = new Object();Synchronized(ob){while(!Thread.interrupted()){ob.notifyAll();}}


这里需要注意的是,notifyAll()并没有将所有处于wait状态的线程唤醒,二是仅仅当它在某个特定的锁中被调用时,等待这个锁的线程会被唤醒。简单来说,通过调用ob1.wait()方法进入阻塞的线程,只有在ob1.notifyAll()方法执行时会被唤醒,在ob2.notifyAll()方法执行时不会被唤醒。

(2)这里需要介绍一下notify()和notifyAll()的区别,notifyAll()方法会将所以等待对应锁的线程都唤醒,然后这些被唤醒的线程再去竞争一个锁;notify()方法会在等待对应锁的线程中只挑选一个线程唤醒。


4 使用显示的Lock和Condition对象

synchronized配合wait()、notifyAll()可以实现线程之间的配合;使用Lock和Condition可以达到同样的目的。例如:


class Car{private Lock lock = new ReentrantLock();Condition condition = lock.newCondition();public void method1(){lock.lock();try{ //使用Lock时惯用的try-finally结构condition.await();}finnaly{lock.unlock();}}public void method2(){lock.lock();try{ //使用Lock时惯用的try-finally结构condition.signalAll();}finnaly{lock.unlock();}}}



上面的代码中,Lock上锁对应synchronized,condition.await()对应object.wait(),condition.signalAll()对应object.notifyAll();

根据《Java编程思想》一书中的解释,使用在比较复杂的多线程问题中,才会使用Lock、Condition代理synchronized、wait()、notifyAll()。


5 生产者-消费者模式(单个产品)

下面是一个单个商品的生产者消费者例子,Product类表示商品,full表示商品是否存在(单个商品的例子,所以使用boolean值表示是否需要生产),true表示商品存在,等待消费者消费,false表示商品不存在,等待生产者生产。


public class Product {private boolean full;public Product(){this.full = false;}public boolean get(){return full;}public void set(boolean value){full = value;}public String toString(){return "product "+full;}}public class Producer implements Runnable {private Object pLock;private Object cLock;private Product product;public Producer(Object pLock,Object cLock,Product product) {this.pLock = pLock;this.cLock = cLock;this.product = product;}@Overridepublic void run() {try{while(!Thread.interrupted()){synchronized (pLock) {while(product.get()){pLock.wait();}}synchronized (cLock) {System.out.println("Producer");TimeUnit.SECONDS.sleep(1);product.set(true);cLock.notifyAll();}}}catch(Exception e){System.out.println("Producer Exception");}}}public class Cosumer implements Runnable {private Object pLock;private Object cLock;private Product product;public Cosumer(Object pLock,Object cLock,Product product) {this.pLock = pLock;this.cLock = cLock;this.product = product;}@Overridepublic void run() {try{while(!Thread.interrupted()){synchronized (cLock) {while(!product.get()){cLock.wait();}}synchronized (pLock) {System.out.println("Cosumer");TimeUnit.SECONDS.sleep(1);product.set(false);pLock.notifyAll();}}}catch(Exception e){System.out.println("Cosumer Exception");}}}public class cspdTest {public static void main(String[] args) throws InterruptedException{Product p = new Product();Object pLock = new Object();Object cLock = new Object();ExecutorService exec = Executors.newCachedThreadPool();exec.execute(new Producer(pLock, cLock, p));exec.execute(new Cosumer(pLock, cLock, p));TimeUnit.SECONDS.sleep(10);exec.shutdownNow();}}


6 生产者-消费者模式(多产品)

下面是《Java编程思想》中的例子。表示一个吐司面包的生产过程,面包首先被烤出来(DRY),然后涂黄油(BUTTERED),然后涂酱(JAMMED),然后被吃。整个过程中,使用同步队列(BlockingQueue)来解决不同线程之间的配合。


public class Toast {public enum Status{DRY,BUTTERED,JAMMED}private final int id;private Status status = Status.DRY;public Toast(int id){this.id = id;}public void buttered(){status = Status.BUTTERED;}public void jammed(){status = Status.JAMMED;}public int getId(){return id;}public Status getStatus(){return status;}public String toString(){return "Toast "+id+": "+status;}}public class Toaster implements Runnable {private int count =0;private LinkedBlockingQueue<Toast> toastQueue;public Toaster(LinkedBlockingQueue<Toast> toastQueue){this.toastQueue = toastQueue;}@Overridepublic void run() {try{while(!Thread.interrupted()){Toast t = new Toast(++count);System.out.println("Dry "+t);toastQueue.put(t);TimeUnit.SECONDS.sleep(1);}}catch(InterruptedException e){System.out.println("Toaster Interrupted");}catch(Exception e){System.out.println("Toast Exception");}System.out.println("Toaster exit");}}public class Butter implements  Runnable{private LinkedBlockingQueue<Toast> toastQueue;private LinkedBlockingQueue<Toast> butteredQueue;public Butter(LinkedBlockingQueue<Toast> toastQueue, LinkedBlockingQueue<Toast> butteredQueue){this.toastQueue = toastQueue;this.butteredQueue = butteredQueue;}public void run() {try{while(!Thread.interrupted()){Toast t = toastQueue.take();System.out.println("Butter "+t);butteredQueue.put(t);TimeUnit.SECONDS.sleep(1);}}catch(InterruptedException e){System.out.println("Butter Interrupted");}catch(Exception e){System.out.println("Butter Exception");}System.out.println("Butter exit");}}public class Jammed implements Runnable {private LinkedBlockingQueue<Toast> butteredQueue;private LinkedBlockingQueue<Toast> finishQueue;public Jammed(LinkedBlockingQueue<Toast> butteredQueue, LinkedBlockingQueue<Toast> finishQueue){this.butteredQueue = butteredQueue;this.finishQueue = finishQueue;}public void run() {try{while(!Thread.interrupted()){Toast t = butteredQueue.take();System.out.println("Jammed "+t);finishQueue.put(t);TimeUnit.SECONDS.sleep(1);}}catch(InterruptedException e){System.out.println("Jammed Interrupted");}catch(Exception e){System.out.println("Jammed Exception");}System.out.println("Jammed exit");}}public class Eater implements Runnable {private LinkedBlockingQueue<Toast> finishQueue;public Eater( LinkedBlockingQueue<Toast> finishQueue){this.finishQueue = finishQueue;}public void run() {try{while(!Thread.interrupted()){Toast t = finishQueue.take();System.out.println("Eater "+t);TimeUnit.SECONDS.sleep(1);}}catch(InterruptedException e){System.out.println("Eater Interrupted");}catch(Exception e){System.out.println("Eater Exception");}System.out.println("Eater exit");}}public class ToastTest {public static void main(String[] args) throws InterruptedException{ExecutorService exec = Executors.newCachedThreadPool();LinkedBlockingQueue<Toast> toastQueue = new LinkedBlockingQueue<Toast>();LinkedBlockingQueue<Toast> butteredQueue = new LinkedBlockingQueue<Toast>();LinkedBlockingQueue<Toast> finishQueue = new LinkedBlockingQueue<Toast>();exec.execute(new Toaster(toastQueue));exec.execute(new Butter(toastQueue, butteredQueue));exec.execute(new Jammed(butteredQueue, finishQueue));exec.execute(new Eater(finishQueue));TimeUnit.SECONDS.sleep(10);exec.shutdownNow();}}



整个过程中,实现Runnable接口的类的run()方法中都没有同步控制,这是由于他们只和BlockingQueue通信,同步问题已经被BlockingQueue解决,它可以保证同一时刻是能有一个线程访问同一资源。这样,类与类之间的耦合也就消失了。


7 死锁产生的条件

具体例子参考哲学集吃饭的问题。

(1)互斥,至少有一个资源不能被多个线程同时访问。

(2)至少有一个任务持有一个资源并且在等待获取当前被其他线程持有的资源。

(3)资源不可抢占。

(4)循环等待。一个任务在等待另一个任务的资源,另一个任务又在等待其他任务的资源,一直循环下去,有一个任务在等待第一个任务的资源。


二 同步的性能调优

Java中实现同步互斥的方法有synchronized、Lock和原子类。

1 Lock和Synchronized

(1)Lock的效率要比Synchronized高;而且Synchronized的开销在线程数量变化时变化的范围大,Lock的开销比较一致。

(2)Synchronized代码可读性好。而且,实际线程中,被互斥的代码执行所花费的时间所占百分比要比实现互斥锁所占的时间多很多,这也湮灭了提高互斥速度带来的效率提升。

(3)因此,尽量使用Synchronize的,只有在性能调优时使用Lock。


2 Synchronized和原子类

原子类只有在非常简单的情况下才有效,这种情况下,需要共享的资源仅仅是可以使用原子类代表的一个参数。

因此,尽量使用传统的互斥方式,在需要调优时再使用原子类。

0 0
原创粉丝点击