JUC——检视阅读
维护一个属于自己的知识框架图;隔三差五去看看你所记的东西。
J.U.C包的作者:Doug Lea
JUC底层实现
Concurrent包下所有类底层都是依靠CAS操作来实现,而sun.misc.Unsafe为我们提供了一系列的CAS操作。
CAS,即Compare And Swap。 顾名思义就是比较并交换。CAS操作一般涉及三个操作数:内存值,预期原值,新值。如果内存值与预期原值相同,则将会用新值替换内存值,返回更新成功,否则,什么也不处理,返回更新失败。java.util.concurrent包的底层即是依靠CAS操作来实现,CAS在java中的具体实现是sun.misc.Unsafe类,作为java.util.concurrent的实现基石,学习sun.misc.Unsafe类的方法特性就会显得十分重要。
CAS缺点:
- ABA问题 。(ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么 A-B-A 就会变成1A-2B-3A。类AtomicStampedReference来解决ABA问题 .)
- 循环时间长开销大 。(如果JVM能支持处理器提供的pause指令那么效率会有一定的提升)
- 只能保证一个共享变量的原子操作 。(加锁或者使用AtomicReference类来保证引用对象之间的原子性,把多个变量放在一个对象里来进行CAS操作。 )
JAVA CAS操作与volatile的内存模型关系
java的CAS同时具有 volatile 读和volatile写的内存语义。
concurrent包的源代码实现,会发现一个通用化的实现模式:
首先,声明共享变量为volatile;
然后,使用CAS的原子条件更新来实现线程之间的同步;
同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类)这三种concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。
concurrent包的实现架构分层:
public class UnSafeTest {
public static Unsafe unsafeBean = null;
static {
Field unsafe = null;
try {
// private static final Unsafe theUnsafe;获取该域
//有必要复习一下反射,全忘光了
unsafe = Unsafe.class.getDeclaredField("theUnsafe");
unsafe.setAccessible(true);
try {
unsafeBean = (Unsafe)unsafe.get(null);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
System.out.println(unsafeBean);
}
}
JUC架构由五个模块组成
分别是Atomic,Locks,Collections,Executor,Tools。
java并发
多任务是当多个进程共享,如CPU处理公共资源。 多线程将多任务的概念扩展到可以将单个应用程序中的特定操作细分为单个线程的应用程序。每个线程可以并行运行。 操作系统不仅在不同的应用程序之间划分处理时间,而且在应用程序中的每个线程之间划分处理时间。
线程的生命周期
线程生命周期的阶段 :
- 新线程(New) - 新线程在新的状态下开始其生命周期。直到程序启动线程为止,它保持在这种状态。它也被称为出生线程。
- 可运行(Runnable) - 新诞生的线程启动后,该线程可以运行。状态的线程被认为正在执行其任务。
- 等待(Waiting) - 有时,线程会转换到等待状态,而线程等待另一个线程执行任务。 只有当另一个线程发信号通知等待线程才能继续执行时,线程才转回到可运行状态。
- 定时等待(Timed Waiting) - 可运行的线程可以在指定的时间间隔内进入定时等待状态。 当该时间间隔到期或发生等待的事件时,此状态的线程将转换回可运行状态。
- 终止(Dead) - 可执行线程在完成任务或以其他方式终止时进入终止状态。
线程优先级
每个Java线程都有一个优先级,可以帮助操作系统确定安排线程的顺序。Java线程优先级在MIN_PRIORITY
(常数为1
)和MAX_PRIORITY
(常数为10
)之间的范围内。 默认情况下,每个线程都被赋予优先级NORM_PRIORITY
(常数为5
)。
具有较高优先级的线程对于一个程序来说更重要,应该在低优先级线程之前分配处理器时间。注意,线程优先级不能保证线程执行的顺序,并且依赖于平台。只是有优选的权限,并不代表一定优先执行。
java创建线程的4种方式
- 继承Thread类创建线程类 。
- 实现Runnable接口创建线程类 。(其中还可以使用“匿名内部类”创建多线程)
- 通过Callable和Future创建线程 。
- 通过线程池创建线程。
继承Thread类创建线程类
步骤:
1、定义一个类继承Thread类,并重写run()方法,run()方法的方法体就是线程任务;
2、创建该类的实例对象,即创建了线程对象;
3、调用线程对象的start()方法来启动线程;
实例:
public class DaqingMountainThread extends Thread {
private static final Logger LOGGER = LoggerFactory.getLogger(DaqingMountainThread.class);
@Override
public void run() {
for (int i = 0; i <3; i++) {
LOGGER.error("----大青山副团长做第"+i+"个佣兵任务----");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类:
DaqingMountainThread thread = new DaqingMountainThread();
thread.start();
实例2:
public class DQMountainThread extends Thread {
private static final Logger LOGGER = LoggerFactory.getLogger(DQMountainThread.class);
private String name;
private Thread thread;
public DQMountainThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i <3; i++) {
LOGGER.error("----大青山副团长做第"+i+"个佣兵任务----");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void start(){
LOGGER.error("start thread "+name);
if (thread == null) {
//该构造函数Thread(Runnable target, String name) 可用是因为Thread 实现了 Runnable
thread = new Thread(this,name);
}
thread.start();
}
}
测试类
DQMountainThread thread = new DQMountainThread("大青山副团长");
thread.start();
输出:
ERROR - start thread 大青山副团长
ERROR - ----大青山副团长做第0个佣兵任务----
ERROR - ----大青山副团长做第1个佣兵任务----
ERROR - ----大青山副团长做第2个佣兵任务----
实现Runnable接口创建线程类
步骤:
1、定义一个类实现Runnable接口,该类的run()方法是该线程的线程任务;
2、创建该Runnable的对象实例;
3、将Runnable对象实例作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象;
4、调用线程对象的start()方法启动该线程
示例1:
public class AmyRunnable implements Runnable{
private static final Logger LOGGER = LoggerFactory.getLogger(AmyRunnable.class);
@Override
public void run() {
for (int i = 0; i <10; i++) {
LOGGER.error("----艾米团长做第"+i+"个佣兵任务----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类:
AmyRunnable amyRunnable = new AmyRunnable();
//艾米团长为该线程命名
Thread t = new Thread(amyRunnable,"艾米团长");
t.start();
实例2:
public class AmyHaborRunnable implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(AmyRunnable.class);
private String name;
private Thread thread;
public AmyHaborRunnable(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i <10; i++) {
LOGGER.error("----艾米团长做第"+i+"个佣兵任务----");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void start(){
LOGGER.error("start thread "+name);
if (thread == null) {
thread = new Thread(this,name);
}
thread.start();
}
}
测试类:
AmyHaborRunnable amyHaborRunnable = new AmyHaborRunnable("艾米团长");
amyHaborRunnable.start();
通过Callable和Future创建线程
步骤:
1、创建Callable接口实现类,并实现call()方法,是该线程的线程任务,有返回值。
2、创建Callable实现类的对象实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
3、使用FutureTask对象实例作为构造器参数传入Thread类实例对象。
4、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
示例:
public class GreenDragon implements Callable<String> {
private static final Logger LOGGER = LoggerFactory.getLogger(GreenDragon.class);
@Override
public String call() throws Exception {
for (int i = 0; i <10; i++) {
LOGGER.error("----绿儿发射第"+i+"个龙息----");
TimeUnit.SECONDS.sleep(1);
}
return "绿儿完成任务赏蜥蜴干";
}
}
测试类1:
GreenDragon greenDragon = new GreenDragon();
FutureTask<String> futureTask = new FutureTask<>(greenDragon);
Thread thread = new Thread(futureTask);
thread.start();
//Callable是否任务完成
if (!futureTask.isDone()) {
TimeUnit.SECONDS.sleep(1);
}
//get()方法阻塞当前线程,直到调用的线程运行结束。
LOGGER.error(futureTask.get());
测试类2:
GreenDragon greenDragon = new GreenDragon();
FutureTask<String> futureTask = new FutureTask<>(greenDragon);
Thread thread = new Thread(futureTask);
thread.start();
//get(long timeout, TimeUnit unit) 方法只会阻塞到设定的时间超时就会返回超时异常TimeoutException
LOGGER.error(futureTask.get(10,TimeUnit.SECONDS));
测试类3,取消任务:
GreenDragon greenDragon = new GreenDragon();
FutureTask<String> futureTask = new FutureTask<>(greenDragon);
Thread thread = new Thread(futureTask);
thread.start();
//取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。
// 参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。
// 如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;
// 如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;
// 如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
/**
* 经过实验,当mayInterruptIfRunning设置为false时,任务并不会中断,会继续执行完整个task。这里的任务指的是这条线程执行的call方法体
* 输出:
* ERROR - ----绿儿发射第0个龙息----
* ERROR - ----绿儿发射第1个龙息----
* ERROR - true
* ERROR - ----绿儿发射第2个龙息----
* ERROR - ----绿儿发射第3个龙息----
* ERROR - ----绿儿发射第4个龙息----
* ERROR - ----绿儿发射第5个龙息----
* ERROR - ----绿儿发射第6个龙息----
* ERROR - ----绿儿发射第7个龙息----
* ERROR - ----绿儿发射第8个龙息----
* ERROR - ----绿儿发射第9个龙息----
*
* 当mayInterruptIfRunning设置为true时,会取消正在执行过程中的任务,任务中断。会返回true。
* 输出:
* ERROR - ----绿儿发射第0个龙息----
* ERROR - ----绿儿发射第1个龙息----
* ERROR - true
*
* 特别注意的是,在调用futureTask.cancel(true)方法时,调用线程或者说主线程不能调用futureTask.get()方法,get()方法阻塞当前线程,直到调用的线程运行结束。
* 因此在get()方法后面调用cancel是没有意义的,任务已经结束,返回肯定是false;
* 另一种情况是也不能调用get(long timeout, TimeUnit unit) 方法,因为当超时时调用线程或者说主线程就会抛出TimeoutException异常而中断执行不到,
* 而如果没有超时的话任务已经结束,返回肯定是false;
*/
boolean cancel = futureTask.cancel(true);
LOGGER.error(String.valueOf(cancel));
通过线程池创建线程
待添加。
四种创建线程方式对比
待对比。
Java并发线程间通信 (早期用法)
编号 | 方法 | 描述 |
---|---|---|
1 | public void wait() |
使当前线程等到另一个线程调用notify() 方法。 |
2 | public void notify() |
唤醒在此对象监视器上等待的单个线程。 |
3 | public void notifyAll() |
唤醒所有在同一个对象上调用wait() 的线程。 |
实例:
public class Dialogue {
boolean flag = false;
public synchronized void question(String msg) {
if (flag) {
try {
wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("question: "+msg);
flag = true;
notify();
}
public synchronized void answer(String msg) {
if (!flag) {
try {
wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("answer: "+msg);
flag = false;
notify();
}
}
public class AmyHabor implements Runnable {
private Dialogue dialogue;
private String[] strArr = {"绿儿~~~","想不想吃蜥蜴干呀?","来,咱们先签个绿龙穴探索财产分割协议!"};
public AmyHabor(Dialogue dialogue) {
this.dialogue = dialogue;
}
@Override
public void run() {
for (int i = 0; i < strArr.length; i++) {
dialogue.question(strArr[i]);
}
}
}
public class GreenDragon implements Runnable {
private Dialogue dialogue;
private String[] strArr = {"啊球,鼻涕留下来!","好啊,好啊","艾米哥哥真好!(喝不尽的仇人血啊)",};
public GreenDragon(Dialogue dialogue) {
this.dialogue = dialogue;
}
@Override
public void run() {
for (int i = 0; i < strArr.length; i++) {
dialogue.answer(strArr[i]);
}
}
}
测试类:
Dialogue dialogue = new Dialogue();
AmyHabor amy = new AmyHabor(dialogue);
GreenDragon dragon = new GreenDragon(dialogue);
new Thread(amy).start();
new Thread(dragon).start();
输出:
question: 绿儿~~~
answer: 啊球,鼻涕留下来!
question: 想不想吃蜥蜴干呀?
answer: 好啊,好啊
question: 来,咱们先签个绿龙穴探索财产分割协议!
answer: 艾米哥哥真好!(喝不尽的仇人血啊)
Java简单并发同步
可以用同步方法或者同步代码块synchronized来实现并发同步。他们的原理都是一个对象实例就是一把锁,当大家用同一把锁(对象)时,该对象的同步方法(无论有几个),一次就只能有一条线程在执行其中的一个方法,执行完释放了锁才能让下一个线程获取到锁执行同步方法。
实例:
public class CoinCount {
public synchronized void count() {
for (int i = 0; i < 3; i++) {
System.out.println(i + "个金币");
}
}
}
public class ChiHanFeng implements Runnable {
private Thread thread;
private String name;
private CoinCount coinCount;
public ChiHanFeng(String name, CoinCount coinCount) {
this.name = name;
this.coinCount = coinCount;
}
@Override
public void run() {
System.out.println(name+"开始算钱");
coinCount.count();
}
public void start(){
if (thread == null) {
thread = new Thread(this,name);
}
thread.start();
}
}
测试类:
CoinCount coinCount = new CoinCount();
ChiHanFeng chiHanFeng = new ChiHanFeng("chihanfeng_1",coinCount);
chiHanFeng.start();
ChiHanFeng chiHanFeng2 = new ChiHanFeng("chihanfeng_2",coinCount);
chiHanFeng2.start();
输出:
chihanfeng_1开始算钱
0个金币
1个金币
2个金币
chihanfeng_2开始算钱
0个金币
1个金币
2个金币
Java并发死锁
死锁描述了两个或多个线程等待彼此而被永久阻塞的情况。 当多个线程需要相同的锁定但以不同的顺序获取时,会发生死锁。
//待看java并发编程书详解。
实例:
public class AmyHaborRunnable implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(AmyHaborRunnable.class);
private String name;
private Thread thread;
public AmyHaborRunnable(String name) {
this.name = name;
}
@Override
public void run() {
synchronized (JUCTest.rengar) {
LOGGER.error("----艾米团长获取雷葛老师学习魔法----");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.error("----艾米团长尝试获取池寒枫练习长剑----");
synchronized (JUCTest.chiHanFeng) {
for (int i = 0; i < 10; i++) {
LOGGER.error("----雷葛,池寒枫给艾米团长教学----");
}
}
}
}
public void start() {
LOGGER.error("start thread " + name);
if (thread == null) {
thread = new Thread(this, name);
}
thread.start();
}
}
public class DQMountainThread extends Thread {
private static final Logger LOGGER = LoggerFactory.getLogger(DQMountainThread.class);
private String name;
private Thread thread;
public DQMountainThread(String name) {
this.name = name;
}
@Override
public void run() {
synchronized (JUCTest.chiHanFeng) {
LOGGER.error("----大青山副团长获取池寒枫练习龙枪----");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.error("----大青山副团长尝试获取雷葛老师学习帝国历史----");
synchronized (JUCTest.rengar) {
for (int i = 0; i < 10; i++) {
LOGGER.error("----雷葛,池寒枫给大青山副团长教学----");
}
}
}
}
@Override
public void start(){
LOGGER.error("start thread "+name);
if (thread == null) {
//该构造函数Thread(Runnable target, String name) 可用是因为Thread 实现了 Runnable
thread = new Thread(this,name);
}
thread.start();
}
}
测试类
//雷葛
public static Object rengar = new Object();
//池寒枫
public static Object chiHanFeng = new Object();
public static void main(String[] args) {
//死锁演示
AmyHaborRunnable amyRunnable = new AmyHaborRunnable("艾米团长");
DQMountainThread dqMountainThread = new DQMountainThread("大青山副团长");
amyRunnable.start();
dqMountainThread.start();
}
输出:死锁
ERROR - start thread 艾米团长
ERROR - start thread 大青山副团长
ERROR - ----大青山副团长获取池寒枫练习龙枪----
ERROR - ----艾米团长获取雷葛老师学习魔法----
ERROR - ----艾米团长尝试获取池寒枫练习长剑----
ERROR - ----大青山副团长尝试获取雷葛老师学习帝国历史----
死锁解决方法之一:调整锁的申请顺序,总是以相同的顺序来申请锁 。
实例:
@Override
public void run() {
synchronized (JUCTest.chiHanFeng) {
LOGGER.error("----大青山副团长获取池寒枫练习龙枪----");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.error("----大青山副团长尝试获取雷葛老师学习帝国历史----");
synchronized (JUCTest.rengar) {
for (int i = 0; i < 1; i++) {
LOGGER.error("----雷葛,池寒枫给大青山副团长教学----");
}
}
}
}
@Override
public void run() {
synchronized (JUCTest.chiHanFeng) {
LOGGER.error("----艾米团长尝试获取池寒枫练习长剑----");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.error("----艾米团长获取雷葛老师学习魔法----");
synchronized (JUCTest.rengar) {
for (int i = 0; i < 1; i++) {
LOGGER.error("----雷葛,池寒枫给艾米团长教学----");
}
}
}
}
输出:
ERROR - start thread 艾米团长
ERROR - start thread 大青山副团长
ERROR - ----艾米团长尝试获取池寒枫练习长剑----
ERROR - ----艾米团长获取雷葛老师学习魔法----
ERROR - ----雷葛,池寒枫给艾米团长教学----
ERROR - ----大青山副团长获取池寒枫练习龙枪----
ERROR - ----大青山副团长尝试获取雷葛老师学习帝国历史----
ERROR - ----雷葛,池寒枫给大青山副团长教学----
原子变量(Atomic)
使用原子变量类最大的好处就是可以避免多线程的优先级倒置和死锁情况的发生,提升在高并发处理下的性能。
AtomicLong
java.util.concurrent.atomic.AtomicLong
类提供了可以被原子地读取和写入的底层long
值的操作,并且还包含高级原子操作。 AtomicLong
支持基础long
类型变量上的原子操作。 **它具有获取和设置方法,如在volatile
变量上的读取和写入。
这些原子变量类主要用于在高并发环境下的高效程序处理,来帮助我们简化同步处理。
AtomicLong类中的方法
序号 | 方法 | 描述 |
---|---|---|
1 | public long addAndGet(long delta) |
将给定值原子地添加到当前值。 |
2 | public boolean compareAndSet(long expect, long update) |
如果当前值与预期值相同,则将该值原子设置为给定的更新值。 |
3 | public long decrementAndGet() |
当前值原子减1 。 |
4 | public double doubleValue() |
以double 形式返回指定数字的值。 |
5 | public float floatValue() |
以float 形式返回指定数字的值。 |
6 | public long get() |
获取当前值。 |
7 | public long getAndAdd(long delta) |
自动将给定值添加到当前值。 |
8 | public long getAndDecrement() |
当前值原子减1 。 |
9 | public long getAndIncrement() |
当前值原子增加1 。 |
10 | public long getAndSet(long newValue) |
将原子设置为给定值并返回旧值。 |
11 | public long incrementAndGet() |
原子上增加一个当前值。 |
12 | public int intValue() |
以int 形式返回指定数字的值。 |
13 | public void lazySet(long newValue) |
最终设定为给定值。 |
14 | public long longValue() |
返回指定数字的值为long 类型。 |
15 | public void set(long newValue) |
设置为给定值。 |
16 | public String toString() |
返回当前值的String 表示形式。 |
17 | public boolean weakCompareAndSet(long expect, long update) |
如果当前值与预期值相同,则将该值原子设置为给定的更新值。 |
实例:
public class Soldier implements Callable<String> {
private RedStoneCounter redStoneCounter;
public Soldier(RedStoneCounter redStoneCounter) {
this.redStoneCounter = redStoneCounter;
}
@Override
public String call() throws Exception {
redStoneCounter.counterSoldier();
return "yes,sir";
}
}
public class RedStoneCounter {
private static final Logger LOGGER = LoggerFactory.getLogger(RedStoneCounter.class);
private AtomicLong atomicLong = new AtomicLong(0);
//不是原子变量类的话,不能确保一定是1000,要想保证则要加synchronized
//ERROR - 红石大帝点兵,应到1000人,实到:996
//private Long atomicLong = 0L;
public void counterSoldier(){
//这些都可以用
atomicLong.getAndIncrement();
//atomicLong.addAndGet(1);
//atomicLong.getAndAdd(1);
//atomicLong.incrementAndGet();
}
public Long getTotalSoldier(){
return atomicLong.get();
}
public static void main(String[] args) throws InterruptedException {
final RedStoneCounter redStone = new RedStoneCounter();
for (int i = 0; i < 1000; i++) {
new Thread(new FutureTask<String>(new Soldier(redStone))).start();
}
//等待时间不够,ERROR - 红石大帝点兵,应到1000人,实到:663
//TimeUnit.MILLISECONDS.sleep(1);
//ERROR - 红石大帝点兵,应到1000人,实到:1000
TimeUnit.MILLISECONDS.sleep(500);
LOGGER.error("红石大帝点兵,应到1000人,实到:" +redStone.getTotalSoldier());
}
}
AtomicInteger
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
AtomicInteger类:
//AtomicInteger的关键域
public class AtomicInteger extends Number implements java.io.Serializable {
// setup to use Unsafe.compareAndSwapInt for updates
//unsafe是java提供的获得对象内存地址访问的类,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具。
private static final Unsafe unsafe = Unsafe.getUnsafe();
//valueOffset是用来记录value值本身在内存的偏移地址的,这个记录主要是为了在更新操作时在内存中找到value的位置,方便比较。
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//value是用来存储整数的实际变量,也就是AtomicInteger的值,这里被声明为volatile,是为了保证在更新操作时,当前线程可以拿到value最新的值。
private volatile int value;
//CAS自增
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
//比较更新值
public final boolean compareAndSet(int expect, int update) {
//使用unsafe的native方法,实现高效的硬件级别CAS
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
//...
}
性能测试示例:
public interface Counter {
void counterSoldier();
}
public class SimpleRedStoneCounter implements Counter {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleRedStoneCounter.class);
private Long atomicLong = 0L;
@Override
public synchronized void counterSoldier() {
atomicLong++;
}
public Long getTotalSoldier() {
return atomicLong;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
int testNum = 100000;
long start = System.currentTimeMillis();
final SimpleRedStoneCounter redStone = new SimpleRedStoneCounter();
//ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(100, 200, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
for (int i = 0; i < testNum; i++) {
FutureTask<String> futureTask = new FutureTask<>(new Soldier(redStone));
//poolExecutor.execute(futureTask);
new Thread(futureTask).start();
if (i == testNum-1) {
futureTask.get();
}
}
//TimeUnit.MILLISECONDS.sleep(500);
long end = System.currentTimeMillis();
LOGGER.error("红石大帝点兵,应到" + testNum + "人,实到:" + redStone.getTotalSoldier());
LOGGER.error("红石大帝点兵耗时:" + (end - start));
//poolExecutor.shutdown();
}
}
public class RedStoneCounter implements Counter {
private static final Logger LOGGER = LoggerFactory.getLogger(RedStoneCounter.class);
private AtomicLong atomicLong = new AtomicLong(0);
//不是原子变量类的话,不能确保一定是1000,ERROR - 红石大帝点兵,应到1000人,实到:996
//private Long atomicLong = 0L;
@Override
public void counterSoldier() {
//这些都可以用
atomicLong.getAndIncrement();
//atomicLong.addAndGet(1);
//atomicLong.getAndAdd(1);
//atomicLong.incrementAndGet();
}
public Long getTotalSoldier() {
return atomicLong.get();
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
int testNum = 100000;
long start = System.currentTimeMillis();
final RedStoneCounter redStone = new RedStoneCounter();
//ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(100, 200, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
for (int i = 0; i < testNum; i++) {
FutureTask<String> futureTask = new FutureTask<>(new Soldier(redStone));
//poolExecutor.execute(futureTask);
new Thread(futureTask).start();
if (i == testNum - 1) {
futureTask.get();
}
}
//等待时间不够,ERROR - 红石大帝点兵,应到1000人,实到:663
//TimeUnit.MILLISECONDS.sleep(1);
//ERROR - 红石大帝点兵,应到1000人,实到:1000
//TimeUnit.MILLISECONDS.sleep(500);
long end = System.currentTimeMillis();
LOGGER.error("红石大帝点兵,应到" + testNum + "人,实到:" + redStone.getTotalSoldier());
LOGGER.error("红石大帝点兵耗时:" + (end - start));
//poolExecutor.shutdown();
}
}
Soldier类不变。
输出:
/**
* 测试基数:int testNum = 100000;
* 使用线程池耗时:ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(100, 200, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
* 普通synchronized 方法:
* ERROR - 红石大帝点兵,应到100000人,实到:100000
* ERROR - 红石大帝点兵耗时:118
*
*AtomicLong:
*ERROR - 红石大帝点兵,应到100000人,实到:99905
* ERROR - 红石大帝点兵耗时:74
*
* 使用new Thread 创建线程:
*普通synchronized 方法:
* ERROR - 红石大帝点兵,应到100000人,实到:100000
* ERROR - 红石大帝点兵耗时:10959
*
* AtomicLong:
*ERROR - 红石大帝点兵,应到100000人,实到:100000
* ERROR - 红石大帝点兵耗时:10392
*
* 结论:AtomicLong确实会比普通synchronized性能更高,JNI本地的CAS性能远超synchronized关键字,
* 不过synchronized也是挺快的现在,最让人惊艳的是线程池如果运用得当,效率是普通创建线程的百倍。
*/
AtomicBoolean
AtomicIntegerArray
public class AtomicIntegerArrayTest {
private static final Logger LOGGER = LoggerFactory.getLogger(AtomicIntegerArrayTest.class);
private static AtomicIntegerArray foodStore = new AtomicIntegerArray(3);
public static void main(String[] args) throws InterruptedException {
LOGGER.error("从军每人分粮2斛");
for (int i = 0; i < foodStore.length(); i++) {
foodStore.set(i,2);
}
Thread supplyOfficer = new Thread(new DecrementSupply());
Thread bossCao = new Thread(new RewardSupply());
supplyOfficer.start();
bossCao.start();
supplyOfficer.join();
bossCao.join();
LOGGER.error("如今分粮情况:"+foodStore.toString());
}
public static class DecrementSupply implements Runnable{
@Override
public void run() {
for (int i = 0; i < foodStore.length(); i++) {
int decrement = foodStore.decrementAndGet(i);
LOGGER.error("曹老板要求小斛分粮,士兵:"+i+"现在分粮:"+decrement);
}
}
}
public static class RewardSupply implements Runnable{
@Override
public void run() {
LOGGER.error("汝妻子我养之,汝无虑也!");
LOGGER.error("军需官擅自小斛分粮,已被我祭旗,所有小斛分粮的将士们每天分粮3斛!");
for (int i = 0; i < foodStore.length(); i++) {
boolean decrement = foodStore.compareAndSet(i,1,3);
LOGGER.error("曹老板要求小斛分粮,士兵:"+i+"现在分粮:"+foodStore.get(i));
}
}
}
}
输出:
ERROR - 从军每人分粮2斛
ERROR - 曹老板要求小斛分粮,士兵:0现在分粮:1
ERROR - 曹老板要求小斛分粮,士兵:1现在分粮:1
ERROR - 曹老板要求小斛分粮,士兵:2现在分粮:1
ERROR - 汝妻子我养之,汝无虑也!
ERROR - 军需官擅自小斛分粮,已被我祭旗,所有小斛分粮的将士们每天分粮3斛!
ERROR - 曹老板要求小斛分粮,士兵:0现在分粮:3
ERROR - 曹老板要求小斛分粮,士兵:1现在分粮:3
ERROR - 曹老板要求小斛分粮,士兵:2现在分粮:3
ERROR - 如今分粮情况:[3, 3, 3]
AtomicLongArray
java.util.concurrent.atomic.AtomicLongArray
类提供了可以原子读取和写入的底层long
类型数组的操作,并且还包含高级原子操作。 AtomicLongArray
支持对基础long
类型数组变量的原子操作。
AtomicLongArray
类方法列表。
序号 | 方法 | 描述 |
---|---|---|
1 | public long addAndGet(int i, long delta) |
原子地将给定的值添加到索引i 的元素。 |
2 | public boolean compareAndSet(int i, long expect, long update) |
如果当前值== 期望值,则将位置i 处的元素原子设置为给定的更新值。 |
3 | public long decrementAndGet(int i) |
索引i 处的元素原子并自减1 。 |
4 | public long get(int i) |
获取位置i 的当前值。 |
5 | public long getAndAdd(int i, long delta) |
原子地将给定的值添加到索引i 的元素。 |
6 | public long getAndDecrement(int i) |
索引i 处的元素原子并自减1 ,并返回旧值。 |
7 | public long getAndIncrement(int i) |
将位置i 处的元素原子设置为给定值,并返回旧值。 |
8 | public long getAndSet(int i, long newValue) |
将位置i 处的元素原子设置为给定值,并返回旧值。 |
9 | public long incrementAndGet(long i) |
在索引i 处以原子方式自增元素。 |
10 | public void lazySet(int i, long newValue) |
最终将位置i 处的元素设置为给定值。 |
11 | public int length() |
返回数组的长度。 |
12 | public void set(int i, long newValue) |
将位置i 处的元素设置为给定值。 |
13 | public String toString() |
返回数组的当前值的String 表示形式。 |
14 | public boolean weakCompareAndSet(int i, int expect, long update) |
如果当前值== 期望值,则将位置i 处的元素原子设置为给定的更新值。 |
AtomicReferenceArray
java.util.concurrent.atomic.AtomicReferenceArray
类提供了可以原子读取和写入的底层引用数组的操作,并且还包含高级原子操作。 AtomicReferenceArray
支持对底层引用数组变量的原子操作。
AtomicReferenceArray
类方法:
序列 | 方法 | 描述 |
---|---|---|
1 | public boolean compareAndSet(int i, E expect, E update) |
如果当前值== 期望值,则将位置i 处的元素原子设置为给定的更新值。 |
2 | public E get(int i) |
获取位置i 的当前值。 |
3 | public E getAndSet(int i, E newValue) |
将位置i 处的元素原子设置为给定值,并返回旧值。 |
4 | public void lazySet(int i, E newValue) |
最终将位置i 处的元素设置为给定值。 |
5 | public int length() |
返回数组的长度。 |
6 | public void set(int i, E newValue) |
将位置i 处的元素设置为给定值。 |
7 | public String toString() |
返回数组的当前值的String 表示形式。 |
8 | public boolean weakCompareAndSet(int i, E expect, E update) |
如果当前值== 期望值,则将位置i 处的元素原子设置为给定的更新值。 |
实用类实例
ThreadLocal
ThreadLocal
类用于创建只能由同一个线程读取和写入的线程局部变量。 例如,如果两个线程正在访问引用相同threadLocal
变量的代码,那么每个线程都不会看到任何其他线程操作完成的线程变量。
ThreadLocal
类中可用的重要方法的列表:
编号 | 方法 | 描述 |
---|---|---|
1 | public T get() |
返回当前线程的线程局部变量的副本中的值。 |
2 | protected T initialValue() |
返回此线程局部变量的当前线程的“初始值”。 |
3 | public void remove() |
删除此线程局部变量的当前线程的值。 |
4 | public void set(T value) |
将当前线程的线程局部变量的副本设置为指定的值。 |
示例:
public class FlyDragonRunnable implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(FlyDragonRunnable.class);
int count = 0;
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
@Override
public void run() {
if (threadLocal.get() != null) {
logger.error("风之精灵呀,幻化成守护的龙吧!" + threadLocal.get() + 1);
} else {
threadLocal.set(1);
logger.error("风之精灵呀,幻化成守护的龙吧! :" + threadLocal.get());
}
count++;
logger.error("风之幻龙数:" + count);
}
}
public static void main(String[] args) throws InterruptedException {
FlyDragonRunnable flyDragonRunnable = new FlyDragonRunnable();
LOGGER.error("雷葛召唤守护3条风龙!");
for (int i = 0; i < 3; i++) {
Thread flyDragon = new Thread(flyDragonRunnable);
flyDragon.start();
flyDragon.join();
}
}
输出:
ERROR - 雷葛召唤守护3条风龙!
ERROR - 风之精灵呀,幻化成守护的龙吧! :1
ERROR - 风之幻龙数:1
ERROR - 风之精灵呀,幻化成守护的龙吧! :1
ERROR - 风之幻龙数:2
ERROR - 风之精灵呀,幻化成守护的龙吧! :1
ERROR - 风之幻龙数:3
ThreadLocalRandom
java.util.concurrent.ThreadLocalRandom
是从jdk 1.7
开始引入的实用程序类,当需要多个线程或ForkJoinTasks
来生成随机数时很有用。 它提高了性能,并且比Math.random()
方法占用更少的资源。
ThreadLocalRandom方法
以下是ThreadLocalRandom
类中可用的重要方法的列表。
编号 | 方法 | 说明 |
---|---|---|
1 | public static ThreadLocalRandom current() |
返回当前线程的ThreadLocalRandom 。 |
2 | protected int next(int bits) |
生成下一个伪随机数。 |
3 | public double nextDouble(double n) |
返回伪随机,均匀分布在0(含)和指定值(独占)之间的double 值。 |
4 | public double nextDouble(double least, double bound) |
返回在给定的least 值(包括)和bound (不包括)之间的伪随机均匀分布的值。 |
5 | public int nextInt(int least, int bound) |
返回在给定的least 值(包括)和bound (不包括)之间的伪随机均匀分布的整数值。 |
6 | public long nextLong(long n) |
返回伪随机均匀分布的值在0(含)和指定值(不包括)之间的长整数值。 |
7 | public long nextLong(long least, long bound) |
返回在给定的最小值(包括)和bound (不包括)之间的伪随机均匀分布的长整数值。 |
8 | public void setSeed(long seed) |
设置伪随机的种子值,抛出UnsupportedOperationException 异常。 |
Random
之所以在多线程环境中性能不高的原因是多个线程共享同一个 Random
实例并进行争夺。 ThreadLocalRandom
结合了 Random
和 ThreadLocal
类,并被隔离在当前线程中。因此它通过避免任何对 Random
对象的并发访问,从而在多线程环境中实现了更好的性能。
不同于 Random
, ThreadLocalRandom
明确的不支持设置随机种子。 它重写了 Random
的 setSeed(long seed)
方法并直接抛出了 UnsupportedOperationException
异常。
在多线程下使用 ThreadLocalRandom
产生随机数时,直接使用 ThreadLocalRandom.current().int()
Locks(锁 )
Lock ——ReentrantLock
java.util.concurrent.locks.Lock
接口用作线程同步机制,类似于同步块。新的锁定机制更灵活,提供比同步块更多的选项。 锁和同步块之间的主要区别如下:
- 序列的保证 - 同步块不提供对等待线程进行访问的序列的任何保证,但
Lock
接口处理它。 - 无超时,如果未授予锁,则同步块没有超时选项。
Lock
接口提供了这样的选项。 - 单一方法同步块必须完全包含在单个方法中,而
Lock
接口的方法lock()
和unlock()
可以以不同的方式调用。
Lock类中的方法
以下是Lock
类中可用的重要方法的列表。
编号 | 方法 | 描述说明 |
---|---|---|
1 | public void lock() |
获得锁 |
2 | public void lockInterruptibly() |
获取锁定,除非当前线程中断 |
3 | public Condition newCondition() |
返回绑定到此Lock 实例的新Condition 实例 |
4 | public boolean tryLock() |
只有在调用时才可以获得锁 |
5 | public boolean tryLock(long time, TimeUnit unit) |
如果在给定的等待时间内自由,并且当前线程未被中断,则获取该锁。 |
6 | public void unlock() |
释放锁 |
ReentrantLock
类是Lock
接口的一个实现。 ReentrantLock
类允许线程锁定方法,即使它已经具有其他方法锁。
ReentranLock和synchronize的区别
①都是独占锁,线程阻塞同步
ReentrantLock和synchronized都是加锁式同步,当一个线程获取了对象锁后,其它要进入同步块的线程就必须阻塞在同步块外等待。线程的阻塞和唤醒需要操作系统在用户态和内核态之间切换,所以,ReentrantLock和synchronized都是代价比较高的。
②实现方式不同,synchronized的锁机制是由jvm实现,ReentrantLock是api层面的锁。
synchronized是java语言的关键字,它的锁机制是由jvm实现的,是原生语法层面上的互斥,最底层是mutex。而ReentrantLock则是JDK1.5之后,提供的api层面 的锁,需要在代码中显示调用lock、unlock等方法来完成。
所以,从便利性来说,synchronized使用起来更简单一些。但是从灵活度来说,ReentrantLock更灵活,可控性也更强,可实现更细粒度的锁。但是,在使用ReentrantLock时,一定要注意lock和unlock的匹配和顺序,否则就可能造成死锁。常见的方案是把unlock放在异常处理的finally语句块中。
③性能效率相差不多
人们很容易被大众化的观点所误导,认为synchronized的效率会比ReentrantLock差很多。但是事实上,synchronized在JDK的发展过程中,经过了不断优化,比如引入了偏向锁,轻量级锁,锁升级机制等,目前,已经和ReentrantLock的效率相差不多了。如果没有特殊的场景,推荐使用synchronized,因为它使用起来比较简单,且不会造成死锁。
④是否公平锁,synchronized是非公平锁,ReentrantLock可以实现公平锁和非公平锁。
排队等厕所,厕所门上有把锁。里面的人用完出来,把钥匙给队伍最前面的人,这就是公平锁。如果里面的人用完出来,把钥匙直接扔地上,谁抢上算谁的,这就是非公平锁。
synchronized是非公平锁,并且它无法实现公平锁。要实现公平锁,可以通过ReentrantLock来实现。ReentrantLock默认是非公平锁,通过new ReentrantLock(true)可以用来构造一个公平锁。
⑤都是可重入锁
一个线程可以对某个资源重复加锁,称之为可重入锁。这个情形很常见于递归。如果锁不可重入,就有可能会发生如下情况:
A线程获取方法B的锁,在方法B中,有代码递归调用了自己。于是,A线程需要在方法B中再次获取B的锁。如果锁不可重入,A就会发现,方法B上已经有锁,A就进入了等待。但事实上,给B加锁的就是A自己。自己一直在等待自己,岂不是可笑?
synchronized就是一把可重入锁。当然了,使用ReentrantLock也可以实现可重入锁。
⑥synchronized等待不可响应中断,ReentrantLock等待可响应中断。
等待可中断是使用ReentrantLock时,可以实现的一个机制。当某个线程等待锁过长时间时,程序可以通过lockInterruptibly方法来使当前线程中断等待,转去执行其它的线程。
⑦ReentrantLock可实现线程分组唤醒,synchronized不能。
有些场景下,我们可能不希望唤醒所有的线程,而是唤醒部分线程。这种方式在synchronized下是无法实现的。但是,ReentrantLock通过提供一个Condition类,可以同时绑定多个对象,以此,来实现线程的分组唤醒。
推荐写多线程代码的小口诀:
- 高内聚/低耦合前提下,线程—— 操作——资源类 (高内聚/低耦合)
- 判断/干活/通知
- 多线程交互中,必须要防止多线程的虚假唤醒,也即(判断只用while,不能用if)
- 一定要注意,标志位的修改更新.
示例:
public class MercenaryTask {
private static final Logger logger = LoggerFactory.getLogger(MercenaryTask.class);
private final Lock lock = new ReentrantLock();
public void adoptTask() throws InterruptedException {
lock.lock();
try {
int level = ThreadLocalRandom.current().nextInt(1, 4);
logger.error(Thread.currentThread().getName()+"领取了一个"+level+"级别任务!");
TimeUnit.SECONDS.sleep(level);
} finally {
logger.error(Thread.currentThread().getName()+"接收了任务成功!");
lock.unlock();
}
}
}
public class MercenaryRunnable implements Runnable {
private MercenaryTask mercenaryTask;
public MercenaryRunnable(MercenaryTask mercenaryTask) {
this.mercenaryTask = mercenaryTask;
}
@Override
public void run() {
try {
mercenaryTask.adoptTask();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试类:
MercenaryRunnable mercenaryRunnable = new MercenaryRunnable(new MercenaryTask());
for (int i = 0; i < 3; i++) {
Thread mercenary = new Thread(mercenaryRunnable,"mercenary "+i);
mercenary.start();
}
输出:
ERROR - mercenary 0领取了一个2级别任务!
ERROR - mercenary 0接收了任务成功!
ERROR - mercenary 1领取了一个3级别任务!
ERROR - mercenary 1接收了任务成功!
ERROR - mercenary 2领取了一个3级别任务!
ERROR - mercenary 2接收了任务成功!
Condition实现通知 :
class ShareResource
{
private int number = 1;//1:A 2:B 3:C
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print(int totalLoopNumber)
{
lock.lock();
try
{
//1 判断
while(number != 1 && Thread.currentThread().getName().equals("A"))
{
//A 就要停止
c1.await();
}
while(number != 2 && Thread.currentThread().getName().equals("B"))
{
//B 就要停止
c2.await();
}
while(number != 3 && Thread.currentThread().getName().equals("C"))
{
//C 就要停止
c3.await();
}
//2 干活
System.out.println(Thread.currentThread().getName()+"\t"+"\t totalLoopNumber: "+totalLoopNumber);
//3 通知
if (Thread.currentThread().getName().equals("A")){
number = 2;
c2.signal();
}else if (Thread.currentThread().getName().equals("B")){
number = 3;
c3.signal();
}else {
number = 1;
c1.signal();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class AutoBuildVehicle {
public static void main(final String[] args) {
final ShareResource shareResource = new ShareResource();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareResource.print(5);
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareResource.print(10);
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareResource.print(15);
}
}
},"C").start();
}
}
ReadWriteLock ——ReentrantReadWriteLock
java.util.concurrent.locks.ReadWriteLock
接口允许一次读取多个线程,但一次只能写入一个线程。
- 读锁 - 如果没有线程锁定
ReadWriteLock
进行写入,则多线程可以访问读锁。 - 写锁 - 如果没有线程正在读或写,那么一个线程可以访问写锁。
ReentrantReadWriteLock方法:
编号 | 方法 | 描述 |
---|---|---|
1 | public Lock readLock() |
返回用于读的锁。 |
2 | public Lock writeLock() |
返回用于写的锁。 |
ReentrantReadWriteLock的两把锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁,描述如下:
线程进入读锁的前提条件:
没有其他线程的写锁,
没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。
线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
读写锁有以下三个重要的特性:
①公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
②重进入:读锁和写锁都支持线程重进入。
③锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
示例:对共享资源的多读少写情况:
class MyCache{
/*
“volatile在Java并发编程中常用于保持内存可见性和防止指令重排序。
内存可见性(MemoryVisibility):所有线程都能看到共享内存的最新状态
防止指令重排:在基于偏序关系的Happens-Before内存模型中,指令重排技术大大提高了程序执行效率
*/
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void set(String key,Object value){
System.out.println(Thread.currentThread().getName()+"尝试获取写锁!");
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"开始写");
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写完了:"+key);
} finally {
rwLock.writeLock().unlock();
}
}
public void get(String key){
System.out.println(Thread.currentThread().getName()+"尝试获取读锁!");
rwLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"开始读");
Object re = map.get(key);
System.out.println(Thread.currentThread().getName()+"读完了:"+re);
} finally {
rwLock.readLock().unlock();
}
}
}
public class ReadWriteLockTest{
public static void main(String[] args) {
final MyCache myCache = new MyCache();
for (int i = 0; i < 3; i++) {
final int tempI = i;
new Thread(new Runnable() {
@Override
public void run() {
myCache.set(tempI+"",tempI);
}
},"A"+String.valueOf(i)).start();
}
for (int i = 0; i < 3; i++) {
final int tempI = i;
new Thread(new Runnable() {
@Override
public void run() {
myCache.get(tempI+"");
}
},"A"+String.valueOf(i)).start();
}
}
}
输出:
A1尝试获取写锁!
A0尝试获取写锁!
A1开始写
A2尝试获取写锁!
A1写完了:1
A0尝试获取读锁!
A0开始写
A0写完了:0
A2开始写
A1尝试获取读锁!
A2写完了:2
A2尝试获取读锁!
A0开始读
A0读完了:0
A2开始读
A1开始读
A2读完了:2
A1读完了:1
Condition
java.util.concurrent.locks.Condition
接口提供一个线程挂起执行的能力,直到给定的条件为真。 Condition
对象必须绑定到Lock
,并使用newCondition()
方法获取对象。
Condition类的方法
以下是Condition
类中可用的重要方法的列表。
序号 | 方法名称 | 描述 |
---|---|---|
1 | public void await() |
使当前线程等待,直到发出信号或中断信号。 |
2 | public boolean await(long time, TimeUnit unit) |
使当前线程等待直到发出信号或中断,或指定的等待时间过去。 |
3 | public long awaitNanos(long nanosTimeout) |
使当前线程等待直到发出信号或中断,或指定的等待时间过去。 |
4 | public long awaitUninterruptibly() |
使当前线程等待直到发出信号。 |
5 | public long awaitUntil() |
使当前线程等待直到发出信号或中断,或者指定的最后期限过去。 |
6 | public void signal() |
唤醒一个等待线程。 |
7 | public void signalAll() |
唤醒所有等待线程。 |
示例:
//待补充。
Collections
LinkedBlockingQueue
BlockingQueue
java.util.concurrent.BlockingQueue
接口是Queue
接口的子接口,另外还支持诸如在检索元素之前等待队列变为非空的操作,并在存储元素之前等待队列中的空间变得可用 。
BlockingQueue接口中的方法
序号 | 方法 | 描述 |
---|---|---|
1 | boolean add(E e) |
将指定的元素插入到此队列中,如果可以立即执行此操作,而不会违反容量限制,在成功时返回true ,并且如果当前没有空间可用,则抛出IllegalStateException 。 |
2 | boolean contains(Object o) |
如果此队列包含指定的元素,则返回true 。 |
3 | int drainTo(Collection<? super E> c) |
从该队列中删除所有可用的元素,并将它们添加到给定的集合中。 |
4 | int drainTo(Collection<? super E> c, int maxElements) |
最多从该队列中删除给定数量的可用元素,并将它们添加到给定的集合中。 |
5 | boolean offer(E e) |
将指定的元素插入到此队列中,如果可以立即执行此操作而不违反容量限制,则成功返回true ,如果当前没有空间可用,则返回false 。 |
6 | boolean offer(E e, long timeout, TimeUnit unit) |
将指定的元素插入到此队列中,等待指定的等待时间(如有必要)才能使空间变得可用。 |
7 | E poll(long timeout, TimeUnit unit) |
检索并删除此队列的头,等待指定的等待时间(如有必要)使元素变为可用。 |
8 | void put(E e) |
将指定的元素插入到此队列中,等待空间/容量可用。 |
9 | int remainingCapacity() |
返回此队列可理想地(在没有内存或资源约束的情况下)接受而不阻止的附加元素数,如果没有内在限制则返回Integer.MAX_VALUE 。 |
10 | boolean remove(Object o) |
从该队列中删除指定元素的单个实例(如果存在)。 |
11 | E take() |
检索并删除此队列的头,如有必要,等待元素可用。 |
示例:
public class Producer implements Runnable {
private BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
int nextInt = ThreadLocalRandom.current().nextInt(0,10);
//将指定的元素插入到此队列中,等待空间/容量可用。
queue.put(nextInt);
System.out.println("put:"+nextInt);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Consumer implements Runnable {
private BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
//检索并删除此队列的头,如有必要,等待元素可用。
System.out.println("take:" +queue.take());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
输出:
put:6
put:1
put:3
take:6
take:1
take:3
ConcurrentMap
java.util.concurrent.ConcurrentMap
接口是Map
接口的子接口,支持底层Map
变量上的原子操作。
ConcurrentMap接口中的方法
序号 | 方法 | 描述 |
---|---|---|
1 | default V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) |
尝试计算指定键及其当前映射值的映射(如果没有当前映射,则为null )。 |
2 | default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) |
如果指定的键尚未与值相关联(或映射到null ),则尝试使用给定的映射函数计算其值,并将其输入到此映射中,除非为null 。 |
3 | default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) |
如果指定键的值存在且非空,则尝试计算给定键及其当前映射值的新映射。 |
4 | default void forEach(BiConsumer<? super K,? super V> action) |
对此映射中的每个条目执行给定的操作,直到所有条目都被处理或操作引发异常。 |
5 | default V getOrDefault(Object key, V defaultValue) |
返回指定键映射到的值,如果此映射不包含该键的映射,则返回defaultValue 。 |
6 | default V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) |
如果指定的键尚未与值相关联或与null 相关联,则将其与给定的非空值相关联。 |
7 | V putIfAbsent(K key, V value) |
如果指定的键尚未与值相关联,请将其与给定值相关联。 |
8 | boolean remove(Object key, Object value) |
仅当当前映射到给定值时才删除键的条目。 |
9 | V replace(K key, V value) |
仅当当前映射到某个值时才替换该项的条目。 |
10 | boolean replace(K key, V oldValue, V newValue) |
仅当当前映射到给定值时才替换键的条目。 |
11 | default void replaceAll(BiFunction<? super K,? super V,? extends V> function) |
将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常。 |
示例:
public static void main(final String[] arguments){
Map<String,String> map = new ConcurrentHashMap<String, String>();
map.put("1", "One");
map.put("2", "Two");
map.put("3", "Three");
map.put("5", "Five");
map.put("6", "Six");
System.out.println("Initial ConcurrentHashMap: "+map);
Iterator<String> iterator = map.keySet().iterator();
try{
while(iterator.hasNext()){
String key = iterator.next();
if(key.equals("3")) {
map.put("4", "Four");
}
}
}catch(ConcurrentModificationException cme){
cme.printStackTrace();
}
System.out.println("ConcurrentHashMap after modification: "+map);
map = new HashMap<String, String>();
map.put("1", "One");
map.put("2", "Two");
map.put("3", "Three");
map.put("5", "Five");
map.put("6", "Six");
System.out.println("Initial HashMap: "+map);
iterator = map.keySet().iterator();
try{
while(iterator.hasNext()){
//没加锁导致遍历读写不一致if (modCount != expectedModCount)
String key = iterator.next();
if(key.equals("3")) {
map.put("4", "Four");
}
}
System.out.println("HashMap after modification: "+map);
}catch(ConcurrentModificationException cme){
cme.printStackTrace();
}
}
}
CopyOnWriteArrayList
不仅仅是为写操作加了锁,还实现了写时复制(也就是我们平时说的读写分离)。
写时复制
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,
而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后向新的容器Object[] newElements里添加元素。添加元素后,再将原容器的引用指向新的容器setArray(newElements)。
这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
适用与读多写少的情况下。
ConcurrentHashMap
//待补充,原理和使用
CopyOnWriteArraySet
Executor
java.util.concurrent.Executor
接口是支持启动新任务的一个简单接口。
public static void main(String[] args) {
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
threadPool.execute(new AmyHaborRunnable("艾米团长"));
threadPool.shutdown();
}
输出:
ERROR - ----艾米团长尝试获取池寒枫练习长剑----
ERROR - ----艾米团长获取雷葛老师学习魔法----
ERROR - ----雷葛,池寒枫给艾米团长教学----
ExecutorService
ExecutorService接口的方法
序号 | 方法 | 描述 |
---|---|---|
1 | boolean awaitTermination(long timeout, TimeUnit unit) |
阻止所有任务在关闭请求完成后执行,或发生超时,或当前线程中断,以先到者为准。 |
2 | <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) |
执行给定的任务,返回持有它们的状态和结果的所有完成的列表。 |
3 | <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) |
执行给定的任务,返回在所有完成或超时到期时持有其状态和结果的列表,以先发生者为准。 |
4 | <T> T invokeAny(Collection<? extends Callable<T>> tasks) |
执行给定的任务,返回一个已经成功完成的结果(即不抛出异常)。 |
5 | <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) |
执行给定的任务,返回一个已经成功完成的结果(即,不抛出异常),如果有则在给定的超时过去之前。 |
6 | boolean isShutdown() |
如果执行程序已关闭,则返回true 。 |
7 | boolean isTerminated() |
如果所有任务在关闭后完成,则返回true 。 |
8 | void shutdown() |
启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。 |
9 | List<Runnable> shutdownNow() |
尝试停止所有主动执行的任务,停止等待任务的处理,并返回正在等待执行的任务列表。 |
10 | <T> Future<T> submit(Callable<T> task) |
提交值返回任务以执行,并返回代表任务待处理结果。 |
11 | Future<?> submit(Runnable task) |
提交一个可运行的任务执行,并返回一个表示该任务的Future 。 |
12 | <T> Future<T> submit(Runnable task, T result) |
提交一个可运行的任务执行,并返回一个表示该任务的Future 。 |
示例:
public static void main(String[] args) {
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
try {
threadPool.submit(new AmyHaborRunnable("艾米团长"));
System.out.println("Shutdown executor");
threadPool.shutdown();
threadPool.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
System.err.println("tasks interrupted");
}finally {
if (!threadPool.isTerminated()){
System.err.println("cancel non-finished tasks");
}
//调用shutdownNow()会把正在运行中的任务强行终止掉,目前我们正在运行中的任务在睡眠,因此会终止睡眠
//java.lang.InterruptedException: sleep interrupted
threadPool.shutdownNow();
System.out.println("shutdown finished");
}
}
输出:
Shutdown executor
ERROR - ----艾米团长尝试获取池寒枫练习长剑----
shutdown finished
ERROR - ----艾米团长获取雷葛老师学习魔法----
ERROR - ----雷葛,池寒枫给艾米团长教学----
cancel non-finished tasks
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:360)
at com.juc.AmyHaborRunnable.run(AmyHaborRunnable.java:56)
ScheduledExecutorService
java.util.concurrent.ScheduledExecutorService
接口是ExecutorService
接口的子接口,并支持将来和/或定期执行任务。
ScheduledExecutorService接口的方法
序号 | 方法 | 描述 |
---|---|---|
1 | ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) |
创建并执行在给定延迟后启用ScheduledFuture 。 |
2 | ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) |
创建并执行在给定延迟后启用的单次操作。 |
3 | ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) |
创建并执行在给定的初始延迟之后,随后以给定的时间段首先启用的周期性动作; 那就是执行会在initialDelay 之后开始,然后是initialDelay + period ,然后是initialDelay + 2 * period ,等等。 |
4 | ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) |
创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 |
示例:
public class ScheduledExecutorTest {
public static void main(String[] args) {
final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
//为什么不能用Callable作为任务?只能执行一次是什么原因呢?
//ScheduledFuture<?> scheduledFuture = executorService.scheduleAtFixedRate(new FutureTask<String>(new GreenDragon()), 3, 3, TimeUnit.SECONDS);
//3s执行一次任务
final ScheduledFuture<?> scheduledFuture = executorService.scheduleAtFixedRate(new Breathe(), 3, 3, TimeUnit.SECONDS);
//5次执行后关闭调度任务
executorService.schedule(new Runnable() {
@Override
public void run() {
//表示取消任务,true表示可以中断取消任务,而不是等任务执行完后再取消
scheduledFuture.cancel(true);
executorService.shutdown();
}
},13,TimeUnit.SECONDS);
}
}
newFixedThreadPool
以通过调用Executors
类的static newFixedThreadPool()
方法获得一个固定线程池。
说明
- 最多
2
个线程将处于活动状态。 - 如果提交了两个以上的线程,那么它们将保持在队列中,直到线程可用。
- 如果一个线程由于执行关闭期间的失败而终止,则执行器尚未被调用,则创建一个新线程。
- 线程会一直存在,直到池关闭。
示例:
public static void main(final String[] arguments) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
// Cast the object to its class type
ThreadPoolExecutor pool = (ThreadPoolExecutor) executor;
//Stats before tasks execution
System.out.println("Core threads: " + pool.getCorePoolSize());
System.out.println("Largest executions: "
+ pool.getLargestPoolSize());
System.out.println("Maximum allowed threads: "
+ pool.getMaximumPoolSize());
System.out.println("Current threads in pool: "
+ pool.getPoolSize());
System.out.println("Currently executing threads: "
+ pool.getActiveCount());
System.out.println("Total number of threads(ever scheduled): "
+ pool.getTaskCount());
executor.submit(new Task());
executor.submit(new Task());
//Stats after tasks execution
System.out.println("Core threads: " + pool.getCorePoolSize());
System.out.println("Largest executions: "
+ pool.getLargestPoolSize());
System.out.println("Maximum allowed threads: "
+ pool.getMaximumPoolSize());
System.out.println("Current threads in pool: "
+ pool.getPoolSize());
System.out.println("Currently executing threads: "
+ pool.getActiveCount());
System.out.println("Total number of threads(ever scheduled): "
+ pool.getTaskCount());
executor.shutdown();
}
static class Task implements Runnable {
public void run() {
try {
Long duration = (long) (Math.random() * 5);
System.out.println("Running Task! Thread Name: " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(duration);
System.out.println("Task Completed! Thread Name: "+ Thread.currentThread().getName());
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
Core threads: 2
Largest executions: 0
Maximum allowed threads: 2
Current threads in pool: 0
Currently executing threads: 0
Total number of threads(ever scheduled): 0
Core threads: 2
Largest executions: 2
Maximum allowed threads: 2
Current threads in pool: 2
Currently executing threads: 2
Total number of threads(ever scheduled): 2
Running Task! Thread Name: pool-1-thread-1
Running Task! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-1
newCachedThreadPool
通过调用Executors
类的静态newCachedThreadPool()
方法可以获得缓存的线程池。
newCachedThreadPool()
方法创建一个具有可扩展线程池的执行器。
这样的执行者适合于发起许多短命的任务的应用程序。
newScheduledThreadPool
通过调用Executors
类的static newScheduledThreadPool()
方法获得一个调度的线程池 。
newSingleThreadExecutor
通过调用Executors
类的static newSingleThreadExecutor()
方法获得单个线程池。
ThreadPoolExecutor
推荐使用:灵活自由控制。
待整理。
fork-join
fork-join
框架允许在几个工作进程中断某个任务,然后等待结果组合它们。 它在很大程度上利用了多处理器机器的生产能力。
Fork是一个进程,其中任务将其分成可以并发执行的较小且独立的子任务。
连接(Join
)是子任务完成执行后任务加入子任务的所有结果的过程,否则它会持续等待 .
ForkJoinPool它是一个特殊的线程池,旨在使用fork-and-join
任务拆分。
RecursiveAction
表示不返回任何值的任务。
RecursiveTask
表示返回值的任务。
public class ForkJoinTest {
public static void main(final String[] arguments) throws InterruptedException, ExecutionException {
int nThreads = Runtime.getRuntime().availableProcessors();
System.out.println(nThreads);
int[] numbers = new int[100];
//初始化
for(int i=0; i< numbers.length; i++){
numbers[i] = i;
}
//并行级别为nThreads个CPU。
ForkJoinPool forkJoinPool = new ForkJoinPool(nThreads);
Long result = forkJoinPool.invoke(new Sum(numbers,0,numbers.length));
System.out.println(result);
}
static class Sum extends RecursiveTask<Long> {
int low;
int high;
int[] array;
Sum(int[] array, int low, int high) {
this.array = array;
this.low = low;
this.high = high;
}
//重写compute(),里面的逻辑判断是否要fork ,join
@Override
protected Long compute() {
if(high - low <= 10) {
long sum = 0;
for(int i=low; i < high; ++i) {
sum += array[i];
}
return sum;
} else {
int mid = low + (high - low) / 2;
Sum left = new Sum(array, low, mid);
Sum right = new Sum(array, mid, high);
//将任务分解为子任务。
left.fork();
//继续递归
long rightResult = right.compute();
//连接(Join)是子任务完成执行后任务加入子任务的所有结果的过程,否则它会持续等待。
long leftResult = left.join();
return leftResult + rightResult;
}
}
}
}
4
4950
Tools
CountDownLatch
让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒。
举例: 掩护部队负责掩护,必须要等到所有部队都撤了才行撤。
假设掩护部队是main线程,要等所有部队走完之后撤退。
示例:
public static void main(String[] args) throws InterruptedException {
int army = 10;
final CountDownLatch latch = new CountDownLatch(army);
for (int i = 0; i < army; i++) {
final int No = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("division " + No + " fall back,cover me!");
latch.countDown();
}
}).start();
}
//cover division (wait)
latch.await();
System.out.println("all division already fall back!");
}
输出:
division 1 fall back,cover me!
division 0 fall back,cover me!
division 3 fall back,cover me!
division 2 fall back,cover me!
division 8 fall back,cover me!
division 9 fall back,cover me!
division 4 fall back,cover me!
division 7 fall back,cover me!
division 5 fall back,cover me!
division 6 fall back,cover me!
all division already fall back!
CyclicBarrier
字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是, 让一组线程到达一个屏障(也可以叫同步点)时被阻塞, 直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBarrier的await()方法 。
public class CyclicBarrierTest {
public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
final CyclicBarrier cyclicBarrier = new CyclicBarrier(7, new Runnable() {
@Override
public void run() {
System.out.println("爷爷带领7兄弟打妖怪!");
}
});
for (int i = 1; i <= 7; i++) {
final int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(finalI + "娃出生了");
try {
cyclicBarrier.await();
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
输出:
1娃出生了
2娃出生了
3娃出生了
4娃出生了
5娃出生了
6娃出生了
7娃出生了
爷爷带领7兄弟打妖怪!
Semaphore
信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
在信号量上我们定义两种操作:
- acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。
- release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
实例: 停车场有3个车位,陆陆续续一共来了6辆车
public class SemaphoreTest {
public static void main(String[] args) {
final Semaphore carportNum = new Semaphore(2);
for (int i = 0; i < 5; i++) {
final int finalI = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
carportNum.acquire();
System.out.println("车辆"+ finalI +"停靠!");
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(0,5));
System.out.println("车辆"+ finalI +"离开!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
carportNum.release();
}
}
}).start();
}
}
}
输出:
车辆0停靠!
车辆0离开!
车辆1停靠!
车辆2停靠!
车辆1离开!
车辆3停靠!
车辆3离开!
车辆4停靠!
车辆4离开!
车辆2离开!
CyclicBarrier 与 CountDownLatch 区别
- CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
- CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。
疑问
Q:thread.join()方法是干嘛用的?
Q:发生死锁的原因都有哪些,有什么经典场景?解决死锁的方法有哪些?
Q:删除此线程局部变量的当前线程的值是用remove() 方法好还是用set(null)呢?
Q: lock.tryLock(1,TimeUnit.SECONDS);的使用?获取不到锁超时后会怎么处理?
public class MercenaryTask {
private static final Logger logger = LoggerFactory.getLogger(MercenaryTask.class);
private final Lock lock = new ReentrantLock();
public void adoptTask() throws InterruptedException {
try {
lock.tryLock(1,TimeUnit.SECONDS);
int level = ThreadLocalRandom.current().nextInt(1, 4);
logger.error(Thread.currentThread().getName()+"领取了一个"+level+"级别任务!");
TimeUnit.SECONDS.sleep(level);
logger.error(Thread.currentThread().getName()+"接收了任务成功!");
} catch (Exception e){
logger.error(Thread.currentThread().getName()+"接收任务失败!");
}
finally {
lock.unlock();
}
}
}
输出:
ERROR - mercenary 0领取了一个2级别任务!
ERROR - mercenary 1领取了一个3级别任务!
ERROR - mercenary 2领取了一个2级别任务!
ERROR - mercenary 0接收了任务成功!
ERROR - mercenary 2接收了任务成功!
Exception in thread "mercenary 2" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at com.juc.atomic.MercenaryTask.adoptTask(MercenaryTask.java:37)
at com.juc.atomic.MercenaryRunnable.run(MercenaryRunnable.java:24)
at java.lang.Thread.run(Thread.java:745)
ERROR - mercenary 1接收了任务成功!
Exception in thread "mercenary 1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at com.juc.atomic.MercenaryTask.adoptTask(MercenaryTask.java:37)
at com.juc.atomic.MercenaryRunnable.run(MercenaryRunnable.java:24)
at java.lang.Thread.run(Thread.java:745)
Q:tryLock方法可以用来实现加锁限时等待 ,可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。我们可以将这种方法用来解决死锁问题。我们通过while循环判断tryLock()是否成功,成功走入下一个逻辑,不成功则可以进入等待或者处理其他事情,不停的调试,一直等到获取到相应的资源为止。也可以设置tryLock的超时等待时间tryLock(long timeout,TimeUnit unit),也就是说一个线程在指定的时间内没有获取锁,那就会返回false,就可以再去做其他事了。
报错:Exception in thread "mercenary2" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
这个报错的原因是线程mercenary2试图去释放不属于它的锁导致的错误,因此,当这个线程如果获取不到锁的时候,要么在while循环里间隔时间重试,要么返回,不然让代码走入不属于它的锁的释放锁的逻辑中去。
ERROR - mercenary1wait!
ERROR - mercenary2wait!
log4j:ERROR No output stream or file set for the appender named [logfile].
ERROR - mercenary0领取了一个任务!
ERROR - mercenary1领取了一个任务!
Exception in thread "mercenary1" java.lang.IllegalMonitorStateException
ERROR - mercenary2领取了一个任务!
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at com.juc.atomic.MercenaryTask.adoptTask(MercenaryTask.java:44)
at com.juc.atomic.MercenaryRunnable.run(MercenaryRunnable.java:24)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "mercenary2" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at com.juc.atomic.MercenaryTask.adoptTask(MercenaryTask.java:44)
at com.juc.atomic.MercenaryRunnable.run(MercenaryRunnable.java:24)
at java.lang.Thread.run(Thread.java:745)
错误代码:
public class MercenaryTask {
private static final Logger logger = LoggerFactory.getLogger(MercenaryTask.class);
private final Lock lock = new ReentrantLock();
public void adoptTask() throws InterruptedException {
try {
//lock.lock();
//lock.lockInterruptibly();
//boolean isLock = lock.tryLock(1, TimeUnit.SECONDS);
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
//int level = ThreadLocalRandom.current().nextInt(1, 2);
//TimeUnit.MILLISECONDS.sleep(500);
logger.error(Thread.currentThread().getName()+"wait!");
}
TimeUnit.SECONDS.sleep(6);
logger.error(Thread.currentThread().getName()+"领取了一个任务!");
//logger.error(Thread.currentThread().getName()+"领取了一个"+level+"级别任务!");
//logger.error(Thread.currentThread().getName()+"接收了任务成功!");
} catch (Exception e){
logger.error(Thread.currentThread().getName()+"接收任务失败!");
}
finally {
lock.unlock();
}
}
}
Q:Condition c1 = lock.newCondition(); 的c1.await()和c1.signal(); 方法还是不能理解,是指当await()时就会停止在那里么?一直等待被唤醒?一般现实中的使用场景是什么?顺序调用线程执行?
Q:ConcurrentNavigableMap 的用途?一般使用场景?
Q://为什么不能用Callable作为任务?只能执行一次是什么原因呢?用Runnabel就不会。
ScheduledFuture<?> scheduledFuture = executorService.scheduleAtFixedRate(new FutureTask<String>(new GreenDragon()), 3, 3, TimeUnit.SECONDS);
其他:
因为Element时间限制不能选择当天,想选择要如下操作 - 8.64e7
pickerStart: {
disabledDate: time => {
let maxPrePaymentRepayDate = this.form.maxPrePaymentRepayDate
let maxPrePaymentRepayDateTime = new Date(maxPrePaymentRepayDate).getTime()
// 如果大于当前时间,则需要取值时间的区间范围
if (maxPrePaymentRepayDateTime > new Date().getTime()) {
return time.getTime() < new Date(this.endDateValDate).getTime() - 8.64e7 || time.getTime() > maxPrePaymentRepayDateTime - 8.64e7
} else if (this.endDateValDate) {
return time.getTime() < new Date(this.endDateValDate).getTime() - 8.64e7
}
}
},