线程应用:(七)Lock & Condition同步通信

一、线程锁:Lock 代替synchronized

  Lock的作用类似于传统线程模型中的synchronized,更体现面向对象的思想,两个线程执行的代码片段要实现同步互斥的效果,必须要用同一个Lock对象。

1)ReentrantLock

//用Lock替换synchronized互斥
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest1 {
    public static void main(String[] args) {
        new ThreadTest1().init();
    }

    //起了两个线程,一个一直输出"AAAAAA",一个一直输出"BBBBBB",但调用方法的对象是同一个
    public void init(){
        final Outputer outputer = new Outputer();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    outputer.output("AAAAAA");
                }
            }
        }).start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    outputer.output("BBBBBB");
                }
            }
        }).start();
    }
}

class Outputer {
    Lock lock = new ReentrantLock();    //这里两个线程调用方法的是同一个对象,所以是用的同一把锁
    public void output(String name){
        //synchronized (this) {
        lock.lock();    
        try{
            int len = name.length();
            for(int i=0;i<len;i++){
                System.out.print(name.charAt(i));
            }
            System.out.println("");
        }finally{
            lock.unlock();    //无论如何都要释放锁
        }
        //}
    }
}

  锁中的代码出现异常可能会出现死锁,最好finally释放锁。

2)读写锁 ReentrantReadWriteLock

  分为读锁和写锁,使用读写锁可以提高效率,还能实现互斥。ReentrantLock是互斥锁,一个线程获得后,别的线程无论读写都不允许。

  多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。

规则:
读中可以有读,
读中不能有写,
写中不能有读,写中不能有写。

  只是读的时候不用互斥,用读锁即可,不用synchronized。

/**
 * 起3个线程不停写,3个线程不停读
 */
public class ReadWriteLockTest {
    public static void main(String[] args) {
        final Queue q = new Queue();
        for(int i=0;i<3;i++){
            new Thread(){
                @Override
                public void run() {
                    while(true){
                        q.get();
                    }
                }
            }.start();
            
            new Thread(){
                @Override
                public void run() {
                    while(true){
                        q.put(new Random().nextInt(10000));;
                    }
                }
            }.start();
        }
    }
}

/**
 *读时可以有读,读时不能有写
 *写时不能有读,也不能有写。
 */
class Queue {
    private Object data = null;  //共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据
    private ReadWriteLock rw1 = new ReentrantReadWriteLock();    //创建一个读写锁
    
