转载和引用,请注明原文出处! Fork me on GitHub
结局很美妙的事,开头并非如此!

多线程系列三:Lock和Condition

有了synchronized为什么还要Lock

因为Lock和synchronized比较有如下优点,这些特点是synchronized没有的

1、 尝试非阻塞地获取锁

2、 获取锁的过程可以被中断

3、 超时获取锁  多长时间拿不到锁就放弃

Lock的使用范式:

 1 package com.lgs;
 2 
 3 import java.util.concurrent.locks.Lock;
 4 import java.util.concurrent.locks.ReentrantLock;
 5 
 6 // 显示锁Lock的使用范式
 7 public class LockTemplete {
 8 
 9     public static void main(String[] args) {
10         // 创建一把锁 ReentrantLock是Lock的实现类,表示可重入的锁
11         Lock lock = new ReentrantLock();
12         // 获取锁
13         lock.lock();
14         try {
15             // 业务逻辑处理
16         }finally {
17             // 释放锁
18             lock.unlock();
19         }
20     }
21 }

这里解释一下什么叫可重入锁:已经获得锁的线程再次进入加了锁的代码依然可以获取锁,比如递归同步代码块就要求锁是可重入的

Lock的常用方法

Lock() 获取锁

tryLock尝试非阻塞地获取锁

lockInterruptibly:获取锁的过程可以被中断

tryLock(long time, TimeUnit unit) 超时获取锁

unlock()释放锁

锁的可重入

一个线程获得了锁进入了同步代码块遇到了锁仍然可以获得锁进入同步代码块

递归的时候发生锁的重入,没有锁的可重入,就会死锁

公平锁和非公平锁

公平锁,先对锁发出获取请求的一定先被满足。公平锁的效率比非公平锁效率要低。

为什么非公平锁的性能要高:因为非公平锁是可以插队的,如线程C被唤醒变为可执行去获取锁的过程中,线程A插队进来直接获取锁执行自己的业务,线程A执行完以后,线程C刚好唤醒完直接就获取锁运行了,这样在线程C唤醒的过程中线程A就执行完了效率更高

读写锁ReentrantReadWriteLock

允许多个读线程同时进行,但是只允许一个写线程(不允许其他读线程是为了防止脏读),支持读多写少场景,性能会有提升。

ReentrantReadWriteLock的使用范式:

 1 package com.lgs;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 import java.util.concurrent.locks.Lock;
 6 import java.util.concurrent.locks.ReentrantReadWriteLock;
 7 
 8 // 读写锁使用范式
 9 public class ReadWriteLockTemplete {
10 
11     // 对map进行读写操作
12     static final Map<String,String> map = new HashMap<>();
13     
14     // 定义一个读写锁
15     static ReentrantReadWriteLock rwl = new  ReentrantReadWriteLock();
16     
17     // 拿到读锁
18     static Lock r = rwl.readLock();
19     
20     // 拿到写锁
21     static Lock w = rwl.writeLock();
22     
23     // 进行写操作
24     public void put() {
25         // 获取写锁
26         w.lock();
27         try {
28             // 业务逻辑处理
29         }finally {
30             // 释放写锁
31             w.unlock();
32         }
33     }
34     
35     // 进行写操作
36     public void read() {
37         // 获取读锁
38         r.lock();
39         try {
40             // 业务逻辑处理
41         }finally {
42             // 释放读锁
43             r.unlock();
44         }
45     }
46 }

ReentrantReadWriteLocksynchronized性能比较

案例:两种锁实现对库存的读写性能比较

定义一个货物库存的实体类:

 1 package com.lgs.readwritelock.and.sync.perform.compare.model;
 2 
 3 
 4 // 定义一个货物库存的实体类
 5 public class GoodsVo {
 6 
 7     private final String id;
 8     private int totalSaleNumber;//总销售数
 9     private int depotNumber;//当前库存数
10 
11     public GoodsVo(String id, int totalSaleNumber, int depotNumber) {
12         this.id = id;
13         this.totalSaleNumber = totalSaleNumber;
14         this.depotNumber = depotNumber;
15     }
16 
17     public int getTotalSaleNumber() {
18         return totalSaleNumber;
19     }
20 
21     public int getDepotNumber() {
22         return depotNumber;
23     }
24 
25     // 设置总销售数和当前库存数
26     public void setGoodsVoNumber(int changeNumber){
27         this.totalSaleNumber += changeNumber;
28         this.depotNumber -= changeNumber;
29     }
30 }

