JUC并发编程系列(二)

JUC并发编程系列(二)

6.八锁问题

1.两个sync 锁的对象是方法的调用者,按顺序调用 发短信 打电话

2.发短信休眠4S 依旧一样 按顺序调用

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
/\*\*
\* 8锁,关于锁的8个问题
\* 1.两个sync 锁的对象是方法的调用者,按顺序调用 发短信 打电话
\* 2.发短信休眠4S 依旧一样 按顺序调用
\* 核心: 对象!!!
\*
\* @author 路飞
\* @create 2021/1/18
\*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
phone.sendMes();
}, "A").start();
new Thread(() -> {
phone.call();
}, "B").start();
new Thread(() -> {
phone1.call();
}, "C").start();
}
}
class Phone {
public synchronized void sendMes() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}

3.增加一个普通方法,该方法不会受sync的限制,优先执行

4.两个对象,两个同步方法

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
/\*\*
\* 3.增加一个普通方法 该方法不受同步锁限制 会先执行
\* 4.两个对象 两个同步方法
\*
\* @author 路飞
\* @create 2021/1/18
\*/
public class Test2 {
public static void main(String[] args) {
//两个对象,两把锁,那就按时间先后执行
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(() -> {
phone1.sendMes();
}, "A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone2 {
public synchronized void sendMes() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"发短信");
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName() +"打电话");
}
//一个不加锁的同步方法
public void sayHello() {
System.out.println(Thread.currentThread().getName() + "=====>hello");
}
}

注意:

  • 把普通方法和sync方法放一起执行,由于sync执行需要拿到锁才能执行,但普通方法不受锁的影响,所以优先执行;

  • 两个对象,去操作sync方法,会发现没有进行线程休眠的call()先运行,因为两个对象就是两把锁了,会按时间先后进行了。

5.两个静态sync方法,一个对象去调用

6.两个静态sync方法,两个对象去调用

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
/\*\*
\* 5.增加两个静态方法 一个对象 发短信 打电话
\* 6.两个对象 两个静态的同步方法 发短信 打电话
\*
\* @author 路飞
\* @create 2021/1/18
\*/
public class Test3 {
public static void main(String[] args) {
//两个对象的class类模板只有一个,statis锁的是class
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(() -> {
phone1.sendMes();
}, "A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone3 {
//静态的同步方法 锁的是 class类模板
public static synchronized void sendMes() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}

注意:

  • 需要注意的是static是类的修饰,意是在类加载时就创建了,用static去修设sync方法,此时就不是锁的方法的调用者了,是锁的整个类了,故当一个对象去调用时,按调用次序的先后执行。

  • 类锁与资源对象的多少无关,多个资源对象共享一个类锁

7.一个静态的同步方法,一个普通的同步方法 一个对象 打电话 发短信

8.一个静态的同步方法,一个普通的同步方法 两个个对象 打电话 发短信

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
/\*\*
\* 7.一个静态的同步方法 一个普通的同步方法 一个对象 打电话 发短信
\* 8.一个静态的同步方法 一个普通的同步方法 两个对象 打电话 发短信
\*
\* @author 路飞
\* @create 2021/1/18
\*/
public class Test4 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(() -> {
phone1.sendMes();
}, "A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone4 {
public static synchronized void sendMes() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}

注意:

类锁和对象锁互不干扰,由于sendMes() 是类锁,要等4秒,而call()和它不是一类锁,所以先执行。

总结:

1、当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。

2、Java 中的每一个对象都可以作为锁;普通同步方法锁 this,静态同步方法锁 Class,同步方法块锁括号;

3、只要锁的对象不是同一个,就直接按照线程执行的快慢来决定锁的对象是同一个,就按照线程进入的先后顺序决定

7.集合类不安全

List不安全

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
/\*\*
\* @author 路飞
\* @create 2021/1/18
\*/
//java.util.ConcurrentModificationException
public class ListTest {
public static void main(String[] args) {
//测试并发下的ArraysList 不安全 java.util.ConcurrentModificationException
/\*\*
\* 解决方案:
\* 1.new Vector() 线程安全
\* 2.Collections.synchronizedList(new ArrayList<>());
\* 3.new CopyOnWriteArrayList<>();
\*/
//CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
//多线程调用时,list,读取的时候,固定的,写入覆盖
//再写入的时候避免覆盖,造成数据问题
//读写分离
//CopyOnWriteArray 底层是数组复制 Voctor的add底层是sync 效率较低
List<String> list = new Vector<>();
// List<String> list = Collections.synchronizedList(new ArrayList<>());
// List<Object> list = new CopyOnWriteArrayList<>();
for (int i = 0; i <= 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
list.forEach(System.out::println);
}, String.valueOf(i)).start();
}
}
}

