锁 锁是一些对象,它们为使用synchronized控制对共享资源的访问提供替代技术。大体而言,锁的 工作原理如下:在访问共享资源之前,申请用于保护资源的锁;放资源访问完成时,释放锁。当某个线程正在使用锁时,如果另一个线程尝试申请锁,那么后者会被挂起,直到锁被释放为止。通过这种方式,可以防止对共享资源的冲突访问。
当多个线程需要方法共享数据时,锁特别有用。当两个或多个线程正在对某个数据处理时,如果不采取某些形式的同步机制,那么可能导致某几个线程对数据的时候和判断出现不对。
类型
描述
自旋锁
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
乐观锁
假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改。
悲观锁
假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。
独享锁(写)
给资源加上写锁,线程可以修改资源,其他线程不能再加锁; (单写)
共享锁(读)
给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁; (多读)
可重入锁
线程拿到一把锁之后,即使锁不释放,再次拿锁线程也不会会被挂起等待,可以自由进入同一把锁所同步的其他代码。
不可重入锁
线程拿到一把锁之后,如果不释放锁,再次拿锁线程会被挂起等待。
公平锁
争抢锁的顺序,是按先来后到
非公平锁
争抢锁的顺序,不一定是按先来后到
状态
描述
Unlocked
未锁定
Biased/biasable
偏向锁,在JDK6 以后,默认已经开启了偏向锁这个优化,通过JVM 参数 -XX:-UseBiasedLocking 来禁用偏向锁若偏向锁开启,只有一个线程抢锁,可获取到偏向锁
Light-weight locked
轻量级锁,在未锁定的状态下,可以通过CAS来抢锁,抢到的是轻量级锁
Heavy-weight locked
重量级锁,轻量级锁中的自旋有一定的次数限制,超过了次数限制,轻量级锁升级为重量级锁。
默认情况下JVM锁会经历:未锁定->偏向锁 -> 轻量级锁 -> 重量级锁 这四个状态
锁的升级过程
锁升级过程
偏向标记第一次有用,出现过争用后就没用了。 -XX: -UseBiasedLocking 禁用使用偏置锁定,
偏向锁,本质就是无锁,如果没有发生过任何多线程争抢锁的情况, JVM认为就是单线程,无需做同步
(jvm为了少干活:同步在JVM底层是有很多操作来实现的,如果是没有争用,就不需要去做同步操作)
死锁 当两个线程循环依赖一对同步对象时,会产生多任务处理的特殊类型错误死锁。例如,假设一个线程进入对象X的监视器,另一个线程进入对象Y的监视器。如果X中的线程试图调用对象Y的任何同步方法,那么会如我们所期望的被阻塞。但是,如果对象Y中的线程也试图调用对象A的任何同步方法,那么会永远等待下去,因为为了进入X,必须释放对Y加的锁,这样第一个线程才能完成。而且,在编程中,死锁是很很难调试的错误:
死锁通常很少发生,只有当两个线程恰好以这种方式获取CPU时钟周期时才发生死锁。
死锁可能涉及更多的线程以及更多的同步对象(也就是说,死锁可能是通过更复杂的事件序列发生的,而不是通过刚才描述的情况发生的)。
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 class A { synchronized void foo (B b) { String name = Thread.currentThread().getName(); System.out.println(name + " enter A.foo" ); try { Thread.sleep(1000 ); } catch (Exception e) { System.out.println("A Interrupted" ); } System.out.println(name + " trying to cal B.last()" ); b.last(); } synchronized void last () { System.out.println("Inside A.last" ); } } class B { synchronized void bar (A a) { String name = Thread.currentThread().getName(); System.out.println(name + " enter B.foo" ); try { Thread.sleep(1000 ); } catch (Exception e) { System.out.println("B Interrupted" ); } System.out.println(name + " trying to cal A.last()" ); a.last(); } synchronized void last () { System.out.println("Inside B.last" ); } } class Deadlock implements Runnable { A a = new A(); B b = new B(); Deadlock() { Thread.currentThread().setName("MainThread" ); Thread t = new Thread(this , "RacingThread" ); t.start(); a.foo(b); System.out.println("Back in main thread" ); } public void run () { b.bar(a); System.out.println("Bck in other thread" ); } } public class LearnThreadDeadLock { public static void main (String args[]) { new Deadlock(); } }
AbstractOwnableSynchronizer juc包的几乎靠这个实现。
AbstractQueuedLongSynchronizer AbstractOwnableSynchronizer 的子类,所有的同步状态都是用 long 变量来维护的,而不是 int,在需要 64 位的属性来表示状态的时候会很有用
AbstractQueuedSynchronizer 为实现依赖于先进先出队列的阻塞锁和相关同步器(信号量、事件等等)提供的一个框架,它依靠 int 值来表示状态
Lock 所有锁都实现了Lock接口。一般而言,为了申请锁,可以调用lock()方法。如果锁不可得,lock()方法会等待。为了释放锁,可以调用unlock()方法。为了查看锁是否可得,并且如果可得的花,就申请锁,可以调用trylock()方法。如果锁不可得的花,tryLock()方法不会进行等待。相反,如果申请到锁,就返回true;否则返回false。
相比synchronized,lock可以实现更高级的高级功能如:公平锁、中断锁、超时锁、读写锁、共享锁等。使用的时候需手动释放锁unlock,新手使用不当可能造成死锁
方法
描述
void lock()
进行等待,直到可以获得调用锁为止
void lockInterruptibly()
除非被中断,否则进行等待,直到可以获得调用锁为止
Condition newConditio()
返回与调用锁关联的Condition对象
boolean tryLock()
尝试获得锁。如果锁不可得,这个方法不会等待;如果已获得锁,就返回true;如果锁当前整被另一个线程使用,就返回false
boolean tryLock(long wait, TimeUnit tu)
尝试获得锁。如果锁不可得,该方法等待的时间不会超过wait指定的时间长度,时间单位为tu;如果已获得锁,就返回true;如果在指定的时间内无法获得锁,就返回false
void unlock()
释放锁
ReentrantLock ReentrantLock实现了一种可重入锁(reentrant lock),当前持有锁的线程能够重复进入这种锁。当前,对于线程重入锁的这种情况,所有lock()调用必须用相同数量的unlock()调用进行抵消。否则,试图申请锁的线程会被挂起,直到锁不再被使用为止。
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 import java.util.concurrent.locks.ReentrantLock;class Shared { static int count = 0 ; } class LockThread implements Runnable { String name; ReentrantLock lock; LockThread(ReentrantLock lk, String n) { lock = lk; name = n; new Thread(this ).start(); } public void run () { System.out.println("Starting " + name); try { System.out.println(name + " is waiting to lock count" ); lock.lock(); System.out.println(name + " is locking count" ); Shared.count++; System.out.println(name + " : " + Shared.count); System.out.println(name + " is sleeping" ); Thread.sleep(1000 ); } catch (InterruptedException exc) { System.out.println(exc); } finally { System.out.println(name + " is unlocking count" ); lock.unlock(); } } } public class LearnLock1 { public static void main (String args[]) { ReentrantLock lock = new ReentrantLock(); new LockThread(lock, "A" ); new LockThread(lock, "B" ); } }
ReadWriteLock ReadWriteLock维护一对关联锁,一个只用于读操作,一个只用于写操作;读锁可以由多个读线程同时持有,写锁是排他的。 同一时间,两把锁不能被不同线程持有。
适合读取操作多于写入操作的场景,改进互斥锁的性能,比如: 集合的并发线程安全性改造、 缓存组件
锁降级: 指的是写锁降级成为读锁。持有写锁的同时,再获取读锁,随后释放写锁的过程。写锁是线程独占,读锁是共享,所以写->读是降级。 (读->写,是不能实现的)
ReentrantReadWriteLock 可重入读写锁
Condition condition用于替代wait/notify。
Condition是需要与Lock配合使用的, 提供多个等待集合,更精确的控制(底层是park/unpark机制);
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 package learn2;import java.util.ArrayList;import java.util.List;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;class ConditionQueue { Lock lock = new ReentrantLock(); Condition putCondition = lock.newCondition(); Condition takeCondition = lock.newCondition(); private int length; public ConditionQueue (int length) { this .length = length; } List<Object> list = new ArrayList<>(); public void put (Object obj) throws InterruptedException { lock.lock(); for (;;){ if (list.size() < length){ list.add(obj); System.out.println("put:" + obj); takeCondition.signal(); break ; }else { putCondition.await(); } } lock.unlock(); } public Object take () throws InterruptedException { Object obj; lock.lock(); for (;;){ if (list.size() > 0 ){ obj = list.get(0 ); list.remove(0 ); System.out.println("take:" + obj); putCondition.signal(); break ; }else { takeCondition.await(); } } lock.unlock(); return obj; } } public class LearnCondition { public static void main (String args[]) throws InterruptedException { ConditionQueue bb = new ConditionQueue(5 ); new Thread() { @Override public void run () { for (int i = 0 ; i < 20 ; i++) { try { bb.put("x" + i); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); Thread.sleep(3000L ); System.out.println("开始从队列中取元素..." ); for (int i = 0 ; i < 10 ; i++) { bb.take(); Thread.sleep(3000L ); } } }