定义一个货物库存的接口:

 1 package com.lgs.readwritelock.and.sync.perform.compare.dao;
 2 
 3 import com.lgs.readwritelock.and.sync.perform.compare.model.GoodsVo;
 4 
 5 // 定义一个货物库存的接口
 6 public interface IGoodsNum {
 7 
 8     // 获取货物数
 9     public GoodsVo getGoodsNumber();
10     // 设置货物数
11     public void setGoodsNumber(int changeNumber);
12 }

synchronized同步方式实现:

 1 package com.lgs.readwritelock.and.sync.perform.compare.impl;
 2 
 3 import com.lgs.readwritelock.and.sync.perform.compare.dao.IGoodsNum;
 4 import com.lgs.readwritelock.and.sync.perform.compare.model.GoodsVo;
 5 
 6 //synchronized同步方式实现
 7 public class NumSynImpl implements IGoodsNum {
 8 
 9     private GoodsVo goods;
10 
11     public NumSynImpl(GoodsVo goods) {
12         this.goods = goods;
13     }
14 
15 
16     // sync方式阻塞获取货物数
17     @Override
18     public synchronized GoodsVo getGoodsNumber() {
19         try {
20             Thread.sleep(5);
21         } catch (InterruptedException e) {
22             e.printStackTrace();
23         }
24         return this.goods;
25     }
26 
27     // sync方式阻塞设置销售货物数
28     @Override
29     public synchronized void setGoodsNumber(int changeNumber) {
30 
31         try {
32             Thread.sleep(50);
33         } catch (InterruptedException e) {
34             e.printStackTrace();
35         }
36 
37         this.goods.setGoodsVoNumber(changeNumber);
38 
39     }
40 }

ReentrantReadWriteLock可重如读写锁方式实现,可以多个线程读一个线程写:

 1 package com.lgs.readwritelock.and.sync.perform.compare.impl;
 2 
 3 import java.util.concurrent.locks.Lock;
 4 import java.util.concurrent.locks.ReentrantReadWriteLock;
 5 
 6 import com.lgs.readwritelock.and.sync.perform.compare.dao.IGoodsNum;
 7 import com.lgs.readwritelock.and.sync.perform.compare.model.GoodsVo;
 8 
 9 // ReentrantReadWriteLock可重如读写锁方式实现,可以多个线程读一个线程写 
10 // 总体性能比synchronized更好
11 public class RwNumImpl implements IGoodsNum {
12 
13     private GoodsVo goods;
14 
15     private final  ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
16     private final  Lock r = lock.readLock();
17     private final  Lock w = lock.writeLock();
18 
19     public RwNumImpl(GoodsVo goods) {
20         this.goods = goods;
21     }
22 
23 
24     @Override
25     public GoodsVo getGoodsNumber() {
26         r.lock();
27         try{
28             try {
29                 Thread.sleep(5);
30             } catch (InterruptedException e) {
31                 e.printStackTrace();
32             }
33             return this.goods;
34         }finally{
35             r.unlock();
36         }
37     }
38 
39     @Override
40     public void setGoodsNumber(int changeNumber) {
41         w.lock();
42         try{
43             try {
44                 Thread.sleep(50);
45             } catch (InterruptedException e) {
46                 e.printStackTrace();
47             }
48 
49             this.goods.setGoodsVoNumber(changeNumber);
50         }finally{
51             w.unlock();
52         }
53     }
54 }

