歡迎關注個人微信公衆號:程序員私房菜(id:eson_15)java
在多個線程同時操做相同資源的時候,就會遇到併發的問題,如銀行轉帳啊、售票系統啊等。爲了不這些問題的出現,咱們可使用 synchronized
關鍵字來解決,下面針對 synchronized
常見的用法作一個總結。首先寫一個存在併發問題的程序,以下:程序員
public class TraditionalThreadSynchronized {
public static void main(String[] args) {
//在靜態方法中不能new內部類的實例對象
//private Outputer outputer = new Outputer();
new TraditionalThreadSynchronized().init();
}
private void init() {
final Outputer outputer = new Outputer();
//線程1打印:duoxiancheng
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output1("duoxiancheng");
}
}
}).start();;
//線程2打印:eson15
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output1("eson15");
}
}
}).start();;
}
static class Outputer {
//自定義一個字符串打印方法,一個個字符的打印
public void output1(String name) {
int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
}
}
複製代碼
在內部類 Outputer
中定義了一個打印字符串的方法,一個字符一個字符的打印,這樣比較容易直觀的看出併發問題,由於字符順序打亂了就說明出現問題了。而後在init方法中開啓兩個線程,一個線程打印「duoxiancheng」,另外一個線程打印「eson15」。看一下運行結果:安全
eson15duoxianche ng eson15 duoxiancheng duoxiancheng eson15 esduoxiancheng on15 duoxiancheng微信
已經出現問題了,爲了解決這個問題,可使用 synchronized
同步代碼塊來對公共部分進行同步操做,可是須要給它一把鎖,這把鎖是一個對象,能夠是任意一個對象,可是前提是,兩個線程使用的必須是同一個對象鎖才能夠,這很好理解。那麼下面在 output1()
方法中加入 synchronized
代碼塊:併發
static class Outputer {
private String token = ""; //定義一個鎖
public void output1(String name) {
synchronized(token) //任何一個對象均可以做爲參數,可是該對象對於兩個線程來講是同一個才行
//若是用name就不行了,由於不一樣的線程進來name是不同的,不是同一個name
{
int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
}
}
複製代碼
通過上面的改造,線程安全問題就基本解決了,可是還能夠再往下引伸,若是在方法上加 synchronized
關鍵字的話,那麼這個同步鎖是什麼呢?咱們在 Outputer
類中再寫一個output2()
方法:ide
static class Outputer {
private String token = ""; //定義一個鎖
public void output1(String name) {
synchronized(token) //任何一個對象均可以做爲參數,可是該對象對於兩個線程來講是同一個才行
{
int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
}
public synchronized void output2(String name) {
int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
}
複製代碼
方法內部實現邏輯如出一轍,惟一不一樣的就是 synchronized
加在了方法上,那麼咱們讓 init()
方法中的兩個線程中,一個調用 output1(String name)
方法,另外一個調用 output2(String name)
方法,從結果中能看出,線程安全問題又出現了。產生問題的緣由不難發現:如今兩個方法都加了 synchronized
,可是兩個線程在調用兩個不一樣的方法仍是出現了問題,也就是說,仍是各玩各的……那麼問題就出在這個鎖上,說明二者並無使用同一把鎖!函數
若是咱們把 output1()
方法中 synchronized
中的 token 改爲 this,再運行就沒問題了,這說明一點:synchronized關鍵字修飾方法的時候,同步鎖是 this,即等效於代碼塊synchronized(this) {...}
。this
再繼續往下引伸,如今在 Outputer
類中再寫一個靜態方法output3(String name)
,而且也讓 synchronized
去修飾這個靜態方法。spa
static class Outputer {
private String token = ""; //定義一個鎖
public void output1(String name) {
synchronized(token) //任何一個對象均可以做爲參數,可是該對象對於兩個線程來講是同一個才行
{
int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
}
public static synchronized void output3(String name) {
int len = name.length();
for(int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println("");
}
}
}
複製代碼
而後在 init()
方法中一個線程調用 output1()
方法,另外一個線程調用 output3()
方法。毫無疑問,確定又會出現線程安全問題。可是如何解決呢?由於 static 方法在類加載的時候就加載了,因此這個鎖應該是類的字節碼對象。那麼將 token 改成 Outputer.class
就解決問題了,這說明一點:synchronized關鍵字修飾static方法的時候,同步鎖是該類的字節碼對象,即等效於代碼塊 synchronized(classname.class) {...}
。 最後再總結一下:線程
- 同步代碼塊的鎖是任意對象。只要不一樣的線程都執行同一個同步代碼塊的時候,這個鎖隨便設。
- 同步函數的鎖是固定的this。當須要和同步函數中的邏輯實行同步的時候,代碼塊中的鎖必須爲this。
- 靜態同步函數的鎖是該函數所屬類的字節碼文件對象。該對象能夠用
this.getClass()
方法獲取,也可使用當前類名.class
表示。
OK,synchronized 就總結這麼多~若有錯誤之處,歡迎指正~咱們一塊兒進步!
也歡迎你們關注個人微信公衆號:程序員私房菜。我會持續輸出更多文章。