锁是干什么用的
锁一般来说用作资源控制,限制资源访问,防止在并发环境下造成数据错误
java中有哪些锁
按照锁的性质分类
- 公平锁 / 非公平锁
- 乐观锁 / 悲观锁:
- 独享锁 / 共享锁 (互斥锁 / 读写锁)
- 可重入锁(递归锁)
按照锁设计方案分类
- 自旋锁 / 自适应自旋锁
- 锁粗化 / 锁消除
- 偏向锁 / 轻量级锁 / 重量级锁
可重入锁
可重入锁也叫作递归锁,指的是同一个线程外层函数获取到一把锁后,内层函数同样具有这把锁的控制权限
synchronized和ReentrantLock就是重入锁对应的实现
- synchronized重量级的锁
- ReentrantLock轻量级的锁 lock()代表加入锁 unlock()代表释放锁
不可重入锁:说明当没有释放该锁时。其他线程获取该锁会进行等待
例如下面案例
设计一把锁
1 package com.wish.demo06; 2 3 4 public class MyLock { 5 private boolean isLock = false; //判断是否上锁 6 7 //上锁 8 public synchronized void lock() throws InterruptedException { 9 while (isLock){ 10 //如果上锁了就进入等待状态 11 wait(); 12 } 13 //否则就上锁,占用资源 14 isLock =true; 15 } 16 17 //释放锁 18 public synchronized void unLock(){ 19 //将锁的状态进行改变 20 isLock=false; 21 //重新唤起正在等待的线程 22 notify(); 23 } 24 }
使用设计的锁进行测试
1 package com.wish.demo06; 2 3 public class MyLockTest { 4 MyLock myLock=new MyLock(); 5 //A业务方法 6 public void print() throws InterruptedException { 7 //获取一把锁 8 myLock.lock(); 9 System.out.println("print业务方法"); 10 doAdd(); 11 //释放锁 12 myLock.unLock(); 13 } 14 15 //B业务方法 16 public void doAdd() throws InterruptedException { 17 //获取一把锁 18 myLock.lock(); 19 System.out.println("doAdd业务方法"); 20 //释放锁 21 myLock.unLock(); 22 } 23 24 public static void main(String[] args) throws InterruptedException { 25 MyLockTest test=new MyLockTest(); 26 test.print(); 27 } 28 }
结果:因为不是可重入的锁线程就一直进入等待的情况,也就是死锁
可重入锁: 指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
synchronized可重入锁:如果当前A持有一把锁,在A业务内部调用B,那么B也同样拥有这把锁的使用权限
package com.wish.demo06; public class MyLockTest { //A业务方法 public synchronized void print() throws InterruptedException { //获取了一把锁 System.out.println("print业务方法"); doAdd(); } //B业务方法 public synchronized void doAdd() throws InterruptedException { System.out.println("doAdd业务方法"); //释放锁 } public static void main(String[] args) throws InterruptedException { MyLockTest test=new MyLockTest(); test.print(); } }
结果:这就是可重入锁的特性,在同一个线程,外层的方法获取了一把锁,在其内部的方法也可以使用这把锁,不管其内部有多少个方法都可以
ReentrantLock可重入锁
1 package com.wish.demo06; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class MyLockTest { 7 8 //创建锁对象 9 Lock lock=new ReentrantLock(); 10 //A业务方法 11 public void print() throws InterruptedException { 12 //获取了一把锁 13 lock.lock(); 14 System.out.println("print业务方法"); 15 doAdd(); 16 //释放锁 17 lock.unlock(); 18 } 19 20 //B业务方法 21 public void doAdd() throws InterruptedException { 22 //获取了一把锁 23 lock.lock(); 24 System.out.println("doAdd业务方法"); 25 //释放锁 26 lock.unlock(); 27 } 28 29 public static void main(String[] args) throws InterruptedException { 30 MyLockTest test=new MyLockTest(); 31 test.print(); 32 } 33 }
结果:和synchronized可重入锁的效果一样
ReentrantLock底层模拟
1 package com.wish.demo06; 2 3 public class MyReentrantLock { 4 //标识锁是否可用 如果值为true代表当前有线程正在使用该锁,如果为false代表没有人用锁 5 private boolean isLocked=false; 6 //当前线程 7 Thread lockedBy=null; 8 //加锁数量计数 9 Integer lockedCount=0; 10 //加锁 11 public synchronized void lock() throws InterruptedException { 12 //获取当前线程 13 Thread thread=Thread.currentThread(); 14 //判断当前是否正在使用锁,如果正在使用则对比当前使用要使用锁的线程和之前使用锁的线程是否一致 15 //如果一致代表可以重入,继续使用锁,不会发生阻塞 16 //如果不一致代表当前不是一个线程,则等待 17 while (isLocked && thread!=lockedBy){ 18 wait(); 19 } 20 //占用锁 21 isLocked=true; 22 //计数+1 23 lockedCount++; 24 //赋值线程 25 lockedBy=thread; 26 } 27 //释放锁 28 public synchronized void unlock(){ 29 //判断当前是否是用一个线程 30 if(Thread.currentThread()==this.lockedBy){ 31 //锁使用计数器-1 32 lockedCount--; 33 //判断计数器是否为0,如果为0则释放锁,然后唤醒正在等待的线程 34 if(lockedCount==0){ 35 isLocked=false; 36 notify(); 37 } 38 } 39 } 40 }
调用MyReentrantLock
1 package com.wish.demo06; 2 3 public class MyLockTest { 4 5 //创建锁对象 6 MyReentrantLock lock=new MyReentrantLock(); 7 //A业务方法 8 public void print() throws InterruptedException { 9 //获取了一把锁 10 lock.lock(); 11 System.out.println("print业务方法"); 12 doAdd(); 13 //释放锁 14 lock.unlock(); 15 } 16 17 //B业务方法 18 public void doAdd() throws InterruptedException { 19 //获取了一把锁 20 lock.lock(); 21 System.out.println("doAdd业务方法"); 22 //释放锁 23 lock.unlock(); 24 } 25 26 public static void main(String[] args) throws InterruptedException { 27 MyLockTest test=new MyLockTest(); 28 test.print(); 29 } 30 }
结果:在ReentrantLock的底层中基本上就是判断是否在使用锁,如果使用了是否是同一个线程
读写锁
- 并发线程下,所有线程都执行读的操作,会不会有问题
- 并发线程下,部分读部分写会不会有问题 会发生写冲突
- 并发线程下,所有线程都执行写会不会有问题 会发生写冲突
ReentrantReadWriteLock:Lock的另一个实现类,其读锁是共享锁,其写锁是独享锁。
案例
1 package com.wish.demo06; 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 public class ReadWriteLock { 9 //创建一个集合 10 static Map<String,String> map=new HashMap<String,String>(); 11 //创建一个读写锁 12 static ReentrantReadWriteLock lock=new ReentrantReadWriteLock(); 13 //获取读锁 14 static Lock readLock=lock.readLock(); 15 //获取写锁 16 static Lock writeLock=lock.writeLock(); 17 //写操作 18 public Object put(String key,String value){ 19 writeLock.lock(); 20 try { 21 System.out.println("Write正在执行写操作~"); 22 Thread.sleep(100); 23 String put = map.put(key, value); 24 System.out.println("Write写操作执行完毕~"); 25 return put; 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 }finally { 29 writeLock.unlock(); 30 } 31 return null; 32 33 } 34 35 //写操作 36 public Object get(String key){ 37 readLock.lock(); 38 try { 39 System.out.println("Read正在执行读操作~"); 40 Thread.sleep(100); 41 String value = map.get(key); 42 System.out.println("Read读操作执行完毕~"); 43 return value; 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 }finally { 47 readLock.unlock(); 48 } 49 return null; 50 51 } 52 53 public static void main(String[] args) { 54 ReadWriteLock lock=new ReadWriteLock(); 55 for (int i = 0; i < 10; i++) { 56 int finalI = i; 57 new Thread(()->{ 58 try { 59 //写操作 60 lock.put(finalI +"","value"+finalI); 61 //读操作 62 System.out.println(lock.get(finalI+"")); 63 } catch (Exception e) { 64 e.printStackTrace(); 65 } 66 }).start(); 67 } 68 69 } 70 }
结果:可以看到在写的过程中没有读
乐观锁与悲观锁
乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
乐观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,
所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),
如果失败则要重复读-比较-写的操作。 java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
悲观锁
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,
所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。
java中的悲观锁就是Synchronized。