从建立以来,JAVA就支持核心的并发概念如线程和锁。这篇文章会帮助从事多线程编程的JAVA开发人员理解核心的并发概念以及如何使用它们。java
(博主将在其中加上本身的理解以及本身想出的例子做为补充)面试
原子性:原子操做是指该系列操做要么所有执行,要么所有不执行,所以不存在部分执行的状态。
可见性:一个线程可以看见另外一个线程所带来的改变。
当多个线程在一个共享的资源上执行一组操做时,会产生竞争。根据各个线程执行操做的顺序可能产生多个不一样结果。下面的代码不是线程安全的,value
可能会被初始化屡次,由于check-then-act
型(先判断是否为null
,而后初始化)的惰性初始化并不是原子性操做。编程
class Lazy <T> { private volatile T value; T get() { if (value == null) value = initialize(); return value; } }
当两个或多个线程在没有同步的状况下试图访问同一个非final
变量时,会产生数据冲突。不使用同步可能使数据的改变对别的线程不可见,从而可能读取过时的数据,并致使如无限循环,数据结构损坏和不许确的计算等后果。下面这段代码可能会致使无限循环,由于读者线程可能永远都没有看到写入者线程作出的更改:安全
class Waiter implements Runnable { private boolean shouldFinish; void finish() { shouldFinish = true; } public void run() { long iteration = 0; while (!shouldFinish) { iteration++; } System.out.println("Finished after: " + iteration); } } class DataRace { public static void main(String[] args) throws InterruptedException { Waiter waiter = new Waiter(); Thread waiterThread = new Thread(waiter); waiterThread.start(); waiter.finish(); waiterThread.join(); } }
JAVA内存模型是根据读写字段等操做来定义的,并在控制器上进行同步。操做根据happens-before关联
排序,这解释了一个线程什么时候可以看到另外一个线程操做的结果,以及是什么构成了一个同步良好的程序。微信
happens-before关联
有如下属性:数据结构
Thread#start
的方法在线程的全部操做以前执行volatile
变量的操做在全部后序读取该变量的操做以前执行。final
型变量的操做在发布该对象的引用以前执行Thread#join
方法返回以前执行上图中,Action X
在Action Y
以前执行,所以线程1
在Action X
之前执行的全部操做对线程2
在Action Y
以后的全部操做可见。多线程
synchronized
关键字用来防止不一样的线程同时进入一段代码。它确保了你的操做的原子性,由于你只有得到了这段代码的锁才能进入这段代码,使得该锁所保护的数据能够在独占模式下操做。除此之外,它还确保了别的线程在得到了一样的锁以后,可以观察到以前线程的操做。并发
class AtomicOperation { private int counter0; private int counter1; void increment() { synchronized (this) { counter0++; counter1++; } } }
synchronized
关键字也能够在方法层上声明。app
静态方法:将持有该方法的类做为加锁对象
非静态方法:加锁this
指针
锁是可重入的。因此若是一个线程已经持有了该锁,它能够一直访问该锁下的任何内容:高并发
class Reentrantcy { synchronized void doAll() { doFirst(); doSecond(); } synchronized void doFirst() { System.out.println("First operation is successful."); } synchronized void doSecond() { System.out.println("Second operation is successful."); } }
争用程度影响如何得到控制器:
初始化:刚刚建立,没有被获取
biased:锁下的代码只被一个线程执行,不会产生冲突
thin:控制器被几个线程无冲突的获取。使用CAS(compare and swap)
来管理这个锁
fat:产生冲突。JVM请求操做系统互斥,并让操做系统调度程序处理线程停放和唤醒。
wait/notify/notifyAll
方法在Object
类中声明。wait
方法用来将线程状态改变为WAITING
或是TIMED_WAITING
(若是传入了超时时间值)。要想唤醒一个线程,下列的操做均可以实现:
notify
方法,唤醒在控制器上等待的任意的一个线程notifyAll
方法,唤醒在该控制器上等待的全部线程Thread#interrupt
方法被调用,在这种状况下,会抛出InterruptedException
最经常使用的一个模式是一个条件性循环:
class ConditionLoop { private boolean condition; synchronized void waitForCondition() throws InterruptedException { while (!condition) { wait(); } } synchronized void satisfyCondition() { condition = true; notifyAll(); } }
wait/notify/notifyAll
方法,你首先须要获取对象的锁notifyAll
而致使的顺序问题。并且它还防止线程因为伪唤起继续执行。notify/notifyAll
以前已经知足了等待条件。若是不这样的话,将只会发出一个唤醒通知,可是在该等待条件上的线程永远没法跳出其等待循环。博主备注:这里解释一下为什么建议将wait
放在条件性循环中、假设如今有一个线程,并无将wait放入条件性循环中,代码以下:
class UnconditionLoop{ private boolean condition; synchronized void waitForCondition() throws InterruptedException{ //.... wait(); } synchronized void satisfyCondition(){ condition = true; notifyAll(); } }
假设如今有两个线程分别同时调用waitForCondition
和satisfyCondition()
,而调用satisfyCondition的方法先调用完成,而且发出了notifyAll
通知。鉴于waitForCondition
方法根本没有进入wait
方法,所以它就错过了这个解挂信号,从而永远没法被唤醒。
这时你可能会想,那就使用if
判断一下条件呗,若是条件还没知足,就进入挂起状态,一旦接收到信号,就能够直接执行后序程序。代码以下:
class UnconditionLoop{ private boolean condition; private boolean condition2; synchronized void waitForCondition() throws InterruptedException{ //.... if(!condition){ wait(); } } synchronized void waitForCondition2() throws InterruptedException{ //.... if(!condition2){ wait(); } } synchronized void satisfyCondition(){ condition = true; notifyAll(); } synchronized void satisfyCondition2(){ condition2 = true; notifyAll(); } }
那让咱们再假设这个 方法中还存在另外一个condition,而且也有其对应的等待和唤醒方法。假设这时satisfyConsition2
被知足并发出nofityAll
唤醒全部等待的线程,那么waitForCondition
和waitForCondition2
都将会被唤醒继续执行。而waitForCondition
的条件并无被知足!
所以在条件中循环等待信号是有必要的。
volatile
关键字解决了可见性问题,而且使值的更改原子化,由于这里存在一个happens-before
关联:对volatile
值的更改会在全部后续读取该值的操做以前执行。所以,它确保了后序全部的读取操做可以看到以前的更改。
class VolatileFlag implements Runnable { private volatile boolean shouldStop; public void run() { while (!shouldStop) { //do smth } System.out.println("Stopped."); } void stop() { shouldStop = true; } public static void main(String[] args) throws InterruptedException { VolatileFlag flag = new VolatileFlag(); Thread thread = new Thread(flag); thread.start(); flag.stop(); thread.join(); } }
java.util.concurrent.atomic
包中包含了一组支持在单一值上进行多种原子性操做的类,从而从加锁中解脱出来。
使用AtomicXXX
类,能够实现原子性的check-then-act
操做:
class CheckThenAct { private final AtomicReference<String> value = new AtomicReference<>(); void initialize() { if (value.compareAndSet(null, "Initialized value")) { System.out.println("Initialized only once."); } } }
AtomicInteger
和AtomicLong
都用increment/decrement
操做:
class Increment { private final AtomicInteger state = new AtomicInteger(); void advance() { int oldState = state.getAndIncrement(); System.out.println("Advanced: '" + oldState + "' -> '" + (oldState + 1) + "'."); } }
若是你想要建立一个计数器,可是并不须要原子性的读操做,可使用LongAdder
替代AtomicLong/AtomicInteger
,LongAdder
在多个单元格中维护该值,并在须要时对这些值同时递增,从而在高并发的状况下性能更好。
在线程中包含数据而且不须要锁定的一种方法是使用ThreadLocal存储。从概念上将,ThreadLocal就好像是在每一个线程中都有本身版本的变量。ThreadLocal经常使用来存储只属于线程本身的值,好比当前的事务以及其它资源。并且,它还能用来维护单个线程专有的计数器,统计或是ID生成器。
class TransactionManager { private final ThreadLocal<Transaction> currentTransaction = ThreadLocal.withInitial(NullTransaction::new); Transaction currentTransaction() { Transaction current = currentTransaction.get(); if (current.isNull()) { current = new TransactionImpl(); currentTransaction.set(current); } return current; } }
想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注个人微信公众号!将会不按期的发放福利哦~