執行緒安全問題
在多個執行緒同時訪問一個相同的資源的時候會發生執行緒安全問題,
舉個栗子:
買票問題,三個視窗進行買票,
public class ThreadSafe {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("視窗一");
t2.setName("視窗二");
t3.setName("視窗三");
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
private int ticket = 10;//有10張票
@Override
public void run() {
while (true){
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "視窗:" + ticket--);
}else {
break;
}
}
}
}
運行結果:

很明顯可以看出,在三個執行緒同時去訪問Ticket類的時候,票的數量出現的重復和錯誤(結果為0)的情況,
為什么會出現這種情況呢?
因為執行緒是并發的,并發就是三個執行緒同時進行,比如視窗一進入run方法,然后視窗二也進入了run方法,然后兩個同時操作ticket的數量,所以數量出現重復,
如何解決呢?
解決執行緒安全有3中方法:使用同步代碼塊、同步方法、Lock鎖,
1、同步代碼塊
同步代碼塊就是將操作共享資源的代碼放入由synchronized修飾的代碼塊中,
在使用同步代碼塊的時候需要使用一個鎖將代碼塊鎖起來,只允許一個執行緒進行訪問,執行緒在進行操作資料前獲得鎖,操作結束后將鎖釋放,
任何物件都可以是鎖物件,但是鎖物件必須是唯一的
在這里我使用了當前這個物件來作為鎖物件,因為我只宣告了一個ticket物件,該物件是唯一的,
也可以在Ticket類中宣告一個物件,作為鎖物件,
public class ThreadSafe {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("視窗一");
t2.setName("視窗二");
t3.setName("視窗三");
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
private int ticket = 10;//有10張票
//Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (this) {//或者使用synchronized(obj)
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣:" + ticket-- + "號票");
} else {
break;
}
}
}
}
}
運行結果:

有結果可以看到,我們加上同步代碼塊之后,顯示的結果就是正確的,
2、同步方法
在方法上加上synchronized關鍵字即可,同步方法默認的鎖物件是當前物件即this物件,
class Ticket implements Runnable{
private int ticket = 10;//有10張票
@Override
public void run() {
while (true) {
sell();
}
}
private synchronized void sell(){
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣:" + ticket-- + "號票");
}
}
}
運行結果:

注意:在使用繼承Thread類來創建執行緒的時候同步方法和同步代碼塊也會出現安全問題,因為默認使用this為鎖物件,在運行的時候創建了三個ticket物件,所以三個執行緒使用的鎖物件不一樣,
這里使用同步代碼塊和同步方法的結果都是一樣的,這里就不展示同步方法的代碼了,
public class ThreadSafe {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Ticket t2= new Ticket();
Ticket t3 = new Ticket();
t1.setName("視窗一");
t2.setName("視窗二");
t3.setName("視窗三");
t1.start();
t2.start();
t3.start();
}
}
class Ticket extends Thread{
private int ticket = 10;//有10張票
@Override
public void run() {
while (true) {
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣:" + ticket-- + "號票");
} else {
break;
}
}
}
}
}
運行結果:

從結果可以看出來,每個視窗都賣了10張票,因為三個執行緒有著不同的鎖,
解決辦法就是使用當前類作為鎖物件,
synchronized(Ticket.class){
//執行陳述句...
}
為什么使用Ticket.class可以呢?
因為Ticket.class回傳的是一個Class類的物件,該物件時唯一的,
3、使用Lock鎖
使用Lock鎖來解決執行緒安全問題時,需要使用到Lock物件中的兩個方法:
lock() 獲得鎖
unlock() 釋放鎖
class Ticket extends Thread{
private int ticket = 10;//有10張票
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {//使用try-finally可以保證所被釋放
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣:" + ticket-- + "號票");
} else {
break;
}
}finally {
lock.unlock();
}
}
}
}
運行結果:

Lock和synchronized的區別?
synchronized會自動釋放鎖,Lock需要手動啟動鎖和釋放鎖,
三種方式優先級
Lock鎖 > 同步代碼塊 > 同步方法
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/195318.html
標籤:Java
上一篇:Java筆記:Java基礎