运行测试方法比较性能,3个线程写 30个线程读

  1 package com.lgs.readwritelock.and.sync.perform.compare.test;
  2 
  3 import java.util.Random;
  4 import java.util.concurrent.CountDownLatch;
  5 
  6 import com.lgs.readwritelock.and.sync.perform.compare.dao.IGoodsNum;
  7 import com.lgs.readwritelock.and.sync.perform.compare.impl.NumSynImpl;
  8 import com.lgs.readwritelock.and.sync.perform.compare.impl.RwNumImpl;
  9 import com.lgs.readwritelock.and.sync.perform.compare.model.GoodsVo;
 10 
 11 //ReentrantReadWriteLock和synchronized性能比较
 12 // 3个线程写 30个线程读
 13 // 结论:ReentrantReadWriteLock性能比synchronized快8倍
 14 public class Test {
 15 
 16     static final int threadBaseCount = 3;
 17     static final int threadRatio = 10;
 18     static CountDownLatch countDownLatch= new CountDownLatch(1);
 19 
 20     //模拟实际的数据库读操作
 21     private static class ReadThread implements Runnable{
 22 
 23         private IGoodsNum goodsNum;
 24 
 25         public ReadThread(IGoodsNum goodsNum) {
 26             this.goodsNum = goodsNum;
 27         }
 28 
 29         @Override
 30         public void run() {
 31             try {
 32                 countDownLatch.await();
 33             } catch (InterruptedException e) {
 34                 e.printStackTrace();
 35             }
 36             long start = System.currentTimeMillis();
 37             for(int i=0;i<100;i++){
 38                 goodsNum.getGoodsNumber();
 39             }
 40             long duration = System.currentTimeMillis()-start;
 41             System.out.println(Thread.currentThread().getName()+"读取库存数据耗时:"+duration+"ms");
 42 
 43         }
 44     }
 45 
 46 
 47     //模拟实际的数据库写操作
 48     private static class WriteThread implements Runnable{
 49 
 50         private IGoodsNum goodsNum;
 51 
 52         public WriteThread(IGoodsNum goodsNum) {
 53             this.goodsNum = goodsNum;
 54         }
 55 
 56         @Override
 57         public void run() {
 58             try {
 59                 countDownLatch.await();
 60             } catch (InterruptedException e) {
 61                 e.printStackTrace();
 62             }
 63             long start = System.currentTimeMillis();
 64             Random r = new Random();
 65             for(int i=0;i<10;i++){
 66                 try {
 67                     Thread.sleep(50);
 68                 } catch (InterruptedException e) {
 69                     e.printStackTrace();
 70                 }
 71                 goodsNum.setGoodsNumber(r.nextInt(10));
 72             }
 73             long duration = System.currentTimeMillis()-start;
 74             System.out.println(Thread.currentThread().getName()+"写库存数据耗时:"+duration+"ms");
 75 
 76         }
 77     }
 78 
 79     public static void main(String[] args) throws InterruptedException {
 80         GoodsVo goodsVo =
 81                 new GoodsVo("goods001",100000,10000);
 82         // synchronized方式
 83         // IGoodsNum goodsNum = new NumSynImpl(goodsVo);
 84         
 85         // ReentrantReadWriteLock方式
 86          IGoodsNum goodsNum = new RwNumImpl(goodsVo);
 87         
 88         //30个线程读
 89         for(int i = 0;i<threadBaseCount*threadRatio;i++){
 90             Thread readT = new Thread(new ReadThread(goodsNum));
 91             readT.start();
 92         }
 93         // 3个线程写
 94         for(int i = 0;i<threadBaseCount;i++){
 95             Thread writeT = new Thread(new WriteThread(goodsNum));
 96             writeT.start();
 97         }
 98         countDownLatch.countDown();
 99 
100     }
101 
102 }

synchronized方式耗时:

Thread-20读取库存数据耗时:1130ms
Thread-23读取库存数据耗时:6468ms
Thread-25读取库存数据耗时:8076ms
Thread-7读取库存数据耗时:9432ms
Thread-26读取库存数据耗时:10527ms
Thread-28读取库存数据耗时:10885ms
Thread-8读取库存数据耗时:11359ms
Thread-29读取库存数据耗时:11519ms
Thread-21读取库存数据耗时:14422ms
Thread-13读取库存数据耗时:14626ms
Thread-5读取库存数据耗时:14994ms
Thread-6读取库存数据耗时:15136ms
Thread-4读取库存数据耗时:15326ms
Thread-14读取库存数据耗时:15496ms
Thread-18读取库存数据耗时:15598ms
Thread-22读取库存数据耗时:15715ms
Thread-24读取库存数据耗时:15757ms
Thread-27读取库存数据耗时:15776ms
Thread-31写库存数据耗时:15963ms
Thread-3读取库存数据耗时:16889ms
Thread-17读取库存数据耗时:17250ms
Thread-32写库存数据耗时:17375ms
Thread-9读取库存数据耗时:17843ms
Thread-2读取库存数据耗时:17944ms
Thread-15读取库存数据耗时:18117ms
Thread-1读取库存数据耗时:18284ms
Thread-11读取库存数据耗时:18418ms
Thread-10读取库存数据耗时:18452ms
Thread-30写库存数据耗时:18515ms
Thread-0读取库存数据耗时:18814ms
Thread-12读取库存数据耗时:18897ms
Thread-16读取库存数据耗时:18990ms
Thread-19读取库存数据耗时:18995ms

