java-doc-lock

锁是一些对象,它们为使用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();

// 结果
// MainThread enter A.foo
// RacingThread enter B.foo
// RacingThread trying to cal A.last()
// MainThread trying to cal B.last()

}
}

AbstractOwnableSynchronizer

juc包的几乎靠这个实现。

AbstractQueuedLongSynchronizer

AbstractOwnableSynchronizer 的子类,所有的同步状态都是用 long 变量来维护的,而不是 int,在需要 64 位的属性来表示状态的时候会很有用

AbstractQueuedSynchronizer

为实现依赖于先进先出队列的阻塞锁和相关同步器(信号量、事件等等)提供的一个框架,它依靠 int 值来表示状态

Lock

所有锁都实现了Lock接口。一般而言,为了申请锁,可以调用lock()方法。如果锁不可得,lock()方法会等待。为了释放锁,可以调用unlock()方法。为了查看锁是否可得,并且如果可得的花,就申请锁,可以调用trylock()方法。如果锁不可得的花,tryLock()方法不会进行等待。相反,如果申请到锁,就返回true;否则返回false。

相比synchronized,lock可以实现更高级的高级功能如:公平锁、中断锁、超时锁、读写锁、共享锁等。使用的时候需手动释放锁unlock,新手使用不当可能造成死锁

  • Lock接口定义的方法
方法 描述
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;

/**
* 使用condition实现一个阻塞队列,只能存储 n个元素
*/
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<>();

/**
* put时,若队列未满,直接put,若队列满,就阻塞,直到再有空间
* @param obj 要插入的对象
* @throws InterruptedException
*/
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();

}

/**
* take时,若队列中有元素,则获取到元素,若无元素,则等待元素
* @return 队列最前面的元素
* @throws InterruptedException
*/
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);
}
}
}