一 交替打印问题
以下实现参考博客:
https://blog.csdn.net/afsvsv/article/details/86521789
https://www.cnblogs.com/ghoster/p/12503082.html
基本思路:
1.输出多次,必须要有while循环或者for循环;
2.交替输出,循环里面必须要有一个能够中止的操作,可以是wait、lock、await;
1.1 ReentrantLock + 共享变量实现
这里由共享变量负责实现交替输出,这里实际上线程并不是真正交替执行,线程1执行unlock后,不一定是线程2抢到锁,有可能是线程1继续执行lock,但是线程1即使再次获取到锁也无法进行输出了,只有某一次线程2抢到锁之后,改变flag的值,线程1才能再执行输出。也就是说:线程不是交替执行的,但是输出语句一定是交替的。这种做法的优势在于res的目标次数可以任意,缺点在于线程可能执行了很多次。
static int res = 0;
static boolean flag = true;
static ReentrantLock lock = new ReentrantLock();
public static void func1() {
while (res < 10) {
lock.lock();
if(flag){
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
flag = !flag;
}
lock.unlock();
}
}
public static void func2() {
while (res < 10) {
lock.lock();
if(!flag){
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
flag = !flag;
}
lock.unlock();
}
}
public static void ReLockTest1() {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
func1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
func2();
}
});
thread1.start();
thread2.start();
}
1.2 ReentrantLock + Condition实现
相比较上面的方法,这里线程是真正交替执行的,总共执行了res次,缺点在于交替次数必须为偶数次,不然最后线程会停在await处;
重点有两个:
1.线程1先await后signal,线程2先signal后await,这样才能让线程交替进行,而且交替的顺序是固定的,必须线程1在前;
2.方法中必须先输出,后await、signal,不然的话会少一次输出,并且线程挂起;
3.主线程中必须先执行线程1,再执行线程2,不然就会出现两个都wait;因此线程1和2直接必须加个sleep;
// 使用Condition对象
static ReentrantLock lock2 = new ReentrantLock();
static Condition condition1 = lock2.newCondition();
static Condition condition2 = lock2.newCondition();
public static void ReLockTest2() {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
func1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
func2();
}
});
thread1.start();
// 主线程必须sleep,不然让thread2先执行的话,程序就被阻塞死了
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
这种实现,没有访问共同的数据,每一个都维护一个计数的变量,同步操作只控制交替;缺点在于:总数必须为偶数,如果为奇数,最后thread1会停在await;
public static void func1(){
try{
lock2.lock();
for(int i=0;i<10;i+=2) {
System.out.println(Thread.currentThread().getName() + " " + i);
// 先await后signal
condition1.await();
condition2.signal();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock2.unlock();
}
}
public static void func2(){
try{
lock2.lock();
for(int i=1;i<10;i+=2) {
System.out.println(Thread.currentThread().getName() + " " + i);
// 先signal后await
condition1.signal();
condition2.await();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock2.unlock();
}
}
这种实现,访问共同的数据;缺点在于:总数必须为偶数,如果为奇数,最后thread1会停在await;
static int res = 0;
public static void func1(){
try{
lock2.lock();
while (res<10){
// 操作必须放在await/signal的前面,不然出错
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
condition1.await();
condition2.signal();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock2.unlock();
}
}
public static void func2(){
try{
lock2.lock();
while (res<10){
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
condition1.signal();
condition2.await();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock2.unlock();
}
}
错误实现:上面输出必须放在await和signal的前面,不然的话就是错的;因为要一次输出,一次await,不然的话,第一次await就浪费了,导致最后缺失一次输出,程序也会被挂起;
错误实现:网上有一些 ReentrantLock + Condition的错误实现;可以这么说吧,只要await、signal顺序不是按照上面哪种方式的,都是错误实现,错误实现也能实现交替输出,但是最终一定会出现线程被await的情况;
以下面这种情况为例:如果res=2,线程1先执行,第一个signal完全没用,啥也没干被挂起;线程2执行,唤醒线程1,啥也没干被挂起;线程1输出0,线程1唤醒线程2,被挂起;线程2输出1,循环退出;最终线程1被挂起了
为啥会这样?首先明确signal和await次数相等时才能没有线程被挂起,这里是两个函数中都把signal放在await前面了,导致先执行的那个线程的signal被浪费了;如果两个函数都把await放在signal前面,那两个线程都会被挂起;所以必须一个singal在前,一个wait在前,而且必须wait在前的线程先执行,这样signal才没有被浪费,而且还必须两个线程运行次数相同,这样await才能和signal配对,不然还是必然会有一个线程被挂起。
public static void func1() throws InterruptedException {
lock2.lock();
while (res < 10) {
condition2.signal();
condition1.await();
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
}
lock2.unlock();
}
public static void func2() throws InterruptedException {
lock2.lock();
while (res < 10) {
condition1.signal();
condition2.await();
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
}
lock2.unlock();
}
1.3 Synchronized + await 实现
完全类似于 ReentrantLock + Condition,一样要注意三点;
还有个特别的点:wait方法必须由锁对象调用,而且要是实例锁对象,不能是Class对象,不能是静态变量。所以这里锁对象不能用Test.class
。
Integer lock = new Integer(0);
static volatile int res = 0;
// wait 不是静态方法,不能用类对象调用,只能用实例对象调用;
public void func1(){
while (res < 2){
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
test4.wait();
test4.notify();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public void func2() {
while (res < 2) {
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
test4.notify();
test4.wait();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public void ReLockTest2() {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
func1();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
func2();
}
});
thread1.start();
// 主线程必须sleep,不然让thread2先执行的话,程序就被阻塞死了
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
偶然发现的一个栈溢出问题
public class Test5 {
// 这样会报栈溢出错误;因为main函数里面执行new Test5()创建对象时,会调用init方法,
// init方法会先执行实例成员变量赋值语句,也就是执行Test5 test5 = new Test5(),
// 这个里面又调用了new Test5()方法,导致又要先调用init方法,死循环了;
// 加个static可以解决
Test5 test5 = new Test5();
public static void main(String[] args) {
Test5 t = new Test5();
}
}
1.4 Synchronized + 共享变量 实现
// 共享变量不需要加volatile
static int res = 0;
static boolean flag = true;
public void func1() {
while (res < 10) {
synchronized (test4) {
if(flag){
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
flag = !flag;
}
}
}
}
public void func2() {
while (res < 10) {
synchronized (test4) {
if(!flag){
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
flag = !flag;
}
}
}
}
1.5 直接用共享变量,不需要同步
ReentrantLock + 共享变量、Synchronized + 共享变量,这两种方式下,锁只是保证了对共享变量的操作是同步的,对于交替真正在起作用的是共享变量,所以可以直接使用共享变量,但是必须保证共享变量的操作是同步的;【这里其实很特别,共享变量加volatile并不能保证原子性,但是这个场景下,不会出现两个线程同时写共享变量】
1.5.1 使用volatile
// 共享变量必须加volatile
static volatile int res = 0;
static volatile boolean flag = true;
public void func1() {
while (res < 10) {
if (flag) {
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
flag = !flag;
}
}
}
public void func2() {
while (res < 10) {
if (!flag) {
System.out.println(Thread.currentThread().getName() + " " + res);
res++;
flag = !flag;
}
}
}
1.5.2 使用原子类
共享变量用原子类也能达到上面的效果,这里有个点:只有res需要是原子变量,flag并不需要
static AtomicInteger res = new AtomicInteger(0);
boolean flag = true;
public void func1() {
while (res.get() < 10) {
if (flag) {
System.out.println(Thread.currentThread().getName() + " " + res);
res.incrementAndGet();
flag = !flag;
}
}
}
public void func2() {
while (res.get() < 10) {
if (!flag) {
System.out.println(Thread.currentThread().getName() + " " + res);
res.incrementAndGet();
flag = !flag;
}
}
}
二 生产者消费者
以下实现参考博客:
https://www.jianshu.com/p/ab013a4d5878
https://www.cnblogs.com/xindoo/p/11426659.html
https://blog.csdn.net/javazejian/article/details/77410889
生产消费者问题,首先要明确场景:
简单的场景:有个池子容量为A,里面货物初始数量为B,生产者线程每次生产一个货物放入池子,消费者每次从池子中消费一个货物,同一时刻,只能由一个生产者或消费者访问池子;当池子容量已满时,生产者不能再放入货物,当池子为空时,消费者不能消费货物。【复杂的场景:读者写者问题,同时可有多个读者,但只能有一个写者】
2.1 ReentrantLock + Condition 实现
下面这个多设置了一个运行次数,如果不设置操作次数可以将外层while (times < maxTimes)
改为while(true)
一直循环,里面也不需要if(times >= maxTimes) return;
// 池子最大容量
static int maxNum = 1;
// 货物当前数量
static int currentNum = 0;
// 当前运行次数
static int times = 0;
// 最大运行次数
static int maxTimes = 4;
// 独占锁,同一时刻只有一个生产者或消费者能够进行操作
static ReentrantLock lock = new ReentrantLock();
// 生产者等待队列:当碰到池子容量已满时,生产者进入等待队列
static Condition producerCon = lock.newCondition();
// 消费者者等待队列:当碰到池子容量已空时,消费者进入等待队列
static Condition consumerCon = lock.newCondition();
// 生产
public static void produce(String name){
try {
lock.lock();
while (times < maxTimes){
while (currentNum == maxNum){
System.out.println("容量已满 停止生产");
producerCon.await();
if(times >= maxTimes) return;
}
currentNum++;
times++;
System.out.println("运行次数:" + times + " " + name + " "+"当前容量"+" "+currentNum);
consumerCon.signal();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
// 消费
public static void consume(String name){
try {
lock.lock();
while (times < maxTimes){
while (currentNum == 0){
System.out.println("容量已空 停止消费");
consumerCon.await();
if(times >= maxTimes) return;
}
currentNum--;
times++;
System.out.println("运行次数:" + times + " " + name +" "+"当前容量"+" "+currentNum);
producerCon.signal();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void test(){
Thread pro1 = new Thread(new Producer("生产者1"));
Thread pro2 = new Thread(new Producer("生产者2"));
Thread con1 = new Thread(new Consumer("消费者1"));
Thread con2 = new Thread(new Consumer("消费者2"));
pro1.start();
pro2.start();
con1.start();
con2.start();
}
static class Producer implements Runnable{
public String name = "";
public Producer(String name){
this.name = name;
}
@Override
public void run() {
produce(name);
}
}
static class Consumer implements Runnable{
public String name = "";
public Consumer(String name){
this.name = name;
}
@Override
public void run() {
consume(name);
}
}
不设置运行次数
// 生产
public static void produce(String name){
try {
lock.lock();
while (true){
while (currentNum == maxNum){
System.out.println("容量已满 停止生产");
producerCon.await();
}
currentNum++;
System.out.println(name + " "+"当前容量"+" "+currentNum);
consumerCon.signal();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
// 消费
public static void consume(String name){
try {
lock.lock();
while (true){
while (currentNum == 0){
System.out.println("容量已空 停止消费");
consumerCon.await();
}
currentNum--;
System.out.println(name +" "+"当前容量"+" "+currentNum);
producerCon.signal();
}
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
2.2 synchronized + wait 实现
// 生产
public static void produce(String name){
try {
while (true){
synchronized (lock){
while (currentNum == maxNum){
System.out.println("容量已满 停止生产");
lock.wait();
}
currentNum++;
System.out.println(name + " "+"当前容量"+" "+currentNum);
lock.notify();
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
// 消费
public static void consume(String name){
try {
while (true){
synchronized (lock){
while (currentNum == 0){
System.out.println("容量已空 停止消费");
lock.wait();
}
currentNum--;
System.out.println(name + " "+"当前容量"+" "+currentNum);
lock.notify();
}
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
2.3 直接调用阻塞队列
注意:put/take方法才是阻塞的
static int maxNum = 5;
static ArrayBlockingQueue<Integer> pool = new ArrayBlockingQueue<>(maxNum);
// 生产
public static void produce(String name) {
while (true) {
boolean succ = false;
try {
pool.put(1);
System.out.println(name + " " + "当前容量" + " " + pool.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 消费
public static void consume(String name) {
while (true) {
try {
Integer succ = pool.take();
System.out.println(name + " " + "当前容量" + " " + pool.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三 AQS共享锁Demo
参考:https://blog.csdn.net/romantic_jie/article/details/100146676
只需要记住阻塞API和唤醒API即可
3.1 CountDownLatch
await阻塞,countDown唤醒;阻塞一个,唤醒达到指定次数才真正唤醒
static CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
func();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
func();
}
});
thread1.start();
thread2.start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程输出");
}
public static void func(){
System.out.println(Thread.currentThread().getName() + ":子线程输出");
countDownLatch.countDown();
}
Thread-0:子线程输出
Thread-1:子线程输出
主线程输出
3.2 CyclicBarrier
await阻塞,await唤醒;阻塞多个,阻塞达到指定次数唤醒全部
static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
func();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
func();
}
});
thread1.start();
thread2.start();
}
public static void func(){
System.out.println(Thread.currentThread().getName() + ":栅栏前输出");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":栅栏后输出");
}
Thread-0:栅栏前输出
Thread-1:栅栏前输出
Thread-1:栅栏后输出
Thread-0:栅栏后输出
3.3 Semaphor
await阻塞,await唤醒;阻塞多个,阻塞达到指定次数唤醒全部
static Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
func();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
func();
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
func();
}
});
Thread thread4 = new Thread(new Runnable() {
@Override
public void run() {
func();
}
});
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
public static void func(){
try {
semaphore.acquire();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " semaphore队列长度: " + semaphore.getQueueLength());
semaphore.release();
}
Thread-0 semaphore队列长度: 0
Thread-1 semaphore队列长度: 0
Thread-3 semaphore队列长度: 1
Thread-2 semaphore队列长度: 0
3.4 Exchanger
static Exchanger<String> exchanger = new Exchanger<>();
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
func();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
func();
}
});
thread1.start();
thread2.start();
}
public static void func(){
String strSend = Thread.currentThread().getName();
String strReceive= "";
try {
strReceive = exchanger.exchange(strSend);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + strReceive);
}
Thread-0:Thread-1
Thread-1:Thread-0