有了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 }
ReentrantReadWriteLock和synchronized性能比较
案例:两种锁实现对库存的读写性能比较
定义一个货物库存的实体类:
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 }
结合ReentrantLock和Condition实现线程安全的有界队列
类似于之前的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.....