????為了提高系統(tǒng)的資源利用率着逐,促使了進(jìn)程倘潜,線程的出現(xiàn)砚婆。進(jìn)程和線程提高了系統(tǒng)CPU利用率的同時(shí)械拍,又引出了一些其他的問題。
????這里僅討論線程安全性的問題装盯,因?yàn)槎鄠€(gè)線程中操作執(zhí)行順序是不可預(yù)測(cè)的坷虑,甚至?xí)a(chǎn)生一些奇怪的結(jié)果。
多線程造成的安全性問題
下面通過幾個(gè)簡單的示例來看一下埂奈,在沒有鎖的情況下會(huì)存在什么問題迄损。
1、錯(cuò)誤的單例模式
public class SingleInstance {
// 實(shí)例對(duì)象
private static Object instance;
/**
* 獲取該對(duì)象的實(shí)例
*/
public Object getInstance() {
if (instance == null) {
instance = new Object();
}
return instance;
}
}
????該類提供的方法本意是账磺,多次請(qǐng)求getInstance()方法芹敌,instance對(duì)象只初始化一次。在單線程的情況下貌似沒什么問題垮抗,但是在多線程的情況下党窜,就會(huì)存在問題了。
????假定線程A和線程B同時(shí)調(diào)用getInstance方法借宵,A線程判斷instance對(duì)象當(dāng)前為null,實(shí)例化了一個(gè)對(duì)象矾削。在A線程沒有實(shí)例化完之前壤玫,執(zhí)行了線程調(diào)度,B線程也訪問當(dāng)了if的判斷條件中哼凯,發(fā)現(xiàn)instance也為null欲间,也實(shí)例化了一個(gè)對(duì)象并賦值給了instance。最終結(jié)果就是A断部,B線程拿到了不同的對(duì)象實(shí)例猎贴。
2、共享變量被訪問
public class Counter{
private integer count = 0;
public static Integer addCount() {
return count++;
}
}
????上述是一個(gè)技術(shù)器類,它提供了統(tǒng)計(jì)每個(gè)線程訪問服務(wù)器次數(shù)的作用她渴。但是當(dāng)多個(gè)線程同時(shí)并發(fā)訪問达址,它的統(tǒng)計(jì)出來的數(shù)據(jù)就會(huì)存在誤差。為什么會(huì)有這個(gè)問題呢趁耗?
????count++并不是一個(gè)原子操作沉唠,它主要包含這幾步,讀取count變量在內(nèi)存中的值苛败,將count值加一满葛,得到的結(jié)果再賦值給count變量,這是一個(gè) 讀取 -> 修改 -> 寫入罢屈。假設(shè)現(xiàn)在有兩個(gè)線程A和B嘀韧,當(dāng)前count變量的值為1,A線程對(duì)count變量進(jìn)行了讀取缠捌,修改的操作锄贷,在沒有執(zhí)行寫入操作之前發(fā)生了系統(tǒng)調(diào)度將線程A掛起,線程B開始執(zhí)行自己的操作鄙币,將B線程讀取了count變量的值(count = 1)肃叶,并進(jìn)行了后續(xù)的操作,將計(jì)算的count = 2的結(jié)果賦值給了count十嘿,執(zhí)行完畢因惭。線程A繼續(xù)執(zhí)行,此時(shí)同樣將count = 2 寫入到count中绩衷。這樣得到最后的結(jié)果count就會(huì)少記了一次線程的訪問蹦魔。
如何保證線程安全性
????先來看一下線程安全是如何定義的?
????在線程安全性的定義中要求咳燕,多個(gè)線程之間的操作無論采用何種執(zhí)行時(shí)序或交替方式勿决,都要保證不變性條件不被破壞。
????Java中提供了鎖來保證線程安全性招盲,通過在指定的代碼塊中添加synchronized或ReentrantLock關(guān)鍵字來保證操作的原子性低缩。
????當(dāng)線程要訪問一個(gè)被加鎖的對(duì)象、方法或者代碼塊時(shí)曹货,會(huì)自動(dòng)獲得鎖咆繁,如果當(dāng)前的鎖被其他線程持有,則會(huì)將線程先掛起顶籽,等持有鎖的線程將鎖被釋放后會(huì)發(fā)起信號(hào)喚醒阻塞中的線程玩般,去搶占該鎖。
synchronized的用法
????synchronized是Java提供的一種內(nèi)置鎖礼饱,synchronized的用法可以分為三種
????1. 修飾類
????2. 修飾方法(靜態(tài)方法|非靜態(tài)方法)
????3. 修飾代碼塊(變量)
- 修飾類
public class SynchronizedClass{
public static Object instance = null;
public Object getInstance() {
synchronized(SynchronizedClass.class) {
if (null == instance) {
return new Instance();
}
return instance;
}
}
}
當(dāng)synchronized修飾類時(shí)坏为,不管是訪問SynchronizedClass類的哪個(gè)實(shí)例對(duì)象究驴,所有線程都會(huì)競(jìng)爭(zhēng)同一把鎖。
- 修飾靜態(tài)方法
public class LockStaticMethod{
public static Object instance = null;
public synchronized static Object getInstance() {
if (null == instance) {
return new Instance();
}
return instance;
}
}
當(dāng)synchronized修飾靜態(tài)方法時(shí)匀伏,不管是訪問LockStaticMethod類的哪個(gè)實(shí)例對(duì)象中的getInstance方法洒忧,所有線程都會(huì)競(jìng)爭(zhēng)同一把鎖。
- 修飾非靜態(tài)方法
public class LockMethod{
public static Object instance = null;
public synchronized Object getInstance() {
if (null == instance) {
return new Instance();
}
return instance;
}
}
當(dāng)synchronized修飾非靜態(tài)方法時(shí)帘撰,不同實(shí)例對(duì)象持有的鎖是相互不影響的跑慕。
- 修飾代碼塊(變量)
public class LockVariable{
public static Object instance = null;
public synchronized Object getInstance() {
synchronized(instance) {
if (null == instance) {
return new Instance();
}
return instance;
}
}
}
synchronized修飾變量同樣也分靜態(tài)變量和非靜態(tài)變量,他們的含義同靜態(tài)方法和非靜態(tài)方法類似摧找。
下面看一下核行,當(dāng)線程訪問一個(gè)被加鎖的方法時(shí),執(zhí)行過程是怎樣的蹬耘。
ReentrantLock的用法
????ReentrantLock類是Java 5.0才出現(xiàn)的新的加鎖機(jī)制芝雪,ReentrantLock相比于synchronized提供的加鎖機(jī)制更加靈活,并且提供了一些synchronized不具備的功能综苔。例如:可中斷的鎖獲取操作惩系、公平隊(duì)列、非塊結(jié)構(gòu)的鎖如筛,可定時(shí)的鎖堡牡,可輪訓(xùn)的鎖。下面我們來看一下這些特性的使用示例杨刨。
- 可中斷的鎖
可中斷的鎖提供了對(duì)可取消任務(wù)加鎖的需求晤柄,具體使用方式如下所示
private ReentrantLock lock = new ReentrantLock();
public void interruptedLock() throws InterruptedException {
lock.lockInterruptibly();
try {
throw new InterruptedException();
}finally {
lock.unlock();
}
}
- 公平隊(duì)列
ReentrantLock提供了公平鎖,線程按照它們發(fā)出請(qǐng)求的順序來獲得鎖妖胀。ReentrantLock類提供了ReentrantLock(boolean fair)構(gòu)造函數(shù)來初始化公平鎖芥颈。使用時(shí)只要將參數(shù)設(shè)置為true即可。
ReentrantLock fairLock = new ReentrantLock(true);
- 非塊結(jié)構(gòu)的鎖
- 可定時(shí)的鎖
ReentrantLock提供了定時(shí)的方法tryLock(long timeout, TimeUnit unit)赚抡,他能根據(jù)剩余時(shí)間來提供一個(gè)時(shí)限爬坑,如果操作不能在指定的時(shí)間內(nèi)給出結(jié)果,那么就會(huì)使程序提前結(jié)束涂臣,示例如下:
public class TimeTryLock {
public static void main(String[] args) throws Exception {
ReentrantLock reentrantLock = new ReentrantLock();
if (reentrantLock.tryLock(100L, TimeUnit.SECONDS)) {
System.out.println("success");
}
}
}
- 可輪訓(xùn)的鎖
tryLock方法實(shí)現(xiàn)了有條件的獲取鎖的模式盾计,與無條件的鎖獲取模式相比,它具有更完善的錯(cuò)誤恢復(fù)機(jī)制赁遗〈彻溃可輪訓(xùn)鎖提供了另一種選擇:避免發(fā)生死鎖。
先來看一下在synchronized模式下死鎖的情況
public class SyncDeadLock {
public void deadLock(Integer v1, Integer v2) {
synchronized (v1) {
System.out.println(Thread.currentThread() + "get object lock " + v1);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (v2) {
System.out.println(Thread.currentThread() + "get object lock " + v2);
}
}
}
public static void main(String[] args) {
Integer v1 = 0;
Integer v2 = 2;
SyncDeadLock syncDeadLock = new SyncDeadLock();
new Thread(new Runnable() {
@Override
public void run() {
syncDeadLock.deadLock(v1, v2);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
syncDeadLock.deadLock(v2, v1);
}
}).start();
}
}
有了tryLock之后吼和,通過tryLock就可以有效的避免死鎖,tryLock可以嘗試獲取鎖骑素,如果沒有獲取到鎖炫乓,可以主動(dòng)將當(dāng)前持有的鎖釋放掉刚夺,這樣可以避免死鎖發(fā)生的條件,看下面的例子:
public class ReentrantLockDeadLock {
private static Integer count = 10;
public boolean tryLock(ReentrantLock lock1, ReentrantLock lock2) {
while (true) {
if (lock1.tryLock()) {
try {
System.out.println(Thread.currentThread() + " get " + lock1);
if (lock2.tryLock()) {
try {
System.out.println(Thread.currentThread() + " get " + lock2);
} finally {
System.out.println(Thread.currentThread() + " unlock " + lock2);
lock2.unlock();
System.out.println("count = " + --count);
if (count < 0) {
return true;
}
}
}
} finally {
System.out.println(Thread.currentThread() + " unlock " + lock1);
lock1.unlock();
}
}
}
}
public static void main(String[] args) {
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
ReentrantLockDeadLock mainLock = new ReentrantLockDeadLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
mainLock.tryLock(lock1, lock2);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
mainLock.tryLock(lock2, lock1);
}
}).start();
}
}