Map不安全

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
/\*\*
\* 测试HashMap
\*
\* @author 路飞
\* @create 2021/1/18
\*/
//java.util.ConcurrentModificationException
public class MapTest {
public static void main(String[] args) {
//map
/\*\*
\* 解决方案
\* 1. Collections.synchronizedMap(new HashMap<>());
\* 2. new ConcurrentHashMap<>();
\*/
//默认等价于什么? new HashMap(16,0.75);
// Map<String, Object> map = new HashMap<>();
// Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
Map<Object, Object> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}).start();
}
}
}

HashMap面试绝对会问底层实现和如何插入重复key,后面会单独写一篇关于HashMap的文章!

Set测试

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
/\*\*
\* 测试HashSet
\*
\* @author 路飞
\* @create 2021/1/18
\*/
//java.util.ConcurrentModificationException
public class SetTest {
public static void main(String[] args) {
//Set<String> set = new HashSet<>();
//HashSet 底层就是HashMap
/\*\*
\* 解决方案:
\* 1.Collections.synchronizedSet(new HashSet<>());
\* 2.new CopyOnWriteArraySet<>();
\*/
// Set<Object> set = Collections.synchronizedSet(new HashSet<>());
Set<Object> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}).start();
}
}
}

hashSet底层是什么?

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
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/\*\*
\* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
\* default initial capacity (16) and load factor (0.75).
\*/
public HashSet() {
//好家伙,底层之间new的HashMap()
map = new HashMap<>();
}
/\*\*
\* Constructs a new set containing the elements in the specified
\* collection. The <tt>HashMap</tt> is created with default load factor
\* (0.75) and an initial capacity sufficient to contain the elements in
\* the specified collection.
\*
\* @param c the collection whose elements are to be placed into this set
\* @throws NullPointerException if the specified collection is null
\*/
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
/\*\*
\* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
\* the specified initial capacity and the specified load factor.
\*
\* @param initialCapacity the initial capacity of the hash map
\* @param loadFactor the load factor of the hash map
\* @throws IllegalArgumentException if the initial capacity is less
\* than zero, or if the load factor is nonpositive
\*/
public HashSet(int initialCapacity, float loadFactor) {
map = new HashMap<>(initialCapacity, loadFactor);
}
/\*\*
\* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
\* the specified initial capacity and default load factor (0.75).
\*
\* @param initialCapacity the initial capacity of the hash table
\* @throws IllegalArgumentException if the initial capacity is less
\* than zero
\*/
public HashSet(int initialCapacity) {
map = new HashMap<>(initialCapacity);
}
/\*\*
\* Constructs a new, empty linked hash set. (This package private
\* constructor is only used by LinkedHashSet.) The backing
\* HashMap instance is a LinkedHashMap with the specified initial
\* capacity and the specified load factor.
\*
\* @param initialCapacity the initial capacity of the hash map
\* @param loadFactor the load factor of the hash map
\* @param dummy ignored (distinguishes this
\* constructor from other int, float constructor.)
\* @throws IllegalArgumentException if the initial capacity is less
\* than zero, or if the load factor is nonpositive
\*/
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
/\*\*
\* Returns an iterator over the elements in this set. The elements
\* are returned in no particular order.
\*
\* @return an Iterator over the elements in this set
\* @see ConcurrentModificationException
\*/
public Iterator<E> iterator() {
return map.keySet().iterator();
}
/\*\*
\* Returns the number of elements in this set (its cardinality).
\*
\* @return the number of elements in this set (its cardinality)
\*/
public int size() {
return map.size();
}
/\*\*
\* Returns <tt>true</tt> if this set contains no elements.
\*
\* @return <tt>true</tt> if this set contains no elements
\*/
public boolean isEmpty() {
return map.isEmpty();
}
/\*\*
\* Returns <tt>true</tt> if this set contains the specified element.
\* More formally, returns <tt>true</tt> if and only if this set
\* contains an element <tt>e</tt> such that
\* <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
\*
\* @param o element whose presence in this set is to be tested
\* @return <tt>true</tt> if this set contains the specified element
\*/
public boolean contains(Object o) {
return map.containsKey(o);
}
/\*\*
\* Adds the specified element to this set if it is not already present.
\* More formally, adds the specified element <tt>e</tt> to this set if
\* this set contains no element <tt>e2</tt> such that
\* <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
\* If this set already contains the element, the call leaves the set
\* unchanged and returns <tt>false</tt>.
\*
\* @param e element to be added to this set
\* @return <tt>true</tt> if this set did not already contain the specified
\* element
\*/
public boolean add(E e) {
//map 和 set的区别 若之前存在的key是不能再加入set的,返回false
//map则会覆盖
return map.put(e, PRESENT)==null;
}

