21Java多线程之线程安全类

Java中与线程安全有关的类:安全的集合类、java.util.concurrent JUC包下类 等。

集合类

线程安全(Thread-safe)的集合对象:

  • Vector:单个操作是原子性的。但是如果两个原子操作复合而来,这个组合的方法是非线程安全的,需要使用锁来保证线程安全。
  • HashTable
  • CopyOnWriteArrayList:JUC包

如何把一个线程不安全的集合类变成一个线程安全的集合类?

1
2
3
4
5
6
7
public class ThreadDemo {
public static void main(String[] args) {
// Vector是线程安全的时候才去考虑使用的,但即使要安全,也不用
List<String> list1 = new ArrayList<String>();// 线程不安全
List<String> list2 = Collections.synchronizedList(list1); // 线程安全
}
}

字符串类

StringBuffer

原子类

CAS

包含三个操作数:当前内存值、预期原值、新值,如果内存值和预期原值匹配,就将内存值更改为新值;否则什么也不做。

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
public class Test {
public static volatile int i = 0;

public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
int expectedValue = i;
System.out.println("期望值: " + expectedValue);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// CAS操作
boolean result = compareAndSwap(i, expectedValue, i + 1);
System.out.println("设置是否成功: " + result);
System.out.println("新值: " + i);
}
};
thread.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
i = 5;
}

public static boolean compareAndSwap(int memoryValue, int expectedValue, int newValue) {
// 当预期原值等于内存值时
if (expectedValue == memoryValue) {
i = memoryValue;
return true;
}
return false;
}
}

ABA问题

假如一个值原来是A,现在变成了B,后来又变成了A,那么CAS检查时会发现它的值没有发生变化,但是实际上却变化了。

AtomicMarkableReference 是一个带修改标记的引用类型原子类。

AtomicStampedReference 是一个带版本号的引用类型原子类。

Lock接口

JUC.locks包

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Lock {
// 这四个方法都可以获取锁
void lock();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void lockInterruptibly() throws InterruptedException;

// 释放锁
void unlock();

// 用于等待通知(线程间通信)
Condition newCondition();
}

ReentrantLock

java.util.concurrent.locks.ReentrantLock

这个是 JDK @since 1.5 添加的一种颗粒度更小的锁,它完全可以替代 synchronized 关键字来实现它的所有功能,而且 ReentrantLock 锁的灵活度要远远大于 synchronized 关键字,它更清晰的表达了如何加锁释放锁

image-20210714185046829

lock() 阻塞

获取锁,平常使用最多的一个方法。

如果其他线程没有持有锁,则获取该锁并立即返回,将锁持有计数设置为 1。

如果当前线程已经持有锁,那么持有计数加一并且该方法立即返回。

如果该锁被另一个线程持有,那么当前线程将因线程调度目的而被禁用并处于休眠状态(等待获取锁),直到获得该锁为止,此时锁持有计数设置为 1。

注意:

  • 必须主动去释放锁。
  • 发生异常时,不会自动释放锁。所以使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
1
2
3
4
5
6
7
8
9
10
11
12
private final Lock lock = new XxxLock();

public void doSomething() {
lock.lock(); // 获取锁,阻塞
try {
// ... 同步代码
} catch (Exception e) {

} finally {
lock.unlock(); // 释放锁
}
}

示例1:

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
public class Test {
public static void main(String[] args) {
Task task = new Task();
Thread thread0 = new Thread(task);
Thread thread1 = new Thread(task);
thread0.start();
thread1.start();
}
}

class Task implements Runnable {
private Lock lock = new ReentrantLock();

@Override
public void run() {
lock.lock(); // 获取锁,阻塞
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
}
}
}

示例2:

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
class SellTicket implements Runnable {
// 定义票
private int tickets = 1;
// 定义锁对象
private Lock lock = new ReentrantLock();

@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
lock.lock(); // 获取锁,阻塞
try {
if (tickets <= 100) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets++) + "张票");
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();// 释放锁
}
}
}
}

tryLock() 非阻塞

tryLock() 有返回值,它表示用来尝试获取锁。

