0%

Java 多线程与高并发练习

题目1

实现一个容器,提供两个方法:addsize。写两个线程,线程1添加10个元素到容器中,线程2实现监控元素个数,当个数到5个的时候,线程2给出提示并结束。

题解

使用volatile

总结:下面三种方法都存在问题:【当把线程1的睡眠时间去掉,则直接出错】因此,在平时写多线程程序时,尽可能的不要使用volatile关键字,如果必须要使用,也只能用来修饰简单变量,不要修饰引用值。

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
package com.xzt.interviewquestion;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
* @author xzt
* @version 1.0
*/
public class T01_WithoutVolatile {
List lists = new ArrayList();

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
T01_WithoutVolatile t = new T01_WithoutVolatile();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
t.add(new Object());
System.out.println("add " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

}
}, "t1").start();

new Thread(() -> {
while(true) {
if(t.size() == 5) {
break;
}
}
System.out.println("t2 结束");
}, "t2").start();
}
}

错误解法,运行结果为线程1添加10个元素后即结束,但是线程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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.xzt.interviewquestion;

import java.util.ArrayList;
import java.util.List;

/**
* @author xzt
* @version 1.0
*/
public class T02_WithVolatile {
volatile List lists = new ArrayList();

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
T02_WithVolatile t = new T02_WithVolatile();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
t.add(new Object());
System.out.println("add " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

}
}, "t1").start();

new Thread(() -> {
while(true) {
if(t.size() == 5) {
break;
}
}
System.out.println("t2 结束");
}, "t2").start();
}
}

使用volatile关键字,但是程序还是存在bug,当已经加入5个元素,但是线程2还没来得及读的时候,线程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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.xzt.interviewquestion;

import java.util.ArrayList;
import java.util.List;

/**
* @author xzt
* @version 1.0
*/
public class T02_WithVolatile {
// volatile List lists = new ArrayList();
volatile List lists = Collections.synchronizedList(new ArrayList<>());

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
T02_WithVolatile t = new T02_WithVolatile();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
t.add(new Object());
System.out.println("add " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

}
}, "t1").start();

new Thread(() -> {
while(true) {
if(t.size() == 5) {
break;
}
}
System.out.println("t2 结束");
}, "t2").start();
}
}

使用关键字volatile和同步容器共同使用。

使用wait/notify

总结wait将线程陷入睡眠,先执行没有进入睡眠的线程。notify将陷入睡眠的线程唤醒,但是不会释放锁,若自己不睡眠,则需要等待自己执行结束后才会继续执行唤醒的线程。

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
package com.xzt.interviewquestion;

import java.util.ArrayList;
import java.util.List;

