题目1
实现一个容器,提供两个方法:add
、size
。写两个线程,线程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;
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;
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;
public class T02_WithVolatile {
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(); } }
|
使用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;
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){ 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) { 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;
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").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 { 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;
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(); } 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;
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); } }
} }, "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;
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;
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
使用了Semaphore
和join
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;
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);
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(); t2.join(); s.acquire(); 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(); } }
|
题目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;
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
写一个固定容量同步容器,拥有put
和get
方法, 以及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;
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;
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(); } }
}
|
正确运行,使用wait
和signalAll
来精确控制唤醒生产者还是消费者。
源码阅读原则
- 跑不起来不读
- 解决问题就好 - 目的性
- 一条线索到底
- 无关细节略过
- 一般不读静态
- 一般动态读法