多線程問題:比如銀行轉(zhuǎn)賬等問題
synchronized:"同步 = 加鎖 ":含有synchronized關(guān)鍵字的代碼塊,同時最多只能有一個線程運行并鎖定,當有其它線程運行時要先檢查有沒有其它線程在使用,若有則等待其線程運行完后再運行,若沒有則直接運行并上鎖。相當于不受CPU切換的干擾。
多線程對同一資源進行處理時,CPU會隨機快速切換,所以會出現(xiàn)線程A的方法處理一半,線程B接收調(diào)用B的方法,導(dǎo)致線程不安全,如下:
public class SynchronizedTest {
public static void main(String[] args) {
final Phone phone = new Phone();
new Thread(new Runnable() { // 1. Thread1
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
phone.getPhone("iphone-x plus");
}
}
}).start();
new Thread(){ // 2. Thread2
@Override
public void run() {
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
phone.getPhone("小米特別版");
}
}
}.start();
}
}
// 調(diào)用的公共方法
class Phone {
public void getPhone(String phone){
for (int i=0; i<phone.length();i++){
System.out.print(phone.charAt(i));
}
System.out.println("---------");
}
}
====console===
iphone-x plus---------
小米特別版---------
iphone-x plus---------
小米特別版---------
iphone-x p小米特別版---------
lus---------
所以會出現(xiàn)數(shù)據(jù)錯誤的情況。
解決:
如果只需要同步一部分代碼塊可以用 synchronized(this){ ... } 來完成,這里有個小問題就是this,synchronized 需要指定的是同一個對象,如果兩個線程中的對象名稱不一樣那么在synchronized()括號里就不知道寫什么,而且容易出現(xiàn)指的不是同一對象的情況,這樣不起作用,所以在這里用this就是指向調(diào)用該方法的對象。如下就會報錯 :
class Phone {
public void getPhone(String phone){
synchronized (phone){ //1 . phone 沒有效果
for (int i=0; i<phone.length();i++){
System.out.print(phone.charAt(i));
}
System.out.println("---------");
}
}
}
===console====
iphone-x plus---------
小米特iphone-x plus---------
別版---------
iphone-x plus---------
小米特別版
代碼中1的phone指是“iphone-x plus”與“小米特別版”也就是當?shù)谝粋€方法進來的時候是為第一個iphone-x的加鎖,當小米進來的時候又是小米所以不是同一個對象無法控制鎖,就好比一間確實上鎖的房子,但訪問者進入了另一間房子。
而且同一個方法最好只加一個synchronized,否則會出現(xiàn)互不相讓而死鎖的情況。
接下來討論三種方法間是否線程互斥,(第二個線程調(diào)用getPhone2()方法)
class Phone {
//1.
public void getPhone(String phone){
synchronized (this){
for (int i=0; i<phone.length();i++){
System.out.print(phone.charAt(i));
}
System.out.println("---------");
}
}
//2.
public synchronized void getPhone2(String phone){
for (int i=0; i<phone.length();i++){
System.out.print(phone.charAt(i));
}
System.out.println("---------");
}
//3.
public static synchronized void getPhone3(String phone){
for (int i=0; i<phone.length();i++){
System.out.print(phone.charAt(i));
}
System.out.println("---------");
}
}
在console上顯示的結(jié)果是12同步、13不同步、23不同步。
12同步:第二個方法其實默認的this與第1個方法是一樣的對象,所以線程是互斥的同步。
13與23不同步:static靜態(tài)方法運行時要有鎖對象與之關(guān)聯(lián),似乎找不到鎖對象,類的字節(jié)碼在內(nèi)存里面也算是對象,靜態(tài)方法運行的時候不用創(chuàng)建類的實例對象,因為靜態(tài)方法在jvm類加載時就已經(jīng)加載并分配內(nèi)存了。而此時字節(jié)碼對象已經(jīng)在內(nèi)存里面了,所以要實現(xiàn)比如13同步就可以用字節(jié)碼對象來保證同步。
(而獲取類的字節(jié)碼在起手式—Object中的getClass()中提到,可以用.class來獲取。)

果然,在改成類的字節(jié)碼之后13的數(shù)據(jù)就正常了,13互斥。
(class Phone 放在外面就相當于Phone.class如果拉到SynchronizedTest內(nèi)部就變成SynchronizedTest.Phone.class所以再建文件時候不會沖突)
(這里又對static靜態(tài)方法、反射、jvm類加載其它知識進行了擴充)