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
38
39
40
41
/**
* 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
44
45
46
47
48
49
50
51
52
/**
* 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
41
42
43
44
45
/**
* 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
39
40
41
42
43
/**
* 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
31
/**
* @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
119
120
121
122
123
124
125
126
127
128
129
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
30
/**
* 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
29
30
/**
* @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
76
77
78
79
80
81
82
83
84
85
86
/**
* 独占锁(写锁) 一次只能被一个线程占用
* 共享锁(读锁) 多个线程可以同时占用
* 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
80
81
82
83
84
85
/**
* 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
38
39
40
41
/**
* 同步队列
* 和其他的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();
}
}