8.常用辅助类

8.1.CountDownLatch

JDK1.8官方文档已经说的很清楚了,CountDownLatch起一个线程同步辅助的作用,类似一个计数器,已经给出示例代码,我们可以写测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/\*\*
\* CountDownLatch 计数器
\*
\* @author 路飞
\* @create 2021/1/18
\*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//有线程任务的时候,可以使用!
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + " Go out"+"当前计数器==>"+countDownLatch.getCount());
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("Close Door");
}
}

注意:

countDownLatch.countDown(); //数量-1

countDownLatch.await(); //等待计数器归零,然后再向下执行

每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续

执行!

8.2.CyclicBarrier

加法计算器

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
/\*\*
\* CyclicBarrier 加法计数器
\*
\* @author 路飞
\* @create 2021/1/19
\*/
public class CyclicbarrierDemo {
public static void main(String[] args) {
/\*\*
\* 集齐7颗龙珠,召唤神龙
\*/
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙成功!");
});
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "拿到" + temp + "龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}

测试发现,当线程预期没有达到计数器的初始值时,都会阻塞,等待下一个线程,直到满足期望才会执行最终的方法。

8.3.Semaphore

信号量

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
/\*\*
\* @author 路飞
\* @create 2021/1/19
\*/
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量:停车位!限流!
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
semaphore.acquire(); //获取
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了车位");
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); //释放
}
}, String.valueOf(i)).start();
}
}
}

结果:

2抢到了车位
3抢到了车位
1抢到了车位
1离开了车位
3离开了车位
4抢到了车位
2离开了车位
6抢到了车位
5抢到了车位
6离开了车位
4离开了车位
5离开了车位

可以看到在同一时间,只有三辆车停着的,与Semaphore初始值相同,符合预期!

semaphore.acquire() 获得,假设如果已经满了,等待直到被释放为止

semaphore.release() 释放,会将当前的信号量释放+1.然后唤醒等待的线程!

作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!

9.读写锁