ReentrantReadWriteLock方式耗时:

Thread-30写库存数据耗时:1899ms
Thread-32写库存数据耗时:1968ms
Thread-31写库存数据耗时:2032ms
Thread-13读取库存数据耗时:2479ms
Thread-7读取库存数据耗时:2480ms
Thread-2读取库存数据耗时:2481ms
Thread-4读取库存数据耗时:2487ms
Thread-18读取库存数据耗时:2490ms
Thread-15读取库存数据耗时:2491ms
Thread-23读取库存数据耗时:2489ms
Thread-17读取库存数据耗时:2490ms
Thread-10读取库存数据耗时:2491ms
Thread-11读取库存数据耗时:2491ms
Thread-5读取库存数据耗时:2492ms
Thread-19读取库存数据耗时:2490ms
Thread-16读取库存数据耗时:2490ms
Thread-12读取库存数据耗时:2491ms
Thread-24读取库存数据耗时:2489ms
Thread-14读取库存数据耗时:2491ms
Thread-20读取库存数据耗时:2490ms
Thread-0读取库存数据耗时:2502ms
Thread-28读取库存数据耗时:2494ms
Thread-1读取库存数据耗时:2498ms
Thread-22读取库存数据耗时:2496ms
Thread-8读取库存数据耗时:2497ms
Thread-21读取库存数据耗时:2496ms
Thread-9读取库存数据耗时:2497ms
Thread-27读取库存数据耗时:2495ms
Thread-26读取库存数据耗时:2495ms
Thread-3读取库存数据耗时:2499ms
Thread-29读取库存数据耗时:2494ms
Thread-25读取库存数据耗时:2495ms
Thread-6读取库存数据耗时:2498ms

结论:ReentrantReadWriteLock性能比synchronized快8倍

Condition接口有何用处?

同Object的 wait,notify/notifyAll 类似实现等待通知机制

 Condition接口和Lock配合来实现等待通知机制

Condition常用方法和使用范式

 1 package com.lgs;
 2 
 3 import java.util.concurrent.locks.Condition;
 4 import java.util.concurrent.locks.Lock;
 5 import java.util.concurrent.locks.ReentrantLock;
 6 
 7 // Condition使用范式
 8 public class ConditionTemplete {
 9 
10     // 创建一个锁
11     Lock lock = new ReentrantLock();
12     
13     // 从创建的锁里面拿到Condition
14     Condition c = lock.newCondition();
15     
16     // 等待条件成熟
17     public void waitCondition() throws InterruptedException {
18         // 获取锁
19         lock.lock();
20         try {
21             // 等待条件成熟
22             c.await();
23         }finally {
24             // 释放锁
25             lock.unlock();
26         }
27     }
28     // 通知其他线程条件成熟
29     public void waitNotify() throws InterruptedException {
30         // 获取锁
31         lock.lock();
32         try {
33             // 通知
34             c.signal();
35             // 尽量少使用signalAll() 因为condition是从lock创建出来的,要通知哪个线程是知道的
36         }finally {
37             // 释放锁
38             lock.unlock();
39         }
40     }
41 }

结合ReentrantLockCondition实现线程安全的有界队列

 类似于之前的wait和notify/notifyAll实现有界阻塞队列

 1 package com.lgs.bq;
 2 
 3 import java.util.LinkedList;
 4 import java.util.List;
 5 import java.util.concurrent.locks.Condition;
 6 import java.util.concurrent.locks.Lock;
 7 import java.util.concurrent.locks.ReentrantLock;
 8 
 9 // Lock和Condition实现有界阻塞队列
