T1:写两个线程,针对一个List容器,线程1 添加10个元素到容器中,线程2 实现监控元素个数,当到达5个时,线程2给出提示并结束
错误的示范:使用volatile
import java.util.ArrayList;
import java.util.List;
/**
* 使用volatile.
* 这是一个错误的解法,volatile只能保证基本类型和对象的引用的可见性,对象内部属性发生变化时,无法保证可见性
* 所以线程二的退出不能依据 size == 5 来执行,所以此题是一个线程通讯问题
*
* @author yangsx
* @version 1.0
* @date 2019/11/15
*/
public class ANS1_Volatile {
private static volatile List<Object> myList = new ArrayList<>();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
myList.add(i);
System.out.println("线程1添加了一个元素: " + i);
}
});
Thread t2 = new Thread(() -> {
while (true) {
if (myList.size() == 5) {
System.out.println("线程2准备退出: " + myList.size());
break;
}
}
});
t1.start();
t2.start();
}
}
使用wait和notify
import java.util.ArrayList;
import java.util.List;
/**
* 使用wait和notify
* 注意:wait会释放锁,notify不会释放锁,线程结束会自定释放锁
*
* @author yangsx
* @version 1.0
* @date 2019/11/15
*/
public class ANS2_WaitNotify {
private static List<Object> myList = new ArrayList<>();
private static final Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
myList.add(i);
System.out.println("线程1添加了一个元素: " + i);
if (i == 4) {
lock.notify(); // 通知t2元素已经5个
lockWait(); // 不释放锁t2无法执行
}
}
}
});
Thread t2 = new Thread(() -> {
while (true) {
synchronized (lock) {
if (myList.size() < 5) { // 如果元素不足5个,等待并释放锁,让t1继续增加
lockWait();
} else if (myList.size() == 5) { // 如果已经五个,显示退出消息,并通知t1继续执行,然后退出线程(自动释放锁)
System.out.println("线程2准备退出: " + myList.size());
lock.notify();
break;
}
}
}
});
t1.start();
t2.start();
}
private static void lockWait() {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用LockSupport.park()
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;
/**
* 使用LockSupport, 最简单的方式
*
* @author yangsx
* @version 1.0
* @date 2019/11/15
*/
public class ANS3_LockSupportPark {
private static List<Object> myList = new ArrayList<>();
private static Thread t1, t2 = null;
public static void main(String[] args) {
t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
myList.add(i);
System.out.println("线程1添加了一个元素: " + i);
if (myList.size() == 5) {
LockSupport.unpark(t2); // 通知t2已经5个了
LockSupport.park(); // t1暂停执行,等待t2打印输出完毕后再次执行
}
}
});
t2 = new Thread(() -> {
LockSupport.park(); // 一开始就park住,等待t1 unpark
System.out.println("线程2准备退出: " + myList.size());
LockSupport.unpark(t1);
});
t2.start(); // 保证t2优先执行
t1.start();
}
}
使用CountDownLatch
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 使用门闩: 保证不了打印的顺序
*
* @author yangsx
* @version 1.0
* @date 2019/11/15
*/
public class ANS4_CountDownLatch {
private static List<Object> myList = new ArrayList<>();
private static volatile int size = myList.size(); // 用来获取准确的list size
public static void main(String[] args) {
CountDownLatch cdl = new CountDownLatch(5);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
myList.add(i);
System.out.println("线程1添加了一个元素: " + i);
size = myList.size();
cdl.countDown();
}
});
Thread t2 = new Thread(() -> {
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2准备退出: " + size);
});
t1.start();
t2.start();
}
}
使用CountDownLatch作为通讯标记
package com.yangsx95.notes.mst.t1;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 使用 cdl 作为通讯标记
*
* @author yangsx
* @version 1.0
* @date 2019/11/17
*/
public class ANS5_CountDownLatch2 {
private static List<Object> myList = new ArrayList<>();
private static CountDownLatch cdl1 = new CountDownLatch(1);
private static CountDownLatch cdl2 = new CountDownLatch(1);
private static volatile int size = myList.size(); // 用来获取准确的list size
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
myList.add(i);
size = myList.size();
System.out.println("线程1添加了一个元素: " + i);
if (myList.size() == 5) {
cdl1.countDown();
cdl2Await();
}
}
});
Thread t2 = new Thread(() -> {
cdl1Await();
System.out.println("线程2准备退出: " + size);
cdl2.countDown();
});
t1.start();
t2.start();
}
private static void cdl1Await() {
try {
cdl1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void cdl2Await() {
try {
cdl2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
T2:用两个线程顺序打印 A1B2…Z26, T1打印 A\~Z , T2 打印 1\~26
使用wait和notify
/**
* 同样是一个线程通讯问题,第一道题是通知一次,而此题是通知多次,并且存在线程先后执行的问题
* @author yangsx
* @version 1.0
* @date 2019/11/17
*/
public class ANS1_WaitNotify {
private static char[] abc = PrepareData.abc();
private static int[] num = PrepareData.num();
private static final Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (char a : abc) {
synchronized (lock) {
System.out.print(a);
lock.notify();
lockWait();
}
}
});
Thread t2 = new Thread(() -> {
for (int i : num) {
synchronized (lock) {
System.out.print(i);
lock.notify();
if (i != 26) { // 如果i不是26则释放锁等待下一次执行,否则直接退出
lockWait();
}
}
}
});
t1.start(); // 保证t1线程先执行
t2.start();
}
private static void lockWait() {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用LockSupport
import java.util.concurrent.locks.LockSupport;
/**
* 使用LockSupport实现
*
* @author yangsx
* @version 1.0
* @date 2019/11/17
*/
public class ANS2_LockSupportPark {
private static char[] abc = PrepareData.abc();
private static int[] num = PrepareData.num();
private static Thread t1, t2 = null;
public static void main(String[] args) {
t1 = new Thread(() -> {
for (char a : abc) {
System.out.print(a);
LockSupport.unpark(t2);
if (a != 'z') {
LockSupport.park();
}
}
});
t2 = new Thread(() -> {
for (int i : num) {
System.out.print(i);
LockSupport.unpark(t1);
if (i != 26) {
LockSupport.park();
}
}
});
t1.start(); // 保证t1线程先执行
t2.start();
}
}
经典的生产者消费者问题:写一个固定容量的同步容器,拥有put、get方法,以及getCount方法,可以支持两个生产者线程以及10个消费者线程的阻塞调用
生产者消费者问题,为了方便测试,我写了个测试类:
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author yangsx
* @version 1.0
* @date 2019/11/17
*/
public class MyContainerTestUtil {
public static void test(MyContainer myContainer) {
AtomicInteger producerCounter = new AtomicInteger();
Runnable prodRunnable = () -> {
for (int i = 0; i < 50; i++) {
String ele = "ELE" + producerCounter.incrementAndGet();
myContainer.put(ele);
System.out.println(Thread.currentThread().getName() + "放入了一个元素: \"" + ele + "\"");
}
};
Runnable consumerRunnable = () -> {
for (int i = 0; i < 10; i++) {
String ele = myContainer.get();
System.out.println(Thread.currentThread().getName() + "取得了一个元素: \"" + ele + "\"");
}
};
// 两个生产者线程
for (int i = 0; i < 2; i++) {
new Thread(prodRunnable, "producer" + i).start();
}
// 十个消费者线程
for (int i = 0; i < 10; i++) {
new Thread(consumerRunnable, "consumer" + i).start();
}
}
}
public interface MyContainer {
void put(String ele);
String get();
int getCount();
}
使用wait和notify
import java.util.LinkedList;
/**
* 使用wait和notify,
* 如果容器中没有足够的元素可以消费,消费者线程将会阻塞
* 如果容器中的元素已经满了,那么生产者线程也会阻塞
*
* @author yangsx
* @version 1.0
* @date 2019/11/17
*/
public class ANS1_WaitNotify implements MyContainer{
private LinkedList<String> list = new LinkedList<>();
private final int MAX_SIZE = 10; // 最多添加十个元素
private int count = 0;
public synchronized void put(String ele) {
while (count >= MAX_SIZE) { // 注意,需要使用while循环,if走完后count不见得小于0
containerWait();
}
list.add(ele);
count++;
containerNotifyAll(); // 注意,使用notifyAll,唤醒单个线程的话可能唤醒生产者,没有唤醒消费者
}
public synchronized String get() {
while (count == 0) {
containerWait();
containerNotifyAll();
}
count--;
return list.remove();
}
public int getCount() {
return count;
}
private void containerWait() {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void containerNotifyAll() {
this.notifyAll();
}
public static void main(String[] args) {
ANS1_WaitNotify container = new ANS1_WaitNotify();
MyContainerTestUtil.test(container);
}
}
使用ReentrantLock和Condition
ReentrantLock 和 Condition.await()、Condition.signal()的关系,类似synchronized和wait、notify之间的关系。
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用ReentrantLock和Condition对象
* 使用wait和notify有个缺陷,就是在生产者生产完后,调用notifyAll方法唤醒时,有可能会唤醒其他生产者线程,此时又会wait一次,
* 这个问题可以使用Condition对象来解决
*
* @author yangsx
* @version 1.0
* @date 2019/11/17
*/
public class ANS2_ReentrantLockCondition implements MyContainer {
private LinkedList<String> list = new LinkedList<>();
private final int MAX_SIZE = 10; // 最多添加十个元素
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
@Override
public void put(String ele) {
try {
lock.lock();
while (count >= MAX_SIZE) {
producer.await(); // 满了就等待
}
count++;
list.add(ele);
consumer.signalAll(); // 然后唤醒消费者线程消费
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
@Override
public String get() {
String ele = null;
try {
lock.lock();
while (count <= 0) {
consumer.await(); // 没有元素了让消费者等着
}
count--;
ele = list.remove();
producer.signalAll(); // 唤醒生产者线程生产
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return ele;
}
@Override
public int getCount() {
return count;
}
public static void main(String[] args) {
ANS2_ReentrantLockCondition container = new ANS2_ReentrantLockCondition();
MyContainerTestUtil.test(container);
}
}