Java多執行緒(三)執行緒同步
Java多執行緒(三)
1.多執行緒同步
執行緒安全
之前售票的例子中,多執行緒共享tickets可能導致執行緒安全問題。
舉例:
class SaleThread implements Runnable{ private int tickets=10; public void run(){ while(true){ if(tickets>0){ try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在發售第"+tickets--+"張票"); } } } } public class Page369 { public static void main(String[] args) { SaleThread saleThread=new SaleThread(); new Thread(saleThread,"視窗1").start(); new Thread(saleThread,"視窗2").start(); new Thread(saleThread,"視窗3").start(); new Thread(saleThread,"視窗4").start(); } }
錯誤原因: 在while迴圈中新增sleep()方法,這樣就模擬了售票過程中的延遲。由於執行緒有延遲,當票號減為1,假設視窗2執行緒此時出售1號票,對票號進行判斷後進入while迴圈,由於售票前通過sleep()模擬耗時,此時票號任為1,其他執行緒會進行售票,所以最終會出現0,-1,-2這樣的票號。
同步程式碼塊
執行緒安全問題是由於多個執行緒同時處理共享資源造成的,想要解決執行緒安全問題,必須保證處理共享資源的程式碼在任意時刻只能有一個執行緒訪問。為此Java中提供了執行緒同步機制。將共享資原始碼放置在一個使用synchronized關鍵字修飾的程式碼塊中,這個程式碼塊稱為同步程式碼塊。
語法:
synchronized(lock){
//操作共享資原始碼塊
}
舉例:
class SaleThread2 implements Runnable{ private int tickets=10; Object lock=new Object(); //定義任意一個物件作為同步程式碼塊的鎖 public void run(){ while(true){ //定義同步程式碼塊 synchronized(lock){ if(tickets>0){ try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在發售第"+tickets--+"張票"); } } } } } public class Page371 { public static void main(String[] args) { SaleThread2 saleThread2=new SaleThread2(); new Thread(saleThread2,"視窗1").start(); new Thread(saleThread2,"視窗2").start(); new Thread(saleThread2,"視窗3").start(); new Thread(saleThread2,"視窗4").start(); } }
同步方法
把共享資源放在synchronized定義的區域內,便為這些操作都加了同步鎖。在方法前也可以用synchronized修飾,被修飾的方法為同步方法,它能實現和同步程式碼塊同樣的功能。
舉例:
class SaleThread3 implements Runnable {
private int tickets=10;
public void run(){
while(true){
saleTicket();
}
}
private synchronized void saleTicket(){
if(tickets>0){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在發售第"+tickets--+"張票");
}
}
}
public class Page372 {
public static void main(String[] args) {
SaleThread3 saleThread=new SaleThread3();
new Thread(saleThread,"視窗1").start();
new Thread(saleThread,"視窗2").start();
new Thread(saleThread,"視窗3").start();
new Thread(saleThread,"視窗4").start();
}
}
同步鎖
synchronized同步程式碼塊和同步方法也有一些限制,比如它無法中斷一個正在等候獲得鎖的執行緒,也無法通過輪詢得到鎖,如果不想等下去就沒辦法得到鎖。從JDK5開始Java增加了一個功能更強大的Lock鎖。Lock鎖與synchronized隱式鎖在功能上基本相同,但Lock鎖可以讓某個執行緒在持續獲取同步鎖失敗後返回,不再繼續等待,在使用時也更加靈活。
舉例:
import java.util.concurrent.locks.*;
class LockThread implements Runnable{
private int tickets=10;
//定義一個Lock鎖物件
private final Lock lock=new ReentrantLock();
public void run(){
while(true){
lock.lock(); //對程式碼塊加鎖
if(tickets>0){
try{
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"正在發售第"+tickets--+"張票");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock(); //執行完程式碼塊後釋放鎖
}
}
}
}
}
public class Page374 {
public static void main(String[] args) {
LockThread lockThread=new LockThread();
new Thread(lockThread,"視窗1").start();
new Thread(lockThread,"視窗2").start();
new Thread(lockThread,"視窗3").start();
new Thread(lockThread,"視窗4").start();
}
}
通過Lock介面的實現類ReentrantLock來建立Lock鎖物件,並通過Lock鎖物件的lock()方法和unlock()方法對核心程式碼塊進行上鎖和解鎖。
死鎖問題
兩個執行緒在執行時都在等待對方的鎖,這樣就導致程式停滯,稱為死鎖。
舉例:
class DeadLockThread implements Runnable{
//定義兩個不同的鎖物件
static Object chopsticks=new Object();
static Object knifeAndFork=new Object();
private boolean flag;
DeadLockThread(boolean flag){
this.flag=flag;
}
public void run(){
if(flag){
while(true){
synchronized(chopsticks){
System.out.println(Thread.currentThread().getName()+"---if---chopsticks");
synchronized(knifeAndFork){
System.out.println(Thread.currentThread().getName()+"---if---knifeAndFork");
}
}
}
}else{
while(true){
synchronized(knifeAndFork){
System.out.println(Thread.currentThread().getName()+"---else---knifeAndFork");
synchronized (chopsticks){
System.out.println(Thread.currentThread().getName()+"---else---chopsticks");
}
}
}
}
}
}
public class Page376 {
public static void main(String[] args) {
DeadLockThread thread1=new DeadLockThread(true);
DeadLockThread thread2=new DeadLockThread(false);
new Thread(thread1,"Chinese").start();
new Thread(thread2,"American").start();
}
}
兩個執行緒都需要對方所佔用的鎖,但是都無法釋放自己所擁有鎖。