代码GitHub地址 github.com/imyiren/con…
Lock
和synchronized
,是Java中最常见的锁,他们都可以达到线程安全的目的,但是在使用上和功能上又有较大的不同,Lock
并不是用来替代synchronized
的,而是当使用synchronized
不适合或不足以瞒住要求的时候,来提供一些其他功能。Lock
接口中最常见的实现类就是ReentrantLock
,通常情况下,Lock
只允许一个线程来访问这个共享资源,不过一些特殊的实现也可允许并发访问,比如ReadWriteLock
里面的ReadLock
。synchronized
不够用,有如下问题:
在Lock
中声明了四个方法来获取锁:lock()
、tryLock()
、tryLock(long time, TimeUnit unit)
和lockInterruptibly()
lock()
就是最普通的获取锁,如果锁已被其他线程获取,则等待;Lock
不会像synchronized
一样在异常时自动释放锁,因此我们需要手动释放锁,最佳实践:在finally中释放锁,以保证发生异常时锁一定被释放。此外lock()
方法不能被中断,这会有很大隐患,一旦陷入死锁,lock()
就会陷入永久等待。/** * Lock最佳实践 Lock不像synchronized主动释放锁,需要调用unlock * @author yiren */ public class LockInterface { private static Lock lock = new ReentrantLock(); public static void main(String[] args) { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " do some work!"); }finally { lock.unlock(); } } } 复制代码
tryLock()
用来尝试获取锁,如果当前所没有被其他线程占用,则获取成功返回true,锁获取失败返回false;相比于lock()
,这样的方法显然功能更强大了,我们可以根据是否能获取到锁来决定后续的程序行为;且此方法会立即返回;tryLock(long time, TimeUnit unit)
和tryLock()
使用类似,不过它本身可以阻塞等待一段时间锁,超时过后再放弃。
/** * 使用tryLock来避免死锁 * * @author yiren */ public class DeadlockTryLock { private static Lock lock1 = new ReentrantLock(); private static Lock lock2 = new ReentrantLock(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { for (int i = 0; i < 100; i++) { try { if (lock1.tryLock(1, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName() + " got lock 1"); TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10)); if (lock2.tryLock(1, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName() + " got lock1 and lock2 successfully."); lock2.unlock(); lock1.unlock(); break; } else { System.out.println(Thread.currentThread().getName() + " fail to get lock2"); lock1.unlock(); } TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10)); } else { System.out.println(Thread.currentThread().getName() + " fail to get lock1"); } } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 100; i++) { try { if (lock2.tryLock(1, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName() + " got lock 2"); TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10)); if (lock1.tryLock(1, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName() + " got lock2 and lock1 successfully."); lock1.unlock(); lock2.unlock(); break; } else { System.out.println(Thread.currentThread().getName() + " fail to get lock1"); lock2.unlock(); } TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10)); } else { System.out.println(Thread.currentThread().getName() + " fail to get lock2"); } } catch (InterruptedException e) { e.printStackTrace(); } } }); thread1.start(); thread2.start(); } } 复制代码
Thread-0 got lock 1 Thread-1 got lock 2 Thread-1 fail to get lock1 Thread-0 fail to get lock2 Thread-0 got lock 1 Thread-1 got lock 2 Thread-1 fail to get lock1 Thread-0 got lock1 and lock2 successfully. Thread-1 got lock 2 Thread-1 got lock2 and lock1 successfully. Process finished with exit code 0 复制代码
lockInterruptibly()
相当于tryLock(long time, TimeUnit unit)
把超时时间设置为无线。并且在等待锁的过程中,线程可以被中断。/** * @author yiren */ public class LockInterruptibly { private static Lock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { Runnable runnable = () -> { try { System.out.println(Thread.currentThread().getName() + " try to get lock"); lock.lockInterruptibly(); try { System.out.println(Thread.currentThread().getName() + " got lock"); Thread.sleep(5000); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " sleep "); } finally { lock.unlock(); System.out.println(Thread.currentThread().getName() + " unlock"); } } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + " lockInterruptibly "); } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); Thread.sleep(2000); thread2.interrupt(); } } 复制代码
Thread-0 try to get lock Thread-0 got lock Thread-1 try to get lock Thread-1 lockInterruptibly Thread-0 unlock Process finished with exit code 0 复制代码
synchronized
和Lock
相关类/** * @author yiren */ public class ReentrantLockDemo { public static void main(String[] args) { Lock lock = new ReentrantLock(); lock.lock(); try { System.out.println("in 1"); lock.lock(); try { System.out.println("in 2"); }finally { lock.unlock(); System.out.println("out 2"); } }finally { lock.unlock(); System.out.println("out 1"); } } } 复制代码
in 1 in 2 out 2 out 1 Process finished with exit code 0 复制代码
/** * @author yiren */ public class ReentrantLockDemo { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); lock.lock(); try { System.out.println("HoldCount:" + lock.getHoldCount() + " in 1"); lock.lock(); try { System.out.println("HoldCount:" + lock.getHoldCount() + " in 2"); lock.lock(); try { System.out.println("HoldCount:" + lock.getHoldCount() + " in 3"); }finally { lock.unlock(); System.out.println("out 3"); } }finally { lock.unlock(); System.out.println("out 2"); } }finally { lock.unlock(); System.out.println("out 1"); } } } 复制代码
HoldCount:1 in 1 HoldCount:2 in 2 HoldCount:3 in 3 out 3 out 2 out 1 Process finished with exit code 0 复制代码
ReentrantLock
中默认是使用的NonfairSync
,而NonfairSync
继承自Sync
,加锁和释放锁主要涉及里面下面两个方法,另外FairSync里面的关于重入锁部分也差不多。final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } 复制代码
nonfairTryAcquire
中else-if会判断如果当前线程就是已经占有锁的线程,则status就会加一,并返回true。tryRelease
中也是先判断当前线程是否是已经占有锁的线程,然后在判断status
,如果status
等于0了,才真正释放锁。isHeldByCurrentThread()
可以查看出锁是否被当前线程锁持有getQueueLength
可以返回当前正在等待这把锁的队列有多长ReentrantLock
是,传入参数true
,此时就会变成公平锁printQueue
里面ReentrantLock
的参数/** * @author yiren */ public class FairLock { public static void main(String[] args) throws InterruptedException { PrintQueue queue = new PrintQueue(); ExecutorService executorService = Executors.newFixedThreadPool(4); for (int i = 0; i < 4; i++) { executorService.execute(()->{ System.out.println(Thread.currentThread().getName()+ " start to print"); queue.printJob(new Object()); System.out.println(Thread.currentThread().getName()+ " finished print "); }); TimeUnit.MILLISECONDS.sleep(100); } } private static class PrintQueue { private Lock lock = new ReentrantLock(true); private void printJob(Object document) { lock.lock(); try { Integer duration = (int) (Math.random() * 3 + 1); System.out.println(Thread.currentThread().getName() + " print 1 need " + duration + " s"); Thread.sleep(duration * 1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } lock.lock(); try { Integer duration = (int) (Math.random() * 3 + 1); System.out.println(Thread.currentThread().getName() + " print 2 need " + duration + " s"); Thread.sleep(duration * 1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } } 复制代码
pool-1-thread-1 start to print pool-1-thread-1 print 1 need 1 s pool-1-thread-2 start to print pool-1-thread-3 start to print pool-1-thread-4 start to print pool-1-thread-2 print 1 need 3 s pool-1-thread-3 print 1 need 1 s pool-1-thread-4 print 1 need 2 s pool-1-thread-1 print 2 need 3 s pool-1-thread-1 finished print pool-1-thread-2 print 2 need 2 s pool-1-thread-2 finished print pool-1-thread-3 print 2 need 3 s pool-1-thread-3 finished print pool-1-thread-4 print 2 need 3 s pool-1-thread-4 finished print 复制代码
pool-1-thread-1 start to print pool-1-thread-1 print 1 need 3 s pool-1-thread-2 start to print pool-1-thread-3 start to print pool-1-thread-4 start to print pool-1-thread-1 print 2 need 2 s pool-1-thread-1 finished print pool-1-thread-2 print 1 need 3 s pool-1-thread-2 print 2 need 1 s pool-1-thread-2 finished print pool-1-thread-3 print 1 need 3 s pool-1-thread-3 print 2 need 2 s pool-1-thread-3 finished print pool-1-thread-4 print 1 need 3 s pool-1-thread-4 print 2 need 1 s pool-1-thread-4 finished print 复制代码
tryLock()
它不遵守设定的公平规则。也就是说:当有线程执行tryLock
的时候,一旦有线程释放了锁,即使他之前已经有其他在等待队列里的线程,这个正在tryLock的线程依旧能获取到锁。static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; final void lock() { acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } } 复制代码
/** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } 复制代码
!hasQueuedPredecessors()
,它会判断是否有现成在队列前面已经排队了,如果没有才去获取锁。ReentrantReadWriteLock
为代表共享锁:又称读锁,获取共享锁过后,可以查看但是无法修改和删除,其他线程可以同时获取到共享锁
排他锁:又称独占锁、独占锁,获取了排他锁后既可以读又可以写,但是其他线程无法再次获取。
如果我们不适用读写锁,那么我们多个线程读的操作,并不能同时进行,只能排队,虽然没有线程安全问题,但是性能会变差。
如果我们在读的地方用读锁,写的地方用写锁,可以提高效率。
ReentrantReadWriteLock
用法/** * @author yiren */ public class ReadWriteLock { private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock(); private static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock(); private static void read() { readLock.lock(); try { System.out.println(Thread.currentThread().getName() + " start to read, got read lock"); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + " read finished, release read lock"); } catch (InterruptedException e) { e.printStackTrace(); } finally { readLock.unlock(); } } private static void write() { writeLock.lock(); try { System.out.println(Thread.currentThread().getName() + " start to write, got write lock"); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + " read finished, release write lock"); } catch (InterruptedException e) { e.printStackTrace(); } finally { writeLock.unlock(); } } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 2; i++) { executorService.execute(ReadWriteLock::write); } for (int i = 0; i < 5; i++) { executorService.execute(ReadWriteLock::read); } } } 复制代码
pool-1-thread-1 start to write, got write lock pool-1-thread-1 read finished, release write lock pool-1-thread-2 start to write, got write lock pool-1-thread-2 read finished, release write lock pool-1-thread-3 start to read, got read lock pool-1-thread-2 start to read, got read lock pool-1-thread-1 start to read, got read lock pool-1-thread-3 read finished, release read lock pool-1-thread-1 read finished, release read lock pool-1-thread-2 read finished, release read lock pool-1-thread-1 start to read, got read lock pool-1-thread-3 start to read, got read lock pool-1-thread-3 read finished, release read lock pool-1-thread-1 read finished, release read lock 复制代码
static final class NonfairSync extends Sync { private static final long serialVersionUID = -8159625535654395037L; final boolean writerShouldBlock() { return false; // writers can always barge } final boolean readerShouldBlock() { /* As a heuristic to avoid indefinite writer starvation, * block if the thread that momentarily appears to be head * of queue, if one exists, is a waiting writer. This is * only a probabilistic effect since a new reader will not * block if there is a waiting writer behind other enabled * readers that have not yet drained from the queue. */ return apparentlyFirstQueuedIsExclusive(); } } 复制代码
apparentlyFirstQueuedIsExclusive
队列头结点是不是排他锁(写锁)如果是就不允许插队了。public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(5); executorService.execute(ReadWriteLock::write); executorService.execute(ReadWriteLock::read); executorService.execute(ReadWriteLock::read); executorService.execute(ReadWriteLock::write); executorService.execute(ReadWriteLock::read); } 复制代码
pool-1-thread-1 start to write, got write lock pool-1-thread-1 read finished, release write lock pool-1-thread-2 start to read, got read lock pool-1-thread-3 start to read, got read lock pool-1-thread-2 read finished, release read lock pool-1-thread-3 read finished, release read lock pool-1-thread-4 start to write, got write lock pool-1-thread-4 read finished, release write lock pool-1-thread-5 start to read, got read lock pool-1-thread-5 read finished, release read lock 复制代码
此时我们就可以看到,线程5读线程,并没有插队执行,而是等待了线程4完成了,再执行。
额外提醒:读锁在队列头部不是写锁的时候,是可以插队的。
public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(4); executorService.execute(ReadWriteLock::write); executorService.execute(ReadWriteLock::read); executorService.execute(ReadWriteLock::read); executorService.execute(ReadWriteLock::write); executorService.execute(ReadWriteLock::read); } 复制代码
pool-1-thread-1 start to write, got write lock pool-1-thread-1 read finished, release write lock pool-1-thread-2 start to read, got read lock pool-1-thread-3 start to read, got read lock pool-1-thread-1 start to read, got read lock pool-1-thread-2 read finished, release read lock pool-1-thread-3 read finished, release read lock pool-1-thread-1 read finished, release read lock pool-1-thread-4 start to write, got write lock pool-1-thread-4 read finished, release write lock 复制代码
/** * @author yiren */ public class ReadWriteLockLevel { public static void main(String[] args) { ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); Thread thread = new Thread(() -> { readWriteLock.writeLock().lock(); try { System.out.println("writer task!"); Thread.sleep(1000); readWriteLock.readLock().lock(); } catch (InterruptedException e) { e.printStackTrace(); } finally { readWriteLock.writeLock().unlock(); } try { System.out.println("reader task!"); Thread.sleep(1000); System.out.println("reader task! end"); } catch (InterruptedException e) { e.printStackTrace(); } finally { readWriteLock.readLock().unlock(); } }); Thread thread1 = new Thread(() -> { readWriteLock.readLock().lock(); try { System.out.println("other reader task!"); }finally { readWriteLock.readLock().unlock(); } }); thread.start(); thread1.start(); } } 复制代码
writer task! reader task! other reader task! reader task! end Process finished with exit code 0 复制代码
自旋锁的缺点:如果锁的占用时间过长,那么自旋的线程就会白白浪费处理器资源,浪费资源随时间线性增长
原理和源码分析
atomic
包下的类基本都是自旋锁试下// AtomicInteger public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } // Unsafe public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; } 复制代码
自己实现一个简单的自旋锁:
/** * @author yiren */ public class SpinLock { private static AtomicReference<Thread> sign = new AtomicReference<>(); private static void lock() { Thread current = Thread.currentThread(); while (!sign.compareAndSet(null, current)) { System.out.println("fail to set!"); } } private static void unlock() { Thread thread = Thread.currentThread(); sign.compareAndSet(thread, null); } public static void main(String[] args) { Runnable runnable = () -> { System.out.println("start to get lock"); SpinLock.lock(); System.out.println("got lock successfully!"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }finally { SpinLock.unlock(); } }; Thread thread = new Thread(runnable); Thread thread1 = new Thread(runnable); thread.start(); thread1.start(); } } 复制代码
在java中,synchronized就是不可中断锁,而Lock是可中断锁,可通过tryLock(time)
和lockInterruptibly
来实现响应中断
上面Lock接口案例演示中已经演示过,可看第一部分的LockInterruptibly
类
缩小同步代码块,只锁需要锁的
尽量不要锁住方法
减少锁的请求次数,减少频繁获取锁的开销。
避免人为制造“热点”,比如一个集合你每次用大小都去遍历一遍计数
锁里面尽量不要包含锁
选择合适的锁的类型或者合适的工具类