如果其他线程没有持有锁,则获取该锁并立即返回true值,将锁持有计数设置为 1。即使此锁已设置为使用公平排序策略,调用tryLock()将立即获取可用的锁,无论其他线程当前是否正在等待该锁。这种“闯入”行为在某些情况下很有用,即使它破坏了公平。 如果你想尊重这个锁的公平性设置,那么使用 tryLock(0, TimeUnit.SECONDS) 这几乎是等效的(它也检测中断)。

如果当前线程已持有此锁,则持有计数将增加 1 并且该方法返回 true。

如果锁被另一个线程持有,那么这个方法将立即返回 false 值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private final Lock lock = new XxxLock();

public void doSomething() {
if (lock.tryLock()) { // 获取锁,非阻塞
try {
// ... 同步代码
} catch (Exception e) {

} finally {
lock.unlock(); // 释放锁
}
} else {
System.out.println("锁被占用,执行其他任务。");
}
}

tryLock(time,unit) 定时阻塞,可中断

如果在给定的等待时间内成功获取锁(没有被另一个线程持有),且当前线程没有被中断,则返回true,将锁持有计数设置为 1。

由于该方法的声明中抛出了异常,所以它必须放在try块中或者在调用的方法上抛出 InterruptedException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private final Lock lock = new XxxLock();

public void doSomething() throws InterruptedException {
try {
if (lock.tryLock(1000, TimeUnit.SECONDS)) { // 定时获取锁,定时阻塞,可中断
try {
// ... 同步代码
} catch (Exception e) {

} finally {
lock.unlock(); // 释放锁
}
} else {
System.out.println("锁被占用,执行其他任务。");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 获取锁时被中断");
}
}

示例:

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
public class Test {
public static void main(String[] args) {
Task task = new Task();
Thread thread0 = new Thread(task);
Thread thread1 = new Thread(task);
thread0.start();
thread1.start();

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 中断线程
thread1.interrupt();
}
}

class Task implements Runnable {
private Lock lock = new ReentrantLock();

@Override
public void run() {
try {
if (lock.tryLock(1000, TimeUnit.SECONDS)) { // 定时获取锁,定时阻塞,可中断
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 执行完成");
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " sleep时被中断");
} finally {
lock.unlock(); // 释放锁
}
} else {
System.out.println("锁被占用,执行其他任务。");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 获取锁时被中断");
}
}
}

lockInterruptibly() 阻塞,可中断

这个方法比较特殊,当通过该方法获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时使用 lock.lockInterruptibly() 获取某个锁时,假若线程A获取到了锁,线程B在等待,那么对线程B调用 threadB.interrupt() 方法能够中断线程B的等待过程。

由于该方法的声明中抛出了异常,所以它必须放在try块中或者在调用的方法上抛出 InterruptedException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private final Lock lock = new XxxLock();

public void doSomething() {
try {
lock.lockInterruptibly(); // 获取锁,阻塞,可中断
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 获取锁时被中断");
return;
}

try {
// ... 同步代码
} catch (Exception e) {

} finally {
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
public class Test {
public static void main(String[] args) {
Task task = new Task();
Thread thread0 = new Thread(task);
Thread thread1 = new Thread(task);
thread0.start();
thread1.start();

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 中断线程
thread1.interrupt();
}
}

class Task implements Runnable {
private Lock lock = new ReentrantLock();

@Override
public void run() {
try {
lock.lockInterruptibly(); // 获取锁,阻塞,可中断
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 获取锁时被中断");
return;
}

try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
}
}
}

其他特有方法

1
2
3
4
public final boolean isFair() // 判断锁是否是公平锁
public boolean isLocked() // 查询此锁是否被任何线程持有。此方法设计用于监视系统状态,而不是用于同步控制。
public boolean isHeldByCurrentThread() // 查询当前线程是否持有此锁。
public final boolean hasQueuedThreads() // 查询是否有线程正在等待获取此锁。请注意,取消可能随时发生,如果可能有其他线程等待获取锁,则返回true。该方法主要设计用于监视系统状态。

ReentrantReadWriteLock

image-20210714204748943

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;

public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }

public static class ReadLock implements Lock, java.io.Serializable {
// ...
}
public static class WriteLock implements Lock, java.io.Serializable {
// ...
}
}

ReentrantReadWriteLock 实现了 ReadWriteLock 接口(非 Lock 接口),调用该接口的 readLock()writeLock() 方法可分别获取读和写的 Lock 锁。

