volatile 是一種輕量級的同步機制。java
JMM(Java 內存模型)是一種抽象的概念,描述了一組規則或規範,定義了程序中各個變量的訪問方式。linux
JVM運行程序的實體是線程,每一個線程建立時 JVM 都會爲其建立一個工做內存,是線程的私有數據區域。JMM中規定全部變量都存儲在主內存,主內存是共享內存。線程對變量的操做在工做內存中進行,首先將變量從主內存拷貝到工做內存,操做完成後寫會主內存。不一樣線程間沒法訪問對方的工做內存,線程通訊(傳值)經過主內存來完成。小程序
JMM 對於同步的規定:api
原子性是不可分割,某個線程正在作某個具體業務時,中間不能夠被分割,要麼所有成功,要麼所有失敗。數組
重排序:計算機在執行程序時,爲了提升性能,編譯器和處理器經常對指令作重排序,源代碼通過編譯器優化重排序、指令並行重排序、內存系統的重排序以後獲得最終執行的指令。緩存
在單線程中保證程序最終執行結果和代碼執行順序執行結果一致。安全
多線程中線程交替執行,因爲重排序,兩個線程中使用的變量可否保證一致性沒法肯定,結果沒法肯定。bash
處理器在處理重排序時須要考慮數據的依賴性。服務器
volatile 實現禁止指令重排序,避免多線程環境下程序亂序執行。是經過內存屏障指令來執行的,經過插入內存屏障禁止在內存屏障後的指令執行重排序優化,並強制刷出緩存數據,保證線程能讀取到這些數據的最新版本。多線程
class MyData {
//volatile int number = 0;//case2
//int number=0; //case1
public void change() {
number = 60;
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData data=new MyData();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t come in");
try{ TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
data.change();
System.out.println(Thread.currentThread().getName()+"\t updated number value:"+data.number);
},"A").start();
while(data.number==0){}
System.out.println(Thread.currentThread().getName()+"\t over, get number:"+data.number);
}
}
複製代碼
當咱們使用case1的時候,也就是number沒有volatile修飾的時候,運行結果:
A come in
A updated number value:60
複製代碼
而且程序沒有執行結束,說明在main線程中因爲不能保證可見性,一直在死循環。
當執行case2的時候:
A come in
A updated number value:60
main over, get number:60
複製代碼
保證了可見性,所以main成功結束。
class MyData {
volatile int number = 0;
public void change() {
number = 60;
}
public void addOne() {
number++;
}
}
public class VolatileDemo {
public static void main(String[] args) {
case2();
}
//驗證原子性
public static void case2() {
MyData myData = new MyData();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.addOne();
}
}, String.valueOf(i)).start();
}
while(Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t number value:"+myData.number);
}
}
複製代碼
最終輸出結果能夠發現並非 20000,且屢次輸出結果並不一致,所以說明 volatile 不能保證原子性。
DCL模式的單例模式
public class Singleton {
private static Singleton instance=null;
private Singleton(){
System.out.println(Thread.currentThread().getName()+" constructor");
}
//DCL 雙端檢鎖機制
public static Singleton getInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null)
instance=new Singleton();
}
}
return instance;
}
}
複製代碼
DCL 機制不能徹底保證線程安全,由於有指令重排序的存在。
緣由在於instance = new Singleton(); 能夠分爲三步:
1. memory=allocate();//分配內存空間
2. instance(memory);//初始化對象
3. instance=memory;//設置instance指向分配的內存地址,分配成功後,instance!=null
複製代碼
因爲步驟2和步驟3不存在數據依賴關係,且不管重排序與否執行結果在單線程中沒有改變,所以這兩個步驟的重排序是容許的。也就是說指令重排序只會保證單線程串行語義的一致性(as-if-serial),可是不會關心多線程間的語義一致性。
所以,重排序以後,先執行3會致使instance!=null,可是對象還未被初始化。此時,別的線程在調用時,獲取了一個未初始化的對象。
所以,在聲明 instance 時,使用 volatile 進行修飾,禁止指令重排序。
private static volatile Singleton instance = null;
複製代碼
CAS 的全程是 CompareAndSwap,是一條 CPU 併發原語。它的功能是判斷內存某個位置的值是否爲預期值,若是是則更新爲新的值,這個過程是原子的。
CAS 的做用是比較當前工做內存中的值和主內存中的值,若是相同則執行操做,不然繼續比較直到主內存和工做內存中的值一致爲止。主內存值爲V,工做內存中的預期值爲A,要修改的更新值爲B,當且僅當A和V相同,將V修改成B,不然什麼都不作。
在原子類中,CAS 操做都是經過 Unsafe 類來完成的。
//AtomicInteger i++
public final int getAndIncrement(){
return unsafe.getAndAddInt(this,valueoffset,1);
}
複製代碼
其中 this 是當前對象, valueoffset 是一個 long ,表明地址的偏移量。
//AtomicInteger.java
private static final Unsafe unsfae=Unsafe.getUnsafe();//unsafe對象
private static final long valueOffset;//地址偏移量
static{
try{
valueoffset=unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value");
}catch(Excepthion ex){throw new Error(ex);}
}
private volatile int value;//存儲的數值
複製代碼
Unsafe 類是 rt.jar 下的 sun.misc 包下的一個類,基於該類能夠直接操做特定內存的數據。
Java方法沒法直接訪問底層系統,須要使用 native 方法訪問,Unsafe 類的內部方法都是 native 方法,其中的方法能夠像C的指針同樣直接操做內存,Java 中的 CAS 操做的執行都依賴於 Unsafe 類的方法。
該變量表示變量值在內存中的偏移地址, Unsafe 就是根據內存偏移地址獲取數據的。
CAS 併發源於體如今 Java 中就是 Unsafe 類的各個方法。調用該類中的 CAS 方法,JVM會幫咱們實現出 CAS 彙編指令,這是一種徹底依賴於硬件的功能。
原語是由若干條指令組成的,用於完成某個功能的過程。原語的執行必須是連續的,執行過程不容許被中斷。因此 CAS 是一條 CPU 的原子指令,不會形成數據不一致問題。
下邊是 AtomicInteger 中實現 i++ 功能所調用的 Unsafe 類的函數。
//unsafe.getAndAddInt
public final int getAndAddInt(Object var1,long var2,int var4){
int var5;
do{
//獲取當前的值的地址
var5=this.getIntVolatile(var1,var2);
//var1表明對象,var2和var5分別表明當前對象的真實值和指望值,若是兩者相等,更新爲var5+var4
}while(!this.compareAndSwapInt(var1,var2,var5,var5+var4);
return var5;
}
複製代碼
在 getAndAddInt 函數中,var1 表明了 AtomicInteger 對象, var2 表明了該對象在內存中的地址, var4 表明了指望增長的數值。
首先經過 var1 和 var2 獲取到當前的主內存中真實的 int 值,也就是 var5。
而後經過循環來進行數據更改,當比較到真實值和對象的當前值相等,則更新,退出循環;不然再次獲取當前的真實值,繼續嘗試,直到成功。
在 CAS 中經過自旋而不是加鎖來保證一致性,同時和加鎖相比,提升了併發性。
具體情境來講:線程A和線程B併發執行 AtomicInteger 的自增操做:
CAS 實現一個重要前提須要取出內存中某個時刻的數據並在當下時刻比較並替換,這個時間差會致使數據的變化。
線程1從內存位置V中取出A,線程2也從V中取出A,而後線程2經過一些操做將A變成B,而後又把V位置的數據變成A,此時線程1進行CAS操做發現V中仍然是A,操做成功。儘管線程1的CAS操做成功,可是不表明這個過程沒有問題。
這個問題相似於幻讀問題,經過新增版本號的機制來解決。在這裏可使用 AtomicStampedReference 來解決。
經過 AtomicStampedReference 來解決這個問題。
public class SolveABADemo {
static AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<>(100,1);
new Thread(()->{
int stamp=atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 版本號:"+stamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 版本號:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 版本號:"+atomicStampedReference.getStamp());
},"t1").start();
new Thread(()->{
int stamp=atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 版本號:"+stamp);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean ret=atomicStampedReference.compareAndSet(100,2019,stamp,stamp+1);
System.out.println(Thread.currentThread().getName()+"\t"+ret
+" stamp:"+atomicStampedReference.getStamp()
+" value:"+atomicStampedReference.getReference());
},"t2").start();
}
}
複製代碼
t1 版本號:1
t2 版本號:1
t1 版本號:2
t1 版本號:3
t2 false stamp:3 value:100
複製代碼
這個異常也就是併發修改異常,java.util.ConcurrentModificationException。
致使這個異常的緣由,是集合類自己是線程不安全的。
解決方案:
底層使用了 private transient volatile Object[] array;
CopyOnWriteArrayList 採用了寫時複製、讀寫分離的思想。
public boolean add(E e){
final ReentrantLock lock=this.lock;
try{
//舊數組
Object[] elements = getArray();
int len = elements.length;
//複製新數組
Object[] newElements = Arrays.copyOf(elements, len+1);
//修改新數組
newElements[len] = e;
//更改舊數組引用指向新數組
setArray(newElements);
return true;
}finally{
lock.unlock();
}
}
複製代碼
添加元素時,不是直接添加到當前容器數組,而是複製到新的容器數組,向新的數組中添加元素,添加完以後將原容器引用指向新的容器。
這樣作的好處是能夠對該容器進行併發的讀,而不須要加鎖,由於讀時容器不會添加任何元素。
CopyOnWriteArraySet 自己就是使用 CopyOnWriteArrayList 來實現的。
ReentrantLock 能夠指定構造函數的 boolean 類型獲得公平或非公平鎖,默認是非公平鎖,synchronized也是非公平鎖。
公平鎖是多個線程按照申請鎖的順序獲取鎖,是 FIFO 的。併發環境中,每一個線程在獲取鎖時先查看鎖維護的等待隊列,爲空則戰友,不然加入隊列。
非公平鎖是指多個線程不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。高併發狀況下可能致使優先級反轉或者飢餓現象。併發環境中,上來嘗試佔有鎖,嘗試失敗,再加入等待隊列。
可衝入鎖指的是同一線程外層函數獲取鎖以後,內層遞歸函數自動獲取鎖。也就是線程能進入任何一個它已經擁有的鎖所同步着的代碼塊。
ReentrantLock 和 synchronized 都是可重入鎖。
可重入鎖最大的做用用來避免死鎖。
自旋鎖是指嘗試獲取鎖的線程不會當即阻塞,而是採用循環的方式嘗試獲取鎖。好處是減小線程上下文切換的消耗,缺點是循環時會消耗CPU資源。
實現自旋鎖:
public class SpinLockDemo {
//使用AtomicReference<Thread>來更新當前佔用的 Thread
AtomicReference<Thread> threadAtomicReference=new AtomicReference<>();
public static void main(String[] args) {
SpinLockDemo demo=new SpinLockDemo();
new Thread(()->{
demo.myLock();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.myUnlock();
},"t1").start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
demo.myLock();
demo.myUnlock();
},"t2").start();
}
public void myLock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t come in");
//若是當前佔用的線程爲null,則嘗試獲取更新
while(!threadAtomicReference.compareAndSet(null,thread)){
}
}
public void myUnlock(){
Thread thread=Thread.currentThread();
//釋放鎖,將佔用的線程設置爲null
threadAtomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t unlocked");
}
}
複製代碼
獨佔鎖:該鎖一次只能被一個線程持有,如 ReentrantLock 和 synchronized。
共享鎖:該鎖能夠被多個線程持有。
ReentrantReadWriteLock 中,讀鎖是共享鎖,寫鎖時獨佔鎖。讀讀共享保證併發性,讀寫互斥。
CountDownLatch 的做用是讓一些線程阻塞直到另一些線程完成一系列操做後才被喚醒。
CountDownLatch 在初始時設置一個數值,當一個或者多個線程使用 await() 方法時,這些線程會被阻塞。其他線程調用 countDown() 方法,將計數器減去1,當計數器爲0時,調用 await() 方法被阻塞的線程會被喚醒,繼續執行。
能夠理解爲,等你們都走了,保安鎖門。
CyclicBarrier 是指能夠循環使用的屏障,讓一組線程到達一個屏障時被阻塞,直到最後一個線程到達屏障,屏障纔會開門,被屏障攔截的線程纔會繼續工做,線程進入屏障經過 await() 方法。
能夠理解爲,你們都到齊了,才能開會。
信號量用於:
能夠理解爲,多個車搶停車場的多個車位。當進入車位時,調用 acquire() 方法佔用資源。當離開時,調用 release() 方法釋放資源。
阻塞隊列首先是一個隊列,所起的做用以下:
試圖從空的阻塞隊列中獲取元素的線程將會被阻塞,直到其餘線程向空的隊列中插入新的元素。一樣的,試圖向已滿的阻塞隊列中添加新元素的線程一樣會被阻塞,直到其餘線程從隊列中移除元素使得隊列從新變得空閒起來並後序新增。
阻塞:阻塞是指在某些狀況下會掛起線程,即阻塞,一旦條件知足,被掛起的線程又會自動被喚醒。
優勢:BlockingQueue 能幫助咱們進行線程的阻塞和喚醒,而無需關心什麼時候須要阻塞線程,什麼時候須要喚醒線程。同時兼顧了效率和線程安全。
BlokcingQueue 接口實現了 Queue 接口,該接口有以下的實現類:
方法類型 | 拋出異常 | 特殊值 | 阻塞 | 超時 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,unit) |
檢查 | element() | peek() | 無 | 無 |
add(e)
會拋出異常IllegalStateException: Queue full
;當隊列空,remove()
和element()
會拋出異常NoSuchElementException
offer(e)
會返回 true/false。peek()
會返回隊列元素或者null。put(e)
會阻塞直到成功或中斷;隊列空take()
會阻塞直到成功。class ShareData {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception {
lock.lock();
try {
//判斷
while (number != 0) {
condition.await();
}
//幹活
number++;
System.out.println(Thread.currentThread().getName() + " produce\t" + number);
//通知喚醒
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement()throws Exception{
lock.lock();
try {
//判斷
while (number == 0) {
condition.await();
}
//幹活
number--;
System.out.println(Thread.currentThread().getName() + " consume\t" + number);
//通知喚醒
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
/** * 一個初始值爲0的變量,兩個線程交替操做,一個加1一個減1,重複5次 * 1. 線程 操做 資源類 * 2. 判斷 幹活 通知 * 3. 防止虛假喚醒機制:判斷的時候要用while而不是用if */
public class ProduceConsumeTraditionalDemo {
public static void main(String[] args) {
ShareData data=new ShareData();
new Thread(()->{
for (int i = 0; i < 5 ; i++) {
try {
data.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 5 ; i++) {
try {
data.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
},"B").start();
}
}
複製代碼
打印結果
A produce 1
B consume 0
A produce 1
B consume 0
A produce 1
B consume 0
A produce 1
B consume 0
A produce 1
B consume 0
複製代碼
public class ProduceConsumeBlockingQueueDemo {
public static void main(String[] args) {
SharedData data=new SharedData(new ArrayBlockingQueue<>(10));
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + "\t生產線程啓動");
try {
data.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Producer").start();
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + "\t消費線程啓動");
try {
data.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Consumer").start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
data.stop();
System.out.println("中止");
}
}
class SharedData{
private volatile boolean FLAG=true;
private AtomicInteger atomicInteger=new AtomicInteger();
BlockingQueue<String> blockingQueue=null;
public SharedData(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println(blockingQueue.getClass().getName());
}
public void produce() throws InterruptedException {
String data=null;
boolean ret;
while(FLAG){
data=""+atomicInteger.incrementAndGet();
ret=blockingQueue.offer(data,2L,TimeUnit.SECONDS);
if(ret){
System.out.println(Thread.currentThread().getName()+"\t插入"+data+"成功");
}else{
System.out.println(Thread.currentThread().getName()+"\t插入"+data+"失敗");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println("生產結束,FLAG=false");
}
public void consume() throws InterruptedException {
String ret=null;
while(FLAG){
ret=blockingQueue.poll(2L,TimeUnit.SECONDS);
if(null==ret||ret.equalsIgnoreCase("")){
System.out.println(FLAG=false);
System.out.println(Thread.currentThread().getName()+"\t消費等待超時退出");
return;
}
System.out.println(Thread.currentThread().getName() + "\t消費" + ret + "成功");
}
}
public void stop(){
FLAG=false;
}
}
複製代碼
使用阻塞隊列+原子類+volatile變量的方式。 打印結果以下:
java.util.concurrent.ArrayBlockingQueue
Producer 生產線程啓動
Consumer 消費線程啓動
Producer 插入1成功
Consumer 消費1成功
Producer 插入2成功
Consumer 消費2成功
Producer 插入3成功
Consumer 消費3成功
中止
生產結束,FLAG=false
false
Consumer 消費等待超時退出
複製代碼
tryLock(long timeout, TimeUnit unit)
,另外一種是lockInterruptibly()
放代碼塊中,調用interrupt()
方法進行中斷。class ShareData{
private int number=1;
private Lock lock=new ReentrantLock();
public void printA(){
lock.lock();
Condition conditionA=lock.newCondition();
try{
while(number!=1){
conditionA.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number=2;
conditionA.signal();
}catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
Condition conditionB=lock.newCondition();
try{
while(number!=2){
conditionB.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number=3;
conditionB.signal();
}catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
Condition conditionC=lock.newCondition();
try{
//判斷
while(number!=3){
conditionC.await();
}
//幹活
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
number=1;
//通知
conditionC.signal();
}catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ShareData data=new ShareData();
new Thread(() -> data.printA(),"A").start();
new Thread(() -> data.printB(),"B").start();
new Thread(() -> data.printC(),"C").start();
}
}
複製代碼
Thread的構造函數中並無傳入 Callable 的方式,可是能夠傳入 Runnable 接口: Thread thread=new Thread(Runnable runnable, String name);
。爲了使用 Callable 接口,咱們須要使用到 FutureTask 類。 FutureTask 類實現了 RunnableFuture 這一接口,而 RunnableFutre 又是 Future 的子接口,所以 FutureTask 能夠做爲參數使用上述的 Thread 構造函數。同時, FutureTask 自己構造函數能夠傳入 Callable 。
class MyThread implements Callable<Integer>{
@Override
public Integer call() {
System.out.println("come in callable");
return 2019;
}
}
class Main{
public static void main(String [] args){
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread2());
Thread t1=new Thread(futureTask,"A");
}
}
複製代碼
線程池有七大參數:
public ThreadPoolExecutor( int corePoolSize,//線程池常駐核心線程數 int maximumPoolSize,//線程池能容納同時執行最大線程數 long keepAliveTime,//多餘的空閒線程的存活時間,當前線程池線程數量超過core,空閒時間達到keepAliveTime,多餘空閒線程會被銷燬直到只剩下core個 TimeUnit unit, BlockingQueue<Runnable> workQueue,//被提交還沒有被執行的任務隊列 ThreadFactory threadFactory,//建立線程的線程工廠 RejectedExecutionHandler handler//拒絕策略 ) {...}
複製代碼
處理流程以下:
在 JDK 中有四種內置的拒絕策略,均實現了 RejectedExecutionHandler 接口。
建立固定容量的線程池,控制最大併發數,超出的線程在隊列中等待。
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
複製代碼
其中 corePoolSize 和 maximumPoolSize 值是相等的,而且使用的是 LinkedBlockingQueue。
適用於執行長期的任務,性能比較高。
建立了一個單線程的線程池,只會用惟一的工做線程來執行任務,保證全部任務按照順序執行。
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
複製代碼
其中 corePoolSize 和 maximumPoolSize 都設置爲1,使用的也是 LinkedBlockingQueue。
適用於一個任務一個任務執行的場景。
建立了一個可緩存的線程池,若是線程池長度超過處理須要,能夠靈活回收空閒線程,沒有能夠回收的,則新建線程。
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
複製代碼
設置 corePoolSize 爲0, maximumPoolSize 設置爲 Integer.MAX_VALUE,使用的是 SynchronousQueue。來了任務就建立線程執行,線程空閒超過60秒後銷燬。
適用於執行不少短時間異步的小程序或者負載比較輕的服務器。
在阿里巴巴Java開發手冊中有以下規定:
Runtime.getRuntime().availableProcessors()
獲取當前設備的CPU個數。
CPU 核心數 + 1
CPU核心數 * 2
,或者採用 CPU 核心數 / (1 - 阻塞係數)
,阻塞係數在0.8 ~ 0.9之間死鎖是指兩個或兩個以上的進程在執行過程當中,由於爭奪資源形成的互相等待的現象。
死鎖須要滿族的四大條件以下:
產生死鎖的主要緣由有:
class HoldLockThread implements Runnable{
private String lock1;
private String lock2;
public HoldLockThread(String lock1, String lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
synchronized (lock1){
System.out.println(Thread.currentThread().getName()+"\t持有"+lock1+"\t嘗試獲取"+lock2);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println(Thread.currentThread().getName()+"\t持有"+lock1+"\t嘗試獲取"+lock2);
}
}
}
}
public class DeadLockDemo {
public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
new Thread(new HoldLockThread(lockA,lockB),"Thread1").start();
new Thread(new HoldLockThread(lockB,lockA),"Thread2").start();
}
}
複製代碼
輸出以下結果,程序並無終止。
Thread2 持有lockB 嘗試獲取lockA
Thread1 持有lockA 嘗試獲取lockB
複製代碼
使用 jps ,相似於 linux 中的 ps 命令。
在上述 java 文件中,使用 IDEA 中的 open In Terminal,或者在該文件目錄下使用 cmd 命令行工具。
首先使用 jps -l
命令,相似於ls -l
命令,輸出當前運行的 java 線程,從中能得知 DeadLockDemo 線程的線程號。
而後,使用jstack threadId
來查看棧信息。輸出以下:
Java stack information for the threads listed above:
===================================================
"Thread2":
at interview.jvm.deadlock.HoldLockThread.run(DeadLockDemo.java:22)
- waiting to lock <0x00000000d6240328> (a java.lang.String)
- locked <0x00000000d6240360> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"Thread1":
at interview.jvm.deadlock.HoldLockThread.run(DeadLockDemo.java:22)
- waiting to lock <0x00000000d6240360> (a java.lang.String)
- locked <0x00000000d6240328> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
複製代碼