    public void get() {
        rw1.readLock().lock();  //从读写锁中获得读锁
        try {
            System.out.println(Thread.currentThread().getName() + "开始读数据");
            Thread.sleep((long)(Math.random()*1000));
            System.out.println(Thread.currentThread().getName() + "已读完数据" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rw1.readLock().unlock();  //释放读锁
        }
    }
    
    public void put(Object data) {
        rw1.writeLock().lock();  //获得写锁
        try {
            System.out.println(Thread.currentThread().getName() + "开始写数据");
            Thread.sleep((long)(Math.random()*1000));
            this.data = data;
            System.out.println(Thread.currentThread().getName() + "已写数据" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rw1.writeLock().unlock();  //释放写锁
        }
    }
}

  利用读写锁来模拟缓存案例。(结合到单例模式下)

/**
 * 模拟缓存
 * 读和写间、写和写间互斥,又可以并发的读,性能高
 */
public class CacheDemo {
    private Map<String, Object> cache = new HashMap<String, Object>();
    private ReadWriteLock rw1 = new ReentrantReadWriteLock();    //读写锁
    
    public Object getData(String key){
        rw1.readLock().lock();    //读1,多个读时应该要可以并发,不会造成数据的破坏
        Object value = null;
        try {
            value = cache.get(key);
            if(value == null){    //如果缓存中没数据,要重新赋值,要先写锁
                rw1.readLock().unlock(); //先释放读1
                rw1.writeLock().lock();    //写2,如果有多个线程到这一步,也只有一个能获得写锁
                try{
                    if(value==null){ //赋值前再判断一次
                        value = "aaa";  //模拟取数据库取数据
                    }
                } finally {
                    rw1.writeLock().unlock();  //写2
                }
                rw1.readLock().lock();  //读3
            }
        } finally {
            rw1.readLock().unlock();  //读3
        }
        return value;
    }
}

二、Condition代替wait/notify实现同步通信

  waitnotify必须和synchronized一起用,并且synchronized和wait、notify要用同一个锁对象。Condition可以用来代替wait、notify实现同步通信,Condition是在Lock对象的基础上使用的。

  使用Lock、Condition代替wait、notify、synchronized,案例代码如下。

public class ConditionTest {
    public static void main(String[] args) throws InterruptedException {
        final Business business = new Business();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=1;i<=50;i++){        //循环50轮
                    try {
                        business.forSub(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        
        for(int i=1;i<=50;i++){        //循环50轮
            business.forMain(i);
        }
    }
}

class Business {
    Lock lock = new ReentrantLock();    //用Lock代替synchronized
    private boolean subGo = true;
    Condition condition = lock.newCondition();    //Condition基于锁之上
    
    public void forSub(int i) throws InterruptedException{
        lock.lock();
        try{
            while(!subGo){    //防止虚假唤醒
                //this.wait();
                condition.await();     //别写成condition.wait();,是有区别的
            }
            for(int j=1;j<=10;j++){
                System.out.println("subThread of, 第"+i+"轮, 序列为:"+j);
            }
            subGo = false;
            condition.signal();    //this.notify();
        }finally{
            lock.unlock();
        }
    }
    
    public void forMain(int i) throws InterruptedException{
        lock.lock();
        try{
            while(subGo){
                condition.await();    //this.wait();
            }
            for(int j=1;j<=100;j++){
                System.out.println("mainThread of, 第"+i+"轮, 序列为:"+j);
            }
            subGo = true;
            condition.signal();    //this.notify();
        }finally{
            lock.unlock();
        }
    }
}

  一个锁内部可以有多个Condition,可实现多路等待和唤醒。

  只用一个Condition的不足:假如有5个线程用来存馒头,1个线程用来取馒头,当笼屉馒头满的时候,5个存的线程等待,当1个取的线程取了一个馒头后,唤醒了5个存线程中的一个继续存,存完后,本来应该要唤醒取的线程,但如果这里只有1个Condition,就不能进行区分,可能会唤醒另外4个存线程中的一个。

  当有三个线程要按顺序交替进行,代码如下。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreeConditionTest {
    public static void main(String[] args) throws InterruptedException {
        final ConditionBusiness business = new ConditionBusiness();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=1;i<=50;i++){        //循环50轮
                    try {
                        business.sub2(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=1;i<=50;i++){        //循环50轮
                    try {
                        business.sub3(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        
        for(int i=1;i<=50;i++){        //循环50轮
            business.forMain(i);
        }
    }
}

//用于三个线程的同步通信,主线程-sub2-sub3的顺序交替进行
class ConditionBusiness {
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    private int flag = 1;    //设定1先走
    
    public void forMain(int i) throws InterruptedException{
        lock.lock();
        try{
            while(flag != 1){
                condition1.await();
            }
            for(int j=1;j<=10;j++){
                System.out.println("main, 第"+i+"轮, 序列为:"+j);
            }
            flag = 2;
            condition2.signal();    //线程1走完唤醒线程2
        }finally{
            lock.unlock();
        }
    }
    
    public void sub2(int i) throws InterruptedException{
        lock.lock();
        try{
            while(flag != 2){
                condition2.await();
            }
            for(int j=1;j<=10;j++){
                System.out.println("sub2, 第"+i+"轮, 序列为:"+j);
            }
            flag = 3;
            condition3.signal();    //线程2走完唤醒线程3
        }finally{
            lock.unlock();
        }
    }
    
    public void sub3(int i) throws InterruptedException{
        lock.lock();
        try{
            while(flag != 3){
                condition3.await();
            }
            for(int j=1;j<=10;j++){
                System.out.println("sub3, 第"+i+"轮, 序列为:"+j);
            }
            flag = 1;
            condition1.signal();    //线程3走完唤醒线程1
        }finally{
            lock.unlock();
        }
    }
}

 

posted @ 2018-08-13 20:51  湮天霸神666  阅读(147)  评论(0编辑  收藏  举报