併發程式設計-(2)執行緒安全問題
阿新 • • 發佈:2018-12-31
目錄
1、什麼叫執行緒安全
當多個執行緒訪問某個類(物件或者方法)時,這個類始終能表現出正確的行為,那麼這個類(物件或方法)是安全的。
2、多視窗買票案例
package com.fly.thread_demo.demo_2; /** * 模擬火車站買票:一共庫存 100張票 ,兩個視窗同時賣票 */ public class ThreadTrain { /** * 車票庫存100張 */ private int count = 100; /** * 買票方法 */ public void sell(){ if (count > 0 ){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } count -- ; System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"張火車票,賣完還剩"+count+"張!"); } } public static void main(String[] args) { ThreadTrain threadTrain = new ThreadTrain(); /** * 視窗一 */ new Thread(new Runnable() { @Override public void run() { while (threadTrain.count > 0 ){ threadTrain.sell(); } } },"視窗一").start(); /** * 視窗二 */ new Thread(new Runnable() { @Override public void run() { while (threadTrain.count > 0 ){ threadTrain.sell(); } } },"視窗二").start(); } }
我們一共有100張火車票,但是最後我們賣了101張,這就是執行緒安全問題
3、如何解決執行緒安全問題
3.1、鎖的特徵
只能同時被一個執行緒持有。
3.2、內建鎖(synchronized)
保證執行緒的原子性,當執行緒進入方法時,自動獲取鎖,一旦鎖被獲取,其他執行緒在執行該方法時候會等待,當程式執行完畢後就會釋放鎖,後面排隊的執行緒會搶這把鎖,沒搶到的執行緒會繼續等待。
3.3、解決火車票問題
3.3.1、同步程式碼塊
private Object obj = new Object(); /** * 買票方法 使用靜態程式碼快的方式 */ public void sell(){ //讓一個obj物件成為一把鎖 synchronized (obj){ if (count > 0 ){ try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } count -- ; System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"張 火車票,賣完還剩"+count+"張!"); } } }
public void sell(){
//這裡我們每次使用不同的物件做為鎖
synchronized (new Object()){
if (count > 0 ){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
count -- ;
System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"張火車票,賣完還剩"+count+"張!");
}
}
}
結論一: 在多執行緒中,要使用同步,必須使用同一把鎖。
另:
如果使用String 型別的鎖,我們改變了String物件的值,就會讓鎖發生改變
如果我們用物件鎖,我們改變物件屬性的值,不會改變鎖
3.3.2、同步方法
/**
* 買票方法 方法加 synchronized 關鍵字
*/
public synchronized void sell(){
if (count > 0 ){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
count -- ;
System.out.println(Thread.currentThread().getName()+ ":正在出售第"+(100-count)+"張火車票,賣完還剩"+count+"張!");
}
}
4、同步方法鎖的進階
這裡我們就只演示結論,不演示不成功的來對比了。
package com.fly.thread_demo.demo_2;
/**
* 非靜態同步方法 和 靜態同步方法
*/
public class ThreadSafe {
private Object obj = new Object();
public void printNum_1(){
synchronized (obj){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
}
public void printNum_2(){
synchronized (obj){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
}
/**
* 現在有兩個執行緒,分別呼叫printNum_1和printNum_2方法
* 如果我們給printNum_1 和 printNum_2 加鎖
* 我們使用前面的結論:如果使用同一把鎖 兩個方法將會同步 如果不是同一把鎖 將不會同步
*/
public static void main(String[] args) {
ThreadSafe threadSafe = new ThreadSafe();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_2();
}
}).start();
}
}
線上程Thread-0執行完畢後再執行執行緒Thread-1,說明前面的結論沒有錯
4.1、非靜態同步方法
package com.fly.thread_demo.demo_2;
/**
* 非靜態同步方法 和 靜態同步方法
*/
public class ThreadSafe {
private Object obj = new Object();
/**
* 在非靜態方法上加synchronized鎖
*/
public synchronized void printNum_1(){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
/**
* 使用當前物件作為鎖
*/
public void printNum_2(){
synchronized (this){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
}
/**
* 現在有兩個執行緒,分別呼叫printNum_1和printNum_2方法
* 如果我們給printNum_1 和 printNum_2 加鎖
* 如果使用同一把鎖 他們將會同步 如果不是同一把鎖 將不會同步
*/
public static void main(String[] args) {
ThreadSafe threadSafe = new ThreadSafe();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_2();
}
}).start();
}
}
結論二: 非靜態方法上加synchronize鎖,其實使用的是this鎖,也就是把當前物件作為鎖
4.2、靜態同步方法
package com.fly.thread_demo.demo_2;
/**
* 非靜態同步方法 和 靜態同步方法
*/
public class ThreadSafe {
private Object obj = new Object();
/**
* 在靜態方法上加synchronized鎖
*/
public synchronized static void printNum_1(){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
/**
* 使用當前類的 位元組碼檔案 作為鎖
*/
public void printNum_2(){
synchronized (ThreadSafe.class){
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ ": i = " + i);
}
}
}
/**
* 現在有兩個執行緒,分別呼叫printNum_1和printNum_2方法
* 如果我們給printNum_1 和 printNum_2 加鎖
* 如果使用同一把鎖 他們將會同步 如果不是同一把鎖 將不會同步
*/
public static void main(String[] args) {
ThreadSafe threadSafe = new ThreadSafe();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
threadSafe.printNum_2();
}
}).start();
}
}
結論三:在靜態方法上加synchronize鎖,其實是把當前類的位元組碼檔案當做鎖。
5、死鎖
5.1、死鎖案例
package com.fly.thread_demo.demo_2;
/**
* 死鎖 一般是由鎖的巢狀引起的
* 小明和小紅打架,小明揪著小紅的頭髮,小紅揪著小明的頭髮,
* 小明要等小紅放手自己才會放手,小紅也要等著小明放手自己才會放手
* 這樣他們永遠也不會鬆手
*/
public class DeathThread implements Runnable{
private String tag;
public void setTag(String tag){
this.tag = tag;
}
private static Object obj1 = new Object();
private static Object obj2 = new Object();
@Override
public void run() {
if ("小明怒了".equals(tag)) {
synchronized (obj1) {
System.out.println("小明揪住了小紅的頭髮...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
System.out.println("因為小紅放開了小明,所以小明放開了小紅...");
}
}
}
if ("小紅怒了".equals(tag)) {
synchronized (obj2) {
System.out.println("小紅揪住了小明的頭髮...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
System.out.println("因為小明放開了小紅,所以小紅放開了小明...");
}
}
}
}
public static void main(String[] args) {
DeathThread dt1 = new DeathThread();
dt1.setTag("小明怒了");
DeathThread dt2 = new DeathThread();
dt2.setTag("小紅怒了");
Thread t1 = new Thread(dt1);
Thread t2 = new Thread(dt2);
t1.start();
t2.start();
}
}
程式一直沒結束,但是小明和小紅都不放手,這是因為小明拿到先拿到了obj1鎖,然後休眠了1秒鐘,與此同時,小紅拿到了obj2鎖,也休眠了1秒鐘,一秒鐘過後,小明要obj2鎖才能放手,但是obj2鎖在小紅手上,要等小紅執行完畢才能釋放obj2鎖,但是小紅想要執行完畢要先獲得obj1鎖,同樣obj1鎖在小明手上,這樣就造成了死鎖問題
5.2、快速定位死鎖位置
我們一般使用jdk工具
開啟控制檯輸入jconsole
這個時候會開啟Java簡直和管理控制檯
- 開啟對應的類
選擇不安全連結
找到對應的執行緒名稱,檢視詳情