/**
* @author xzt
* @version 1.0
*/
public class T03_WaitNotifyLock {
List lists = new ArrayList();

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
T03_WaitNotifyLock t = new T03_WaitNotifyLock();

final Object lock = new Object();

new Thread(() -> {
System.out.println("t2 启动");
synchronized (lock) {
if(t.size() != 5){ // 先启动线程2,当size!=5的时候,线程2等待,先执行线程1
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t2 结束");
}
}, "t2").start();

// 保证先启动的是第二个线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

new Thread(() -> {
System.out.println("t1 启动");
synchronized (lock) {
for (int i = 0; i < 10; i++) {
t.add(new Object());
System.out.println("add " + i);

if (t.size() == 5) { // 当等于5的时候,唤醒线程2
lock.notify();
}

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}, "t1").start();
}
}

仍然错误,原因是notify是只唤醒不释放锁。所以线程2还是等待线程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
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
package com.xzt.interviewquestion;

import java.util.ArrayList;
import java.util.List;

/**
* @author xzt
* @version 1.0
*/
public class T04_NotifyFreeLock {
List lists = new ArrayList();

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
T04_NotifyFreeLock t = new T04_NotifyFreeLock();

final Object lock = new Object();

new Thread(() -> {
System.out.println("t2 启动");
synchronized (lock) {
if(t.size() != 5){
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t2 结束");
lock.notify(); // t2线程结束,唤醒t1线程,否则t1线程永久睡眠
}
}, "t2").start();

// 保证先启动的是第二个线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

new Thread(() -> {
System.out.println("t1 启动");
synchronized (lock) {
for (int i = 0; i < 10; i++) {
t.add(new Object());
System.out.println("add " + i);

if (t.size() == 5) {
lock.notify();

try { // 唤醒线程2后,将自己进入睡眠(释放锁),等待线程2结束后继续执行。
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}, "t1").start();
}
}

正确结果。当线程1唤醒线程2后,讲自己wait释放锁,然后线程2继续执行,执行结束后,必须再次唤醒线程1,否则线程1永远睡眠

使用CountDownLatch

总结:先启动线程2,当size不等于5时,插上门闩await,等待门闩变为0时继续往后继续运行,启动线程2,当size等于5时,门闩值减1。

但是严格来讲,存在问题,当把线程1等待1s才输出一个的等待时间去掉后,t2线程结束则会推迟,不是在size等于5的时候就结束了。因此如果需要严格实现等于5时线程2结束,然后才继续运行线程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
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
package com.xzt.interviewquestion;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
* @author xzt
* @version 1.0
*/
public class T05_CountDownLatch {

List lists = new ArrayList();


public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
T04_NotifyFreeLock t = new T04_NotifyFreeLock();

CountDownLatch latch = new CountDownLatch(1);

new Thread(() -> {
System.out.println("t2 启动");
if (t.size() != 5) {
try {
latch.await(); // 插上门闩,等待门闩值减为1
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t2 结束");
}, "t2").start();

// 保证先启动的是第二个线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

new Thread(() -> {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
t.add(new Object());
System.out.println("add " + i);

if (t.size() == 5) {
latch.countDown(); // 门闩值减一
}

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "t1").start();
}
}

正确运行,但是严格来讲,存在问题,当把线程1等待1s才输出一个的等待时间去掉后,t2线程结束则会推迟,不是在size等于5的时候就结束了。

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
package com.xzt.interviewquestion;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
* @author xzt
* @version 1.0
*/
public class T05_CountDownLatch {

List lists = new ArrayList();


public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
T04_NotifyFreeLock t = new T04_NotifyFreeLock();

CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch1 = new CountDownLatch(1);

new Thread(() -> {
System.out.println("t2 启动");
if (t.size() != 5) {
try {
latch.await(); // 等待
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t2 结束");
latch1.countDown();
}, "t2").start();

// 保证先启动的是第二个线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

new Thread(() -> {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
t.add(new Object());
System.out.println("add " + i);

if (t.size() == 5) {
latch.countDown(); // 门闩值减一
try {
latch1.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
}
}, "t1").start();
}
}

完美解决。

使用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
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
package com.xzt.interviewquestion;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

/**
* @author xzt
* @version 1.0
*/
public class T06_LockSupport {
List lists = new ArrayList();

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

public static void main(String[] args) {
T06_LockSupport t = new T06_LockSupport();


Thread t2 = new Thread(() -> {
System.out.println("t2 启动");
if (t.size() != 5) {
LockSupport.park();
}
System.out.println("t2 结束");
}, "t2");
t2.start();

// 保证先启动的是第二个线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

Thread t1 = new Thread(() -> {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
t.add(new Object());
System.out.println("add " + i);

if (t.size() == 5) {
LockSupport.unpark(t2);
}

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "t1");
t1.start();
}
}

正确运行,但是严格来讲,存在问题,当把线程1等待1s才输出一个的等待时间去掉后,t2线程结束则会推迟,不是在size等于5的时候就结束了。

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
package com.xzt.interviewquestion;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;

/**
* @author xzt
* @version 1.0
*/
public class T07_LockSupport_WithoutSleep {
List lists = new ArrayList();

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

static Thread t1 = null, t2 = null; // 必须先声明

public static void main(String[] args) {
T07_LockSupport_WithoutSleep t = new T07_LockSupport_WithoutSleep();



t2 = new Thread(() -> {
System.out.println("t2 启动");

LockSupport.park();

System.out.println("t2 结束");
LockSupport.unpark(t1);
}, "t2");

t1 = new Thread(() -> {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
t.add(new Object());
System.out.println("add " + i);

if (t.size() == 5) {
LockSupport.unpark(t2);
LockSupport.park();
}
}
}, "t1");


t2.start();
// 保证先启动的是第二个线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t1.start();
}
}

完美解决。

使用Semaphore

使用了Semaphorejoin

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
package com.xzt.interviewquestion;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.LockSupport;

/**
* @author xzt
* @version 1.0
*/
public class T08_Semaphore {
List lists = new ArrayList();

public void add(Object o) {
lists.add(o);
}

public int size() {
return lists.size();
}

static Thread t1 = null, t2 = null;

public static void main(String[] args) {
T08_Semaphore t = new T08_Semaphore();

Semaphore s = new Semaphore(1); // 保证只有1个线程可以同时运行

t1 = new Thread(() -> {
try {
s.acquire();
for (int i = 0; i < 5; i++) {
t.add(new Object());
System.out.println("add " + i);
}
s.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
t2.start(); // t1 线程输出5个后启动t2线程,
t2.join(); // 让t2线程执行结束
s.acquire(); // t1线程继续获得信号量。
for (int i = 5; i < 10; i++) {
t.add(new Object());
System.out.println("add " + i);
}
s.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t1");

t2 = new Thread(() -> {
try {
s.acquire();
System.out.println("t2 结束");
s.release();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}, "t2");

t1.start();
}
}

正确运行。在t1线程里面启动t2线程

题目2

有两个字符串:"ABCDEFG....";"1234567....";,要求使用多线程顺序打印出A1B2C3D4....

题解

使用Synchronized

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
package com.xzt.interviewquestion02;

/**
* @author xzt
* @version 1.0
*/
public class T01_Synchronized {
private char[] aC = "ABCDEFG".toCharArray();
private char[] aI = "1234567".toCharArray();

public static void main(String[] args) {
T01_Synchronized t = new T01_Synchronized();
final Object lock = new Object();

new Thread(() -> {
synchronized (lock) {
for(char c : t.aC) {
System.out.print(c);
try {
lock.notify(); // 先叫醒对方,再睡
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
lock.notify();
}
},"t1").start();

new Thread(() -> {
synchronized (lock) {
for(char c : t.aI) {

System.out.print(c);
try {
lock.notify();
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
lock.notify();
}
},"t2").start();
}
}

题目3

写一个固定容量同步容器,拥有putget方法, 以及getCount方法。能够支持2个生产者线程以及10个消费之线程的阻塞调用。

题解

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
package com.xzt.interviewquestion02;

import java.util.LinkedList;
import java.util.List;

/**
* @author xzt
* @version 1.0
*/
public class MyContainer1<T> {
final private LinkedList<T> lists = new LinkedList<>();
final private int MAX = 10;
private int count = 0;

public synchronized void put(T t) {
while (lists.size() == MAX) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

lists.add(t);
count ++;
this.notifyAll(); // 通知消费者进行消费
}

public synchronized T get() {
T t = null;
while (lists.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}

t = lists.removeFirst();
count --;
this.notifyAll(); // 通知生产者进行生产
return t;
}

public static void main(String[] args) {
MyContainer1<String> c = new MyContainer1<>();

for (int i = 0; i < 10; i++) {
new Thread(()-> {
for (int j = 0; j < 5; j++) {
System.out.println(c.get());
}
}, "c" + i).start();
}

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

for (int i = 0; i < 2; i++) {
new Thread(()->{
for (int j = 0; j < 25; j++) {
c.put(Thread.currentThread().getName() + " " + j);
}
}, "p" + i).start();
}
}

}

正确运行,

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
package com.xzt.interviewquestion02;

import java.util.LinkedList;
import java.util.Locale;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* @author xzt
* @version 1.0
*/
public class MyContainer2<T> {
final private LinkedList<T> lists = new LinkedList<>();
final private int MAX = 10;
private int count = 0;

private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();

public void put(T t) {
lock.lock();
try {
while (lists.size() == MAX) {
producer.wait(); // 生产者线程等待
}

lists.add(t);
count++;
consumer.signalAll(); // 通知消费者进行消费
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}

public synchronized T get() {
T t = null;
lock.lock();
try {
while (lists.size() == 0) {
consumer.wait();
}
t = lists.removeFirst();
count--;
producer.signalAll(); // 通知生产者进行生产
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return t;
}

public static void main(String[] args) {
MyContainer2<String> c = new MyContainer2<>();

for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
System.out.println(c.get());
}
}, "c" + i).start();
}

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 25; j++) {
c.put(Thread.currentThread().getName() + " " + j);
}
}, "p" + i).start();
}
}

}

正确运行,使用waitsignalAll来精确控制唤醒生产者还是消费者。

源码阅读原则

  • 跑不起来不读
  • 解决问题就好 - 目的性
  • 一条线索到底
  • 无关细节略过
  • 一般不读静态
  • 一般动态读法
正在加载今日诗词....