對(duì)于很多剛剛接觸多線程編程的同學(xué)來(lái)說(shuō),可能僅僅是聽說(shuō)過(guò)線程同步和線程安全這兩個(gè)名詞而已,對(duì)于具體什么線程同步、什么是線程安全,可能也只能從他的名字上面去猜測(cè)一下他的意思,至于其他的可能并沒有太多的了解,因此我決定寫一篇這樣的文章,詳細(xì)地告訴大家,線程同步和線程安全究竟是何方神圣!
首先究竟什么是線程同步呢?在了解線程同步之前,我們先了解一下什么是同步,這有助于對(duì)線程同步的理解。所謂同步,就是在發(fā)出一個(gè)方法的調(diào)用時(shí),在沒有得到結(jié)果之前,這個(gè)調(diào)用就不返回,同時(shí)其它的線程也不能調(diào)用這個(gè)方法!線程同步也是類似的意思,但線程同步不是說(shuō)讓一個(gè)線程執(zhí)行完了再執(zhí)行其它線程,一般是指讓線程中的某一些操作進(jìn)行同步就可以了、下面我舉個(gè)例子:讓大家更好地進(jìn)行理解。
而在多線程的編程里,我們不可避免地會(huì)遇上到這樣的一種問題,就是一些數(shù)據(jù)不能被多個(gè)線程同時(shí)訪問,比如A和B同時(shí)去商店里買糖,但商店里一共就只剩下3個(gè)糖了,A說(shuō)我要2個(gè),B同時(shí)也說(shuō)我要2個(gè),那么此時(shí)是不是A和B中肯定有一個(gè)人買不到2個(gè)糖!如果生活中還好,還可以商量著解決!那么線程里有沒有類似商量解決的方法呢?其實(shí)多線程里面也是有類似的方法的,這就先讓A或B中的一個(gè)買,假設(shè)是A先買,等A先買完了,B再買!剛好同步機(jī)制就可以解決上面這個(gè)問題,解決讓誰(shuí)先買,誰(shuí)后買的問題. 采用同步機(jī)制就可以保證數(shù)據(jù)在任何時(shí)候最多只有一個(gè)線程進(jìn)行訪問、從而保證了數(shù)據(jù)的安全!
等A和B處理完買糖的事之后,他們是不是想干嘛就干嘛、所以說(shuō)線程同步,一般是指讓線程中的某一些操作進(jìn)行同步就可以了、
Java是如何做到線程同步的呢?
- 在需要同步的方法的 方法簽名中加上 synchronized關(guān)鍵字
- 使用synchronized關(guān)鍵字對(duì)需要進(jìn)行同步的代碼塊進(jìn)行同步
- 使用java.util.concurrent.lock包中Lock對(duì)象(JDK1.8)
1.在需要同步的方法的 方法簽名中加上 synchronized關(guān)鍵字
public class Data {
private int mVal = 0;
public void setDate(int mVal) {
this.mVal = mVal;
}
public int getDate() {
return mVal;
}
}
public class MyThread implements Runnable{
private Data mData = new Data();
@Override//讓多個(gè)線程共享一個(gè)數(shù)據(jù)
public synchronized void run() {
int tmp = mData.getDate();
++tmp;
mData.setDate(tmp);
System.out.println(Thread.currentThread().getName()+"|"+mData.getDate());
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(myThread);
thread.start();
}
}
}
//打印的結(jié)果
Thread-0|1
Thread-4|2
Thread-3|3
Thread-2|4
Thread-1|5
public class MyThread implements Runnable{
private Data mData = new Data();
@Override//注釋了synchronized關(guān)鍵字后再看結(jié)果
public /*synchronized*/ void run() {
int tmp = mData.getDate();
++tmp;
mData.setDate(tmp);
System.out.println(Thread.currentThread().getName()+"|"+mData.getDate());
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(myThread);
thread.start();
}
}
}
//注釋了synchronized關(guān)鍵字后
Thread-0|2
Thread-4|5
Thread-3|4
Thread-2|3
Thread-1|2
//需要注意的是,運(yùn)行的結(jié)果肯定每次都是不同的,原因:線程搶占到資源的順序
2. 使用synchronized關(guān)鍵字對(duì)需要進(jìn)行同步的代碼塊進(jìn)行同步 (Data類還是一樣)
public class MyThread implements Runnable{
private Data mData = new Data();
@Override//使用synchronized代碼塊(同步塊)
public void run() {
synchronized (this) {
int tmp = mData.getDate();
++tmp;
mData.setDate(tmp);
System.out.println(Thread.currentThread().getName()+"|"+mData.getDate());
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(myThread);
thread.start();
}
}
}
//打印的結(jié)果
Thread-0|1
Thread-4|2
Thread-3|3
Thread-2|4
Thread-1|5
3. 使用java.util.concurrent.lock包中Lock對(duì)象(JDK1.8) 【有需要的時(shí)候再補(bǔ)充】
synchronized使用時(shí)需要注意的一些地方:
被synchronized關(guān)鍵字修飾的代碼塊在被線程執(zhí)行之前,首先要拿到被同步對(duì)象的鎖,并且一個(gè)對(duì)象僅僅是只有一個(gè)鎖,比如上面被synchronized代碼,首先那個(gè)方法需要拿到當(dāng)前對(duì)象的鎖,如果當(dāng)前的鎖已經(jīng)被其它線程拿走了,那么還沒搶到鎖的線程將從可運(yùn)行狀態(tài)轉(zhuǎn)變?yōu)樽枞麪顟B(tài),只有當(dāng)拿到鎖的線程執(zhí)行完同步塊的代碼后,就釋放鎖,讓給別的線程的、這樣就可以保證數(shù)據(jù)的完整性!