ReentrantReadWriteLock 还提供了丰富的用于监视系统状态的方法。

为什么要分两把锁?

因为读操作的锁,要支持被多个线程获取(多线程同时读),这样可以提升读操作的效率。而写操作的锁,只能被一个线程获取。

读锁被占用时,无法申请写锁,但能申请读锁。

写锁被占用时,无法申请写锁,无法申请读锁。

读读的过程是共享的,读写、写读 、写写 的过程是互斥的。

ReentrantReadWriteLock读写锁详解

读写锁实战高并发容器

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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
public class Test {
public static void main(String[] args) {
// 创建高并发容器
ReadWriteDictionary dictionary = new ReadWriteDictionary();
// 创建读、写任务
WriteTask writeTask = new WriteTask(dictionary);
ReadTask readTask = new ReadTask(dictionary);
// 创建读、写线程
Thread writeThread0 = new Thread(writeTask);
Thread writeThread1 = new Thread(writeTask);
Thread writeThread2 = new Thread(writeTask);
Thread readThread3 = new Thread(readTask);
Thread readThread4 = new Thread(readTask);
Thread readThread5 = new Thread(readTask);
// 启动线程
writeThread0.start();
writeThread1.start();
writeThread2.start();
readThread3.start();
readThread4.start();
readThread5.start();
}
}

/**
* 从容器读取数据的任务
*/
class ReadTask implements Runnable {
private ReadWriteDictionary dictionary;

public ReadTask(ReadWriteDictionary dictionary) {
this.dictionary = dictionary;
}

@Override
public void run() {
// 无限读取
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取所有key
String[] keys = dictionary.allKeys();
// 遍历所有的key
for (String key : keys) {
Object value = dictionary.get(key);
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "读取 >>> " + key + ": " + value);
}
}
}
}

/**
* 向容器写入数据的任务
*/
class WriteTask implements Runnable {
private ReadWriteDictionary dictionary;

public WriteTask(ReadWriteDictionary dictionary) {
this.dictionary = dictionary;
}

@Override
public void run() {
// 计数器
int i = 0;
// 无限写入
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object value = Thread.currentThread().getName() + "写入" + i++;
// 存储数据
dictionary.put("固定key", value);
}
}
}