10 // 类似于之前的wait和notify/notifyAll实现有界阻塞队列
11 public class BlockingQueueLockCondition<T> {
12 
13     // 定义一个list存放队列数据
14     private List queue = new LinkedList<>();
15     
16     // 定义队列的容量
17     private final int limit;
18     
19     // 定义Lock
20     Lock lock = new ReentrantLock();
21     
22     // 创建不能为空的condition
23     Condition notEmpty = lock.newCondition();
24     
25     // 创建不能满的condition
26     Condition notFull = lock.newCondition();
27     
28     // 定义构造函数来初始化队列容量
29     public BlockingQueueLockCondition(int limit) {
30         this.limit = limit;
31     } 
32     
33     // 入队
34     public  void enqueue(T item) throws InterruptedException {
35         // 获取锁
36          lock.lock();
37         try {
38             // 当队列已经满了的时候就需要等待
39             while (this.queue.size() == this.limit) {
40                 notFull.await();
41             }
42             // 数据入队
43             this.queue.add(item);
44             // 队列入队后就通知出队可以取数据了 因为可以肯定有出队的线程正在等待
45             notEmpty.signal();
46         } finally {
47             lock.unlock();
48         }
49     }
50     
51     // 出队
52     public  T dequeue() throws InterruptedException {
53         // 获取锁
54         lock.lock();
55         try {
56             // 如果队列没有数据时就等待
57             while (this.queue.size() == 0) {
58                 notEmpty.await();
59             }
60             // 数据即将出队就通知入队可以插入数据了 因为可以肯定有入队的线程正在等待
61             notFull.signal();
62             // 数据出队
63             return (T) this.queue.remove(0);
64         } finally {
65             lock.unlock();
66         }
67     }
68 }

测试;

package com.lgs.bq;

// 测试等待通知机制实现的有界阻塞队列
public class BqTest {
    // 定义一个推数据入队列的线程
    private static class PushThread extends Thread{
        // 持有BlockingQueueLockCondition
        BlockingQueueLockCondition<Integer> bq;
        
        public PushThread(BlockingQueueLockCondition<Integer> bq) {
             this.bq = bq;
        }
        
        public void run() {
            int i = 20;
            while(i > 0) {
                System.out.println(" i=" + i +" will push");
                try {
                    Thread.sleep(500);
                    bq.enqueue(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                i--;
            }
        }
    }
    
     //取数据出队列
    private static class PopThread extends Thread{
        BlockingQueueLockCondition<Integer> bq;

        public PopThread(BlockingQueueLockCondition<Integer> bq) {
            this.bq = bq;
        }
        @Override
        public void run() {
            while(true){
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()
                            +" will pop.....");
                    Integer i = bq.dequeue();
                    System.out.println(" i="+i.intValue()+" alread pop");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
    
    public static void main(String[] args) {
        // 创建一个有界阻塞队列的实例
        BlockingQueueLockCondition<Integer> bq = new BlockingQueueLockCondition(10);
        
        // 创建入队线程
        Thread t1 = new PushThread(bq);
        t1.setName("push");
        
        // 创建出队列线程
        Thread t2 = new PopThread(bq);
        t2.setName("pop");
        
        // 启动线程
        t1.start();
        t2.start();
    }
}

输出:

 i=20 will push
 i=19 will push
pop will pop.....
 i=20 alread pop
 i=18 will push
 i=17 will push
pop will pop.....
 i=19 alread pop
 i=16 will push
 i=15 will push
pop will pop.....
 i=18 alread pop
 i=14 will push
 i=13 will push
pop will pop.....
 i=17 alread pop
 i=12 will push
 i=11 will push
pop will pop.....
 i=16 alread pop
 i=10 will push
 i=9 will push
pop will pop.....
 i=15 alread pop
 i=8 will push
 i=7 will push
pop will pop.....
 i=14 alread pop
 i=6 will push
 i=5 will push
pop will pop.....
 i=13 alread pop
 i=4 will push
 i=3 will push
pop will pop.....
 i=12 alread pop
 i=2 will push
 i=1 will push
pop will pop.....
 i=11 alread pop
pop will pop.....
 i=10 alread pop
pop will pop.....
 i=9 alread pop
pop will pop.....
 i=8 alread pop
pop will pop.....
 i=7 alread pop
pop will pop.....
 i=6 alread pop
pop will pop.....
 i=5 alread pop
pop will pop.....
 i=4 alread pop
pop will pop.....
 i=3 alread pop
pop will pop.....
 i=2 alread pop
pop will pop.....
 i=1 alread pop
pop will pop.....

 

 

 

 

 

posted @ 2018-01-08 21:05  小不点啊  阅读(1006)  评论(0编辑  收藏  举报