更多精彩文章歡迎關注公眾號“Java之康庄大道”
當多個線程並發訪問同一個資源對象時,可能會出現線程不安全的問題,比如現有100個高鐵座位,現在有請三個窗口(A,B,C)同時售票.,此時使用多線程技術來實現這個案例.
package com.yunqing.ssm.test; /** * 存在線程安全問題 */ public class TestRunnable { public static void main(String[] args) { Ticket tick = new Ticket(); Thread t1 = new Thread(tick,"A窗口"); Thread t2 = new Thread(tick,"B窗口"); Thread t3 = new Thread(tick,"C窗口"); t1.start(); t2.start(); t3.start(); } } class Ticket implements Runnable{ int ticket = 100; @Override public void run() { while (true){ try { //模擬網絡延遲 Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (ticket>0) System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket--); } } }
以上代碼運行結果:
為什么編號為84的座位號被3個窗口售出了?
當A窗口打印84座位號,還沒打印完的時候,其他兩個線程就也進入到了84號座位票的分配操作中,所以導致線程安全問題。
要解決上述多線程並發訪問多一個資源的安全性問題,就必須得保證打印座位號和座位號總數減1操作,必須同步完成.即是說,A線程進入操作的時候,B和C線程只能在外等着,A操作結束,A和B和C才有機會進入代碼去執行.
解決多線程並發訪問資源的安全問題,有三種方式:
方式1:同步代碼塊
方式2:同步方法
方式3:鎖機制(Lock)
方式1:同步代碼塊:
語法:
synchronized(同步鎖)
{
需要同步操作的代碼
}
同步鎖:
為了保證每個線程都能正常執行原子操作,Java引入了線程同步機制.也稱為同步監聽對象/同步鎖/同步監聽器/互斥鎖。
實際上,對象的同步鎖只是一個概念,可以想象為在對象上標記了一個鎖,誰拿到鎖,誰就可以進入代碼塊,其他線程只能在代碼塊外面等着,而且注意,在任何時候,最多允許一個線程擁有同步鎖.
Java程序運行可以使用任何對象作為同步監聽對象,但是一般的,我們把當前並發訪問的共同資源作為同步監聽對象.
package com.yunqing.ssm.test; /** * 存在線程安全問題 */ public class TestRunnable { public static void main(String[] args) { Ticket tick = new Ticket(); Thread t1 = new Thread(tick,"A窗口"); Thread t2 = new Thread(tick,"B窗口"); Thread t3 = new Thread(tick,"C窗口"); t1.start(); t2.start(); t3.start(); } } class Ticket implements Runnable{ int ticket = 100; @Override public void run() { while (true){ try { //模擬網絡延遲 Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //this代表Ticket對象,Ticket對象是多線程共享資源 synchronized (this){ if (ticket>0) System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket--); } } } }
方式2:同步方法:
使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外等着.
Synchronized public void doWork(){
///TODO
}
同步鎖是誰:
對於非static方法,同步鎖就是this.
對於static方法,我們使用當前方法所在類的字節碼對象(Ticket.class).
package com.yunqing.ssm.test;
/**
* 存在線程安全問題
*/
public class TestRunnable {
public static void main(String[] args) {
Runnable a = new Ticket();
new Thread(a,"A窗口").start();
new Thread(a,"B窗口").start();
new Thread(a,"C窗口").start();
}
}
class Ticket implements Runnable{
int ticket = 100;
synchronized private void doWork() throws InterruptedException {
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket);
ticket--;
Thread.sleep(100);
}
}
@Override
public void run() {
for (int i=1;i<=100;i++){
try {
doWork();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意:
不要使用synchronized修飾run方法,修飾之后,某一個線程就執行完了所有的功能. 好比是多個線程出現串行.
解決方案:把需要同步操作的代碼定義在一個新的方法中,並且該方法使用synchronized修飾,再在run方法中調用該新的方法即可.
實際上,同步代碼塊和同步方法差不了多少,在本質上是一樣的,兩者都用了一個關鍵字synchronized,synchronized保證了多線程並發訪問時的同步操作,避免線程的安全性問題,但是有一個弊端,就是使用synchronized的方法/代碼塊的性能比不用要低一些,因此如果要用synchronized,建議盡量減小synchronized的作用域。
方式3:同步鎖(鎖機制)
Lock機制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向對象.
package com.yunqing.ssm.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 存在線程安全問題
*/
public class TestRunnable {
public static void main(String[] args) {
Runnable a = new Ticket();
new Thread(a,"A窗口").start();
new Thread(a,"B窗口").start();
new Thread(a,"C窗口").start();
}
}
class Ticket implements Runnable{
int ticket = 100;
//創建鎖對象
private final Lock lock = new ReentrantLock();
private void doWork(){
//進入方法,立馬加鎖
lock.lock();
try {
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket);
ticket--;
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
@Override
public void run() {
for (int i=1;i<=100;i++){
doWork();
}
}
}