/**
* 高并发容器
*/
class ReadWriteDictionary {
/**
* key-value容器
*/
private final Map<String, Object> map = new HashMap<>();

/**
* 读写锁
*/
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

/**
* 读锁
*/
private final Lock readLock = readWriteLock.readLock();

/**
* 写锁
*/
private final Lock writeLock = readWriteLock.writeLock();

public Object get(String key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}

/**
* 返回所有键
*/
public String[] allKeys() {
readLock.lock();
try {
return map.keySet().toArray(new String[0]);
} finally {
readLock.unlock();
}
}

public Object put(String key, Object value) {
writeLock.lock();
try {
return map.put(key, value);
} finally {
writeLock.unlock();
}
}

public void clear() {
writeLock.lock();
try {
map.clear();
} finally {
writeLock.unlock();
}
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Thread-3读取 >>> 固定key: Thread-0写入1
Thread-5读取 >>> 固定key: Thread-0写入1
Thread-4读取 >>> 固定key: Thread-0写入1
Thread-3读取 >>> 固定key: Thread-1写入2
Thread-5读取 >>> 固定key: Thread-1写入2
Thread-4读取 >>> 固定key: Thread-1写入2
Thread-3读取 >>> 固定key: Thread-2写入3
Thread-4读取 >>> 固定key: Thread-2写入3
Thread-5读取 >>> 固定key: Thread-2写入3
Thread-3读取 >>> 固定key: Thread-1写入4
Thread-4读取 >>> 固定key: Thread-1写入4
Thread-5读取 >>> 固定key: Thread-1写入4
Thread-3读取 >>> 固定key: Thread-1写入5
Thread-4读取 >>> 固定key: Thread-1写入5
Thread-5读取 >>> 固定key: Thread-1写入5
Thread-3读取 >>> 固定key: Thread-1写入6
Thread-4读取 >>> 固定key: Thread-1写入6
Thread-5读取 >>> 固定key: Thread-1写入6
Thread-3读取 >>> 固定key: Thread-0写入7
Thread-5读取 >>> 固定key: Thread-2写入7
Thread-4读取 >>> 固定key: Thread-2写入7

可重入性

读锁 写锁
读锁 可重入 不可重入
写锁 不可重入 不可重入

和 StampedLock 相同。验证读写不可重入:

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
public class Test {
public static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public static final Lock readLock = readWriteLock.readLock();
public static final Lock writeLock = readWriteLock.writeLock();

public static void main(String[] args) {
printA();
}

private static void printA() {
readLock.lock();
try {
System.out.println("printA");
printB();
} finally {
readLock.unlock();
}
}

private static void printB() {
writeLock.lock();
try {
System.out.println("printB");
} finally {
writeLock.unlock();
}
}
}

LockSupport工具类

实际开发中用的不多。可以用 LockSupport实现互斥锁(等待唤醒机制)

方法介绍:

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
public class LockSupport {
private LockSupport() {} // 无法实例化

/*********** 无限等待 ************/
// 使当前线程等待,直到唤醒或中断
public static void park();
// 使当前线程等待,并指定同步锁,直到唤醒或中断
public static void park(Object blocker);

/*********** 定时等待***********/
// 使当前线程等待,并指定等待时间,直到唤醒或中断或超时
public static void parkNanos(long nanos);
// 使当前线程等待,并指定同步锁和等待时间,直到唤醒或中断或超时
public static void parkNanos(Object blocker, long nanos);
// 使当前线程等待,并指定截止日期,直到唤醒或超时
public static void parkUntil(long deadline);
// 使当前线程等待,并指定同步锁和截止日期,直到唤醒或中断或超时
public static void parkUntil(Object blocker, long deadline);

// 唤醒指定线程
public static void unpark(Thread thread);

// 获取指定线程的同步锁
public static Object getBlocker(Thread t);
}

示例

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
public class Test {
public static void main(String[] args) {
Task task = new Task();
Thread thread = new Thread(task);
thread.start();

try {
thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
LockSupport.unpark(thread);
}
}


/**
* 从容器读取数据的任务
*/
class Task implements Runnable {
@Override
public void run() {
System.out.println("等待前");
LockSupport.park();
System.out.println("等待后");
}
}

StampedLock邮戳锁

线程饥饿

读写锁存在线程饥饿的问题。饥饿指的是某一线程或多个线程因为某些原因一直获取不到资源,导致程序一直无法执行。场景举例:

1、某一线程因优先级太低导致一直分配不到资源。

2、某一线程一直占着某种资源不放,导致其他线程长期无法执行。如:读(写)操作长时间占用读(写)锁,导致写(读)操作一直等待。

如何解决线程饥饿问题:

1、与死锁相比,饥饿现象有可能在一段时间后恢复执行。可以设置合适的线程优先级,来尽量避免饥饿的产生。

2、使用读写锁的升级版 StampedLock 来解决线程饥饿。

和ReadWriteLock相比

ReadWriteLock可以解决多线程同时读,但同时只能有一个线程写的问题。潜在的问题:如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,即读的过程中不允许写,这是一种悲观的读锁。

要进一步提升并发执行效率,Java 8引入了新的读写锁:StampedLock

StampedLockReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入!这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入,这种读锁是一种乐观锁。

乐观锁的意思就是乐观地估计读的过程中大概率不会有写入,因此被称为乐观锁。反过来,悲观锁则是读的过程中拒绝有写入,也就是写入必须等待。显然乐观锁的并发效率更高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行。

常用方法

方法 描述
public StampedLock() 只有一个无参构造
public long readLock() 获取读锁,返回锁标识
public long writeLock() 获取写锁,返回锁标识
public void unlock(long stamp) 释放读写锁,参数stamp为锁标识
public void unlockRead(long stamp) 释放读锁,参数stamp为锁标识
public void unlockWrite(long stamp) 释放写锁,参数stamp为锁标识
public Lock asReadLock() 转换为读锁
public Lock asWriteLock() 转换为写锁
public ReadWriteLock asReadWriteLock() 转换为读写锁

示例

饥饿问题复现

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
public class Test {
public static void main(String[] args) {
Data data = new Data();
ReadTask readTask = new ReadTask(data);
WriteTask writeTask = new WriteTask(data);
Thread thread0 = new Thread(readTask);
Thread thread1 = new Thread(writeTask);
thread0.start();
thread1.start();
}
}

class ReadTask implements Runnable {
private Data data;

public ReadTask(Data data) {
this.data = data;
}

@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println(data.getMessage());
}
}
}

class WriteTask implements Runnable {
private Data data;

public WriteTask(Data data) {
this.data = data;
}

@Override
public void run() {
// 计数器
int i = 0;
while (!Thread.currentThread().isInterrupted()) {
data.setMessage("当前值: " + i++);
}
}
}

class Data {
private String message;
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();

public String getMessage() {
readLock.lock();
try {
Thread.sleep(1000);
return this.message;
} catch (InterruptedException e) {
e.printStackTrace();
return null;
} finally {
readLock.unlock();
}
}

public void setMessage(String message) {
writeLock.lock();
try {
Thread.sleep(1000);
this.message = message;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
}

输出结果用于最后的对比

1
2
3
4
5
6
7
8
9
10
11
null
当前值: 26
当前值: 30
当前值: 45
当前值: 63
当前值: 64
当前值: 84
当前值: 92
当前值: 128
当前值: 131
当前值: 134

解决饥饿问题

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
class Data {
private String message;
private final StampedLock stampedLock = new StampedLock();

public String getMessage() {
// 获取读锁标识
long readStamp = stampedLock.readLock();
try {
Thread.sleep(1000);
return this.message;
} catch (InterruptedException e) {
e.printStackTrace();
return null;
} finally {
// 释放读锁
stampedLock.unlockRead(readStamp);
}
}

public void setMessage(String message) {
// 获取写锁标识
long writeStamp = stampedLock.writeLock();
try {
Thread.sleep(1000);
this.message = message;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放写锁
stampedLock.unlockWrite(writeStamp);
}
}
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
null
当前值: 51
当前值: 72
当前值: 136
当前值: 152
当前值: 168
当前值: 182
当前值: 205
当前值: 227
当前值: 230
当前值: 260

对比输出结果

输出10行,发现 StampedLock 输出到260,而 ReentrantReadWriteLock 输出到 134,是否能证明饥饿问题的出现和解决呢???

可重入性

和 ReentrantReadWriteLock 相同。验证读写不可重入:

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
public class Test {
public static StampedLock stampedLock = new StampedLock();

public static void main(String[] args) {
printA();
}

private static void printA() {
long stamp = stampedLock.readLock();
try {
System.out.println("printA");
printB();
} finally {
stampedLock.unlockRead(stamp);
}
}

private static void printB() {
long stamp = stampedLock.writeLock();
try {
System.out.println("printB");
} finally {
stampedLock.unlockWrite(stamp);
}
}
}

ThreadLocal

remove()

即便不调用 remove() 也不会导致内存溢出,只不过垃圾回收器压力大些。设置 JVM 参数 -Xms30m -Xmx30m ,用 VisualVM 观察垃圾回收次数,VisualVM也会占用 JVM 内存,所以不能设置太小。

验证代码如下,观察 VisualVM 中的垃圾回收次数 collections:

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
/**
* JVM参数:-Xms30m -Xmx30m
*/
public class Test {
public static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
// 执行次数
public static AtomicInteger cnt = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {
// 这里休眠10s,待visualVM连接后继续
TimeUnit.SECONDS.sleep(10);

ExecutorService executorService =
new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), new DiscardPolicy());
for (int i = 1000; i > 0; i--) {
executorService.submit(new Task());
TimeUnit.MILLISECONDS.sleep(10);
}

executorService.shutdown();
}
}

class Task implements Runnable {
private int _1MB = 1024 * 1024 * 1;

@Override
public void run() {
Byte[] byt = new Byte[_1MB];
Test.threadLocal.set(byt);
System.out.println(Test.cnt.incrementAndGet());
// Test.threadLocal.remove();
}
}

InheritableThreadLocal

InheritableThreadLocal 可继承的线程本地变量。如线程B被线程A创建,B就可以获取A中的 InheritableThreadLocal。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static final ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

public static void main(String[] args) {
threadLocal.set("Hello World!");
new ThreadB().start();
}
}

class ThreadB extends Thread {
@Override
public void run() {
System.out.println(Test.threadLocal.get());
}
}

TransmittableThreadLocal

https://juejin.cn/post/7214901105977671717

继承自 InheritableThreadLocal。该类由阿里提供。