可以看到读写锁是把lock更细粒度的分化,读可以被多个线程同时读,但写的时候只能一个线程去写

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
/\*\*
\* 独占锁(写锁) 一次只能被一个线程占用
\* 共享锁(读锁) 多个线程可以同时占用
\* ReadWriteLock
\*
\* @author 路飞
\* @create 2021/1/19
\*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
// MyCache myCache = new MyCache();
MyCacheLock myCache = new MyCacheLock();
//测试写
for (int i = 1; i <= 10; i++) {
final int temp = i;
new Thread(() -> {
myCache.write(temp + "", temp + "");
}, String.valueOf(i)).start();
}
//测试读取
for (int i = 1; i < 10; i++) {
final int temp = i;
new Thread(() -> {
myCache.read(temp + "");
}, String.valueOf(i)).start();
}
}
}
/\*\*
\* 自定义缓存---不加锁
\*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
public void write(String key, Object o) {
System.out.println(Thread.currentThread().getName() + "写入Key" + key);
map.put(key, o);
System.out.println(Thread.currentThread().getName() + "写入完成" + key);
}
public void read(String key) {
System.out.println(Thread.currentThread().getName() + "读取key" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取成功" + key);
}
}
/\*\*
\* 自定义缓存-->加入读写锁 ReadWriteLock 比lock 粒度更细
\*/
class MyCacheLock {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void write(String key, Object o) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入Key" + key);
map.put(key, o);
System.out.println(Thread.currentThread().getName() + "写入完成" + key);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void read(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取key" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取成功" + key);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}

这里在写测试类的时候,写了两个自定义缓存类,一个不加锁,一个加锁

不加锁的结果如下:

以发现在线程1写入key1的时候,线程5也在写入key5,然后线程4直接就把key4写入完成了,这很明显不符合多线程的要求,很容易出现线程不安全(key1还没写入完成,后面有插队线程执行写入key1,直接就把key1的值覆盖了)。

加如读写锁的结果:

可以看到,写入是时候只有一个线程,必须等该线程写入完毕后,其它线程才能继续写入,保证了线程安全!

10.阻塞队列

阻塞队列的使用场景:并发处理,线程池

学会使用队列的四组API

方法 抛出异常 有返回值,不抛出异常 阻塞等待 等待超时
添加 add offer put offer
移除 remove poll take poll
检查队首元素 element peek
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
/\*\*
\* ArrayBlockingQueue四组API的使用
\*
\* @author 路飞
\* @create 2021/1/19
\*/
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
test4();
}
/\*\*
\* 抛出异常
\*/
public static void test1() {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//java.lang.IllegalStateException: Queue full
//System.out.println(blockingQueue.add("d"));
System.out.println("----------------------");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//java.util.NoSuchElementException
// System.out.println(blockingQueue.remove());
}
/\*\*
\* 有返回值,不抛出异常
\*/
public static void test2() {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//超出队列容量 返回false
//System.out.println(blockingQueue.offer("c"));
System.out.println("--------------------------");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//队列为空 继续poll 返回null
System.out.println(blockingQueue.poll());
}
/\*\*
\* 阻塞等待
\*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//超出队列容量 阻塞ing
//blockingQueue.put("a");
System.out.println("---------------");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//队列已空,继续出队,阻塞ing
//System.out.println(blockingQueue.take());
}
/\*\*
\* 等待,阻塞(超时等待)
\*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//队列已满,若继续入队,则触发超时等待,2秒后结束程序
// blockingQueue.offer("d",2, TimeUnit.SECONDS);
System.out.println("----------------------");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//队列已空,若继续出队,则触发超时等待,3秒后结束程序
//System.out.println(blockingQueue.poll(3,TimeUnit.SECONDS));
}
}

使用这个很简单,和数据结构的队列大同小异。

SynchronousQueue 同步队列

每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。

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
/\*\*
\* 同步队列
\* 和其他的BlockingQueue不一样,SynchronousQueue不存储元素
\* put一个元素,必须从里面取出来,否则不能put
\*
\* @author 路飞
\* @create 2021/1/19
\*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();
}
}

JUC并发编程系列(二)
https://luffy997.github.io/2021/01/25/JUC并发编程系列(二)/
作者
Luffy997
发布于
2021年1月25日
许可协议