公平锁和非公平锁 公平锁 :多个线程按照申请所得顺序来获取锁,先进先出。如果等待队列为空或者在第一个位置则直接占有锁非公平锁 : 线程直接抢占锁,如果抢占失败,则加入等待队列,采用公平锁方式
非公平锁优势就是吞吐量 比非公平锁大。synchronized
和ReentrantLock
默认都是非公平锁。ReentrantLock
在构造的时候传入true
则是公平锁。
可重入锁(递归锁) 可重入锁(也就是递归锁):指的是同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。synchronized
和ReentrantLock
都是常见的可重入锁。
也就是说,线程可以进入任何一个它已经拥有的锁所有同步着的代码块。
可重入锁的作用就是避免死锁 的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Phone { public synchronized void sendSMS() throws Exception{ System.out.println(Thread.currentThread().getId()+"\t invoked sendSMS()" ); sendEmail(); } public synchronized void sendEmail() throws Exception{ System.out.println(Thread.currentThread().getId()+"\t invoked sendEmail()" ); } } public class RenenterLockDemo { public static void main(String[] args){ Phone phone = new Phone(); new Thread(()->{ try { phone.sendSMS(); } catch (Exception e){ e.printStackTrace(); } },"t1" ) .start () ; new Thread (()->{ try { phone.sendSMS(); } catch (Exception e){ e.printStackTrace(); } },"t2" ) .start () ; } }
实现一个可重入锁(递归锁) 为每个锁关联一个获取计数器和一个所有者线程,当计数值为0时,这个锁就被认为是没有被任何线程所占有的。当线程请求一个未被持有的锁时,计数值将会递增。而当线程退出同步代码时,计数器会相应地递减。当计数值为0时,则释放该锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class SpinLockDemo { private MySpinLock lock = new MySpinLock(); class Widget { public void doSomething ( ) { lock .lock (); System.out .println("Widget calling doSomething" ); lock .unlock(); } } class MySpinLock { private AtomicReference<Thread> owner = new AtomicReference<>(); private int count = 0 ; public void lock ( ) { Thread cur = Thread.currentThread(); if (cur == owner.get ()){ count ++; return ; } while (! owner.compareAndSet(null ,cur)){ } } public void unlock ( ) { Thread cur = Thread.currentThread(); if (cur == owner.get ()){ if (count != 0 ){ count --; } else { owner.compareAndSet(cur,null ); } } } } class LoggingWidget extends Widget { @Override public void doSomething ( ) { lock .lock (); System.out .println("LoggingWidget calling doSomething" ); super.doSomething(); lock .unlock(); } } public static void main (String[] args ) { SpinLockDemo spinLockDemo = new SpinLockDemo(); SpinLockDemo.Widget widget = spinLockDemo.new LoggingWidget(); widget.doSomething(); } } 输出结果: LoggingWidget calling doSomething Widget calling doSomething
独占锁和共享锁 读锁是共享的,写锁是独占的 。ReentrantLock
和synchronized
都是独占锁,独占锁就是一个锁只能被一个线程所持有。有的时候,需要读写分离,那么就要引入读写锁,即ReentrantReadWriteLock
。
比如缓存,就需要读写锁来控制。缓存就是一个键值对,读的get方法使用了ReentrantReadWriteLock.ReadLock()
,写的put方法使用了ReentrantReadWriteLock.WriteLock()
。这样避免了写被打断,实现了多个线程同时读。
DEMO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; class MyCache{ private volatile Map<String,Object> map = new HashMap<>() ; private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock() ; public void put(String key,Object value){ reentrantReadWriteLock.writeLock() .lock() ; try { System . out.println(Thread . currentThread() .getName() +"\t 正在写入:" +key); try { TimeUnit .MILLISECONDS . sleep(300 ); } catch (InterruptedException e) {e.printStackTrace() ;} map.put(key,value); System . out.println(Thread . currentThread() .getName() +"\t 写入完成" ); }catch (Exception e){ e.printStackTrace() ; }finally { reentrantReadWriteLock.writeLock() .unlock() ; } } public void get(String key){ reentrantReadWriteLock.readLock() .lock() ; try { System . out.println(Thread . currentThread() .getName() +"\t 正在读取:" +key); try { TimeUnit .MILLISECONDS . sleep(300 ); } catch (InterruptedException e) {e.printStackTrace() ;} Object result = map.get(key); System . out.println(Thread . currentThread() .getName() +"\t 读取完成" +result); }catch (Exception e){ e.printStackTrace() ; }finally { reentrantReadWriteLock.readLock() .unlock() ; } } } public class ReadWriteLockDemo { public static void main(String[] args){ MyCache myCache = new MyCache() ; for(int i=1 ;i<=5 ;i++){ final int tempInt = i; new Thread(() ->{ myCache.put(tempInt+"" ,tempInt+"" ); },String . valueOf(i ) ).start() ; } for(int i=1 ;i<=5 ;i++){ final int tempInt = i; new Thread(() ->{ myCache.get(tempInt+"" ); },String . valueOf(i ) ).start() ; } } }
自旋锁 是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下切换的消耗,缺点是循环会消耗CPU。CAS底层的getAndAddInt就是自旋锁思想。
尝试写个自旋锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; public class SpinLockDemo { AtomicReference<Thread> atomicReference = new AtomicReference<>() ; public void myLock() { Thread thread = Thread . currentThread() ; System . out.println(Thread . currentThread() .getName() +"\t come in " ); while (!atomicReference.compareAndSet(null ,thread ) ){ System . out.println(Thread . currentThread() .getName() +"\t in while" ); } } public void myUnLock() { Thread thread = Thread . currentThread() ; atomicReference.compareAndSet(thread ,null ) ; System . out.println(Thread . currentThread() .getName() +"\t invoked myUnLock()" ); } public static void main(String[] args){ SpinLockDemo spinLockDemo = new SpinLockDemo() ; new Thread(() ->{ spinLockDemo.myLock() ; try { TimeUnit .SECONDS . sleep(30 ); } catch (InterruptedException e) {e.printStackTrace() ;} spinLockDemo.myUnLock() ; },"AA" ).start() ; try { TimeUnit .SECONDS . sleep(1 ); } catch (InterruptedException e) {e.printStackTrace() ;} new Thread(() ->{ spinLockDemo.myLock() ; try { TimeUnit .SECONDS . sleep(5 ); } catch (InterruptedException e) {e.printStackTrace() ;} },"BB" ).start() ; } }
synchronized和lock有什么区别?用新的lock有什么好处?
synchronized
是关键字,属于JVM
层面,monitorenter
(底层是通过monitor
对象来完成,其实wait/notify
等方法也依赖于monitor
对象只有在同步块或者方法中才能调用wait/notify
等方法)
Lock
是具体类(java.util.concurrent.locks.lock)
是api
层面的锁。
synchronized
不需要用户去手动释放锁,当synchronized
代码执行完后系统会自动让线程释放对锁的占用。
ReentrantLock
则需要用户去手动释放锁,若没有主动释放锁,就有可能导致出现死锁现象。需要lock()
和unlock()
方法配合try/finally
语句块来完成。
synchronized
不可中断,除非抛出异常或者正常运行完成。
ReentrantLock
可中断 1.设置超时方法 tryLock(long timeout,TimeUnit unit);
2.lockInterruptibly()
放代码块中,调用interrupt()
方法可中断。
synchronized
非公平锁
ReentrantLock
两者都可以,默认非公平锁,构造方法可以传入boolean
值,true
为公平锁,false
为非公平锁。
synchronized
没有
ReentrantLock
用来实现分组唤醒需要唤醒的线程,可以精确唤醒,而不是像synchronized
要么随机唤醒一个要么唤醒全部线程。
例子 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class ShareResource { private int number = 1 ; private Lock lock = new ReentrantLock(); private Condition c1 = lock .newCondition(); private Condition c2 = lock .newCondition(); private Condition c3 = lock .newCondition(); public void print5 ( ) { lock .lock (); try { while (number != 1 ){ c1.await (); } for (int i=1 ;i<=5 ;i++){ System.out .println(Thread.currentThread().getName()+"\t" +i); } number = 2 ; c2.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock .unlock(); } } public void print10 ( ) { lock .lock (); try { while (number != 2 ){ c2.await (); } for (int i=1 ;i<=10 ;i++){ System.out .println(Thread.currentThread().getName()+"\t" +i); } number = 3 ; c3.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock .unlock(); } } public void print15 ( ) { lock .lock (); try { while (number != 3 ){ c3.await (); } for (int i=1 ;i<=15 ;i++){ System.out .println(Thread.currentThread().getName()+"\t" +i); } number = 1 ; c1.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock .unlock(); } } } public class SyncAndReentrantLockDemo { public static void main (String[] args ) { ShareResource shareResource = new ShareResource(); new Thread(()->{ for (int i=1 ;i<=10 ;i++){ shareResource.print5(); } },"A" ).start(); new Thread(()->{ for (int i=1 ;i<=10 ;i++){ shareResource.print10(); } },"B" ).start(); new Thread(()->{ for (int i=1 ;i<=10 ;i++){ shareResource.print15(); } },"C" ).start(); } }