基本概念
首先看一下線程安全與線程不安全的概念:
線程安全就是多線程訪問時(shí)橡疼,采用了加鎖機(jī)制乔妈,當(dāng)一個(gè)線程訪問該類的某個(gè)數(shù)據(jù)時(shí)妻顶,進(jìn)行保護(hù),其他線程不能進(jìn)行訪問直到該線程讀/寫完匈子,其他線程才可使用锌妻。不會(huì)出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染。
線程不安全就是不提供數(shù)據(jù)訪問保護(hù)旬牲,有可能出現(xiàn)多個(gè)線程先后更改數(shù)據(jù)造成所得到的數(shù)據(jù)是臟數(shù)據(jù)仿粹。
學(xué)習(xí)實(shí)踐
今天在學(xué)習(xí)設(shè)計(jì)模式的時(shí)候,對線程安全的概念有些模糊了原茅,又看到說“線程安全問題都是由靜態(tài)變量引起的”吭历,于是寫了下線程不安全的懶漢式單例模式看線程不安全到底是怎么樣的。話不多說擂橘,代碼如下:
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
System.out.println("sub thread1 get Singleton instance: " + Singleton.getInstance().toString());
}
}.start();
new Thread() {
@Override
public void run() {
System.out.println("sub thread2 get Singleton instance: " + Singleton.getInstance().toString());
}
}.start();
System.out.println("main thread get Singleton instance: " + Singleton.getInstance().toString());
}
static class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
多次執(zhí)行發(fā)現(xiàn)出現(xiàn)了下面的結(jié)果:
sub thread1 get Singleton instance: test.TestSync$Singleton@1670207
sub thread2 get Singleton instance: test.TestSync$Singleton@1e21217
main thread get Singleton instance: test.TestSync$Singleton@1db9742
哇晌区,三個(gè)線程都得到了不同的Singleton對象,當(dāng)然這種情況少一些通贞。也就是說當(dāng)這幾個(gè)線程同時(shí)執(zhí)行時(shí)朗若,由于CPU的調(diào)度不同(執(zhí)行一個(gè)線程,執(zhí)行一部分代碼昌罩,讓它等待或叫阻塞哭懈,接著運(yùn)行另一個(gè)線程,執(zhí)行一部分又阻塞它......)茎用,3個(gè)線程可能都判斷singleton為null遣总,然后都分別執(zhí)行singleton = new Singleton();
所以產(chǎn)生了3個(gè)不同的對象睬罗。
這個(gè)好理解,但是我有點(diǎn)好奇旭斥,難道一般的變量在被多個(gè)線程訪問時(shí)一定是線程安全的容达?于是我又進(jìn)行了下面的嘗試:
public static void main(String[] args) {
ObjTest objTest = new ObjTest();
if(objTest != null){ // 為了防止線程里的代碼先于上一行代碼執(zhí)行
new Thread() {
@Override
public void run() {
System.out.println("sub thread1 get instance: " + objTest.getInstance().toString());
}
}.start();
new Thread() {
@Override
public void run() {
System.out.println("sub thread2 get instance: " + objTest.getInstance().toString());
}
}.start();
new Thread() {
@Override
public void run() {
System.out.println("sub thread3 get instance: " + objTest.getInstance().toString());
}
}.start();
new Thread() {
@Override
public void run() {
System.out.println("sub thread4 get instance: " + objTest.getInstance().toString());
}
}.start();
System.out.println("main thread get instance: " + objTest.getInstance().toString());
}
}
static class ObjTest {
private Object object;
public ObjTest() {
}
public Object getInstance() {
if (object == null) {
object = new Object();
}
return object;
}
}
這次增加了2個(gè)子線程來測試,經(jīng)過多次執(zhí)行發(fā)現(xiàn)同一個(gè)對象的普通成員變量也是會(huì)有線程不安全的情況垂券。比如結(jié)果如下:
sub thread1 get instance: java.lang.Object@e36b9b
sub thread2 get instance: java.lang.Object@eb3e0a
main thread get instance: java.lang.Object@eb3e0a
sub thread3 get instance: java.lang.Object@eb3e0a
sub thread4 get instance: java.lang.Object@eb3e0a
也就是說花盐,當(dāng)多個(gè)線程同時(shí)訪問成員變量時(shí),不管它是否為靜態(tài)變量菇爪,都有可能出現(xiàn)線程不安全的情況算芯。若每個(gè)線程中對靜態(tài)變量只有讀操作,而無寫操作娄帖,一般來說也祠,這個(gè)靜態(tài)變量是線程安全的昙楚;若有多個(gè)線程同時(shí)執(zhí)行寫操作近速,一般都需要考慮線程同步,否則的話就可能影響線程安全堪旧。
如何解決線程不安全呢
- 同步方法
給多線程訪問的成員方法加上synchronized修飾符
public void synchronized doWork(){
// TODO
}
- 使用synchronized修飾的方法削葱,就叫做同步方法,保證線程執(zhí)行該方法的時(shí)候淳梦,其他線程只能在方法外等著析砸。
- 同步代碼塊
synchronized(同步鎖對象)
{
// 需要同步操作的代碼
}
- 實(shí)際上,對象的同步鎖只是一個(gè)概念爆袍,可以想象為在對象上標(biāo)記了一個(gè)鎖首繁,誰拿到鎖,誰就可以進(jìn)入代碼塊陨囊,其他線程只能在代碼塊外面等著弦疮,而且注意,在任何時(shí)候蜘醋,Java虛擬機(jī)最多允許一個(gè)線程擁有該同步鎖胁塞。
- Java程序運(yùn)行可以使用任何對象作為同步監(jiān)聽對象,但是一般的压语,我們把當(dāng)前并發(fā)訪問的共同資源作為同步監(jiān)聽對象啸罢。
實(shí)際上,同步方法和同步代碼塊差不了多少胎食,在本質(zhì)上是一樣的扰才,兩者都用了一個(gè)關(guān)鍵字synchronized,synchronized保證了多線程并發(fā)訪問時(shí)的同步操作厕怜,避免線程的安全性問題训桶,但是有一個(gè)弊端累驮,就是使用synchronized的方法/代碼塊的性能比不用要低一些,因此如果要用synchronized舵揭,建議盡量減小synchronized的作用域谤专。
第一次寫博客呢,不知道寫得怎么樣午绳。有什么意見和建議歡迎評論共同學(xué)習(xí)進(jìn)步~(≧▽≦)/啦啦啦