0、JUC详细详解
1、虚假唤醒
多线程环境下,有多个线程执行了wait()方法,需要其他线程执行notify()或者notifyAll()方法去唤醒它们,假如多个线程都被唤醒了,但是只有其中一部分是有用的唤醒操作,其余的唤醒都是无用功;对于不应该被唤醒的线程而言,便是虚假唤醒。
package sync;
/**
当wait在if语句内时,因为进入前已经判断过条件,所以唤醒时(在哪睡,在哪醒)就不会再判断,继续执行下面语句,既发送虚假唤醒
*/
class Calculator {
private int num = 0;
public synchronized void increment() throws InterruptedException {
//if (num != 0) { //存在虚假唤醒
// this.wait();
//}
while (num != 0) { //每次醒都重新判断,解决虚假唤醒
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "+1,当前值:" + num);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
//if (num != 1) { //存在虚假唤醒
// this.wait();
//}
while (num != 1) { //每次醒都重新判断,解决虚假唤醒
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "-1,当前值:" + num);
this.notifyAll();
}
}
public class Compute {
public static void main(String[] args) {
Calculator calculator = new Calculator();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
calculator.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AA").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
calculator.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BB").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
calculator.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "CC").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
calculator.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "DD").start();
}
}
2、读写锁
保证读写顺序执行。
package readwrite;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class ReadWrite {
private volatile HashMap<String, Object> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
try {
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "---------正在写");
TimeUnit.MILLISECONDS.sleep(300);
System.out.println(Thread.currentThread().getName() + "---------写完了");
map.put(key, value);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
public void get(String key) {
try {
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "---------正在读");
TimeUnit.MILLISECONDS.sleep(300);
System.out.println(Thread.currentThread().getName() + "---------读完了");
map.get(key);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
ReadWrite readWrite = new ReadWrite();
//创建线程写数据
for (int i = 0; i < 5; i++) {
final int num = i;
new Thread(() -> {
readWrite.put(String.valueOf(num), num);
}, String.valueOf(i)).start();
}
//创建线程读数据
for (int i = 0; i < 5; i++) {
final int num = i;
new Thread(() -> {
readWrite.get(String.valueOf(num));
}, String.valueOf(i)).start();
}
}
}
3、读写锁降级
主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁, 假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。
保证数据的可见性可以这样理解:假设线程A修改了数据,释放了写锁,这个时候线程T获得了写锁,修改了数据,然后也释放了写锁,线程A读取数据的时候,读到的是线程T修改的,并不是线程A自己修改的,那么在使用修改后的数据时,就会忽略线程A之前的修改结果。书上说的【当前线程无法感知线程T的数据更新】,是说线程A使用数据时,并不知道别的线程已经更改了数据,所以使用的是线程T的修改结果。因此通过锁降级来保证数据每次修改后的可见性。
原文:ReentrantReadWriteLock中锁降级的理解
package readwrite;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LockDown {
public static void main(String[] args) {
ReadWriteLock lock = new ReentrantReadWriteLock();
lock.writeLock().lock();
System.out.println("写操作");
//当前线程占有读锁,别的线程进不来
lock.readLock().lock();
System.out.println("读操作");
lock.writeLock().unlock();
//读锁释放,表示当前线程读完了(读自己修改的内容),别的线程可以进来了
lock.readLock().unlock();
}
}
4、分支合并计算0-100
package forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
class MyTask extends RecursiveTask<Integer> {
private final int MAX_SPAN = 10;
private int begin;
private int end;
private int result;
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
//小于最大区间
if (end - begin <= MAX_SPAN) {
for (int i = begin; i <= end; i++) {
result += i;
}
} else {
int middle = (begin + end) / 2;
MyTask myTask1 = new MyTask(begin, middle);
MyTask myTask2 = new MyTask(middle + 1, end);
myTask1.fork();
myTask2.fork();
result = myTask1.join() + myTask2.join();
}
return result;
}
}
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyTask myTask = new MyTask(0, 100);
//创建分支合并池对象
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);
Integer integer = forkJoinTask.get();
System.out.println(integer);
forkJoinPool.shutdown();
}
}
5、两阶段终止模式
package heima;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
@Slf4j
public class TwoPhaseTermination {
private Thread monitor;
//监控线程开启
public void start() {
log.info("两阶段终止-监控线程启动");
monitor = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
if (current.isInterrupted()) { //如果被打断
log.info("被打断,料理后事。");
break;
}
try {
TimeUnit.SECONDS.sleep(1);
log.info("执行监控记录");
} catch (InterruptedException e) { //在sleep时被打断,抛出异常打断标记为False,需要修改打断标记
e.printStackTrace();
current.interrupt(); //重新更改打断标记为True
}
}
});
monitor.start();
}
//监控线程关闭
public void stop() {
log.info("打断监控线程" );
monitor.interrupt();
}
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination twoStageTerminationMode = new TwoPhaseTermination();
twoStageTerminationMode.start();
TimeUnit.MILLISECONDS.sleep(2000);
twoStageTerminationMode.stop();
}
}