最近公司某同事非常愛學,下班回家后也會抱書學習,看到多線程寫例子的時候遇到了非常奇怪的問題,故而將例子發給我看讓給解答,下面給出例子。
1.第一例及運行結果
下面是示例代碼

1 package com.coderweb.sys.util; 2 3 public class TxtThread implements Runnable { 4 5 Integer num = 10000; 6 String str = new String(); 7 8 @Override 9 public void run() {10 synchronized (num) {11 while (true) {12 if (num > 0) {13 try {14 // Thread.sleep(10);15 } catch (Exception e) {16 e.getMessage();17 }18 System.out.PRintln(Thread.currentThread().getName()19 + " this is " + num--);20 21 // str+="1";22 } else {23 break;24 }25 26 }27 }28 }29 30 public static void main(String[] args) {31 TxtThread tt = new TxtThread();32 new Thread(tt).start();33 new Thread(tt).start();34 new Thread(tt).start();35 new Thread(tt).start();36 }37 }View Code下面是運行截圖的一部分
Thread-0 this is 10000Thread-2 this is 9999Thread-0 this is 9998Thread-2 this is 9997Thread-3 this is 9995Thread-0 this is 9996Thread-3 this is 9993Thread-3 this is 9991Thread-3 this is 9990Thread-3 this is 9989Thread-3 this is 9988Thread-3 this is 9987Thread-3 this is 9986Thread-3 this is 9985
問題一:Integer不是對象嗎?對象引用不變的呀?修改值后應該不變的呀?為啥起了四個線程后,四個線程都對一個對象里的值進行修改了呢?為啥synchronized語句塊沒有起到任何作用呢?
帶著問題一,修改例子
2.第二例及運行結果(唯一不同的地方是,synchronized(中的內容))

1 package com.coderweb.sys.util; 2 3 public class TxtThread implements Runnable { 4 5 Integer num = 10000; 6 String str = new String(); 7 8 @Override 9 public void run() {10 synchronized (str) {11 while (true) {12 if (num > 0) {13 try {14 // Thread.sleep(10);15 } catch (Exception e) {16 e.getMessage();17 }18 System.out.println(Thread.currentThread().getName()19 + " this is " + num--);20 21 // str+="1";22 } else {23 break;24 }25 26 }27 }28 }29 30 public static void main(String[] args) {31 TxtThread tt = new TxtThread();32 new Thread(tt).start();33 new Thread(tt).start();34 new Thread(tt).start();35 new Thread(tt).start();36 }37 }View Code運行結果部分
Thread-0 this is 10000Thread-0 this is 9999Thread-0 this is 9998Thread-0 this is 9997Thread-0 this is 9996Thread-0 this is 9995
.........
Thread-0 this is 5Thread-0 this is 4Thread-0 this is 3Thread-0 this is 2Thread-0 this is 1
通過問題2,可以得出總結,對于String字符串在初始化后,其引用地址沒有發生變化,后面也沒有進行修改,因此多個線程同時訪問的時候起到了互斥的作用,當四個線程啟動后,哪個線程先進入代碼塊進行了加鎖,誰將一直持有該鎖直到該線程結束,其余線程發現有線程持有該string的鎖,將處于等待狀態,因此結論便是,這四個線程,哪個線程先進入同步快,將一直打印該線程的數據。
由問題1跟2的不同運行結果發現,區別之處在于第一例子中的synchronized是num,并在后面進行了減法操作,而第二個例子中的synchronized是str,并且該str沒有發生變化,難道是因為num改變之后引用地址發生變化了?下面給出思考問題的驗證例子3跟4
3.第三例及運行結果

package com.coderweb.sys.util;public class TxtThread implements Runnable { Integer num = 10000; String str = new String(); Integer testI = 0; @Override public void run() { synchronized (testI) { while (true) { if (num > 0) { try { // Thread.sleep(10); } catch (Exception e) { e.getMessage(); } System.out.println(Thread.currentThread().getName() + " this is " + num--); // str+="1"; } else { break; } } } } public static void main(String[] args) { TxtThread tt = new TxtThread(); new Thread(tt).start(); new Thread(tt).start(); new Thread(tt).start(); new Thread(tt).start(); }}View Code運行結果
Thread-0 this is 10000Thread-0 this is 9999Thread-0 this is 9998Thread-0 this is 9997Thread-0 this is 9996Thread-0 this is 9995Thread-0 this is 9994Thread-0 this is 9993
。。。。。。
Thread-0 this is 7Thread-0 this is 6Thread-0 this is 5Thread-0 this is 4Thread-0 this is 3Thread-0 this is 2Thread-0 this is 1
該例子的不同之處在于,新加了一個成員變量testI,并且沒有對該值進行操作,發現結果居然成功,只有一個線程持有鎖,這也就驗證了Integer類型的確是引用類型,在
創建完成后的引用地址沒有發生變化。那么猜想string如果內容變了會怎樣呢?例子4進行驗證
4.第四例及運行結果

1 package com.coderweb.sys.util; 2 3 public class TxtThread implements Runnable { 4 5 Integer num = 10000; 6 String str = new String(); 7 8 @Override 9 public void run() {10 synchronized (str) {11 while (true) {12 if (num > 0) {13 try {14 // Thread.sleep(10);15 } catch (Exception e) {16 e.getMessage();17 }18 System.out.println(Thread.currentThread().getName()19 + " this is " + num--);20 21 str+="1";22 } else {23 break;24 }25 26 }27 }28 }29 30 public static void main(String[] args) {31 TxtThread tt = new TxtThread();32 new Thread(tt).start();33 new Thread(tt).start();34 new Thread(tt).start();35 new Thread(tt).start();36 }37 }View Code運行部分結果
.............................
Thread-3 this is 9774Thread-2 this is 9779Thread-3 this is 9773Thread-0 this is 9777Thread-3 this is 9771Thread-3 this is 9769Thread-3 this is 9768Thread-2 this is 9772Thread-3 this is 9767Thread-0 this is 9770Thread-3 this is 9765Thread-2 this is 9766Thread-3 this is 9763Thread-0 this is 9764Thread-3 this is 9761
..............................
該例子的不同之處在于,在循環最后不停的對str進行修改,所以導致了多個線程同時訪問,并沒有起到加鎖的作用。
但是我們的都知道,string類型變量是不可變的,也就是所說的immutable,就是說在對象創建之后,該string的引用類型變量是不變的,如果對該變量進行修改操作之后,會重新建立對象,并將新對象的地址賦給該引用,也就是說例子中的不停的修改str對象就相當于不停的創建新對象并賦給該引用。這個例子還好理解,畢竟我們對string還稍微有點了解,但是為什么Integer也會有這樣的效果呢,難道我們對Integer進行了修改之后起引用地址也發生了變化?下面查看了jdk關于Integer封裝類的源碼
5.JDK中關于Integer的部分源碼
public final class Integer extends Number implements Comparable<Integer> { /** * The value of the <code>Integer</code>. * * @serial */ private final int value;/** * Compares this object to the specified object. The result is * <code>true</code> if and only if the argument is not * <code>null</code> and is an <code>Integer</code> object that * contains the same <code>int</code> value as this object. * * @param obj the object to compare with. * @return <code>true</code> if the objects are the same; * <code>false</code> otherwise. */ public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; } /** * Returns a hash code for this <code>Integer</code>. * * @return a hash code value for this object, equal to the * primitive <code>int</code> value represented by this * <code>Integer</code> object. */ public int hashCode() { return value; }觀察上面的源碼我們便能明白道理了,在Integer封裝類中,利用了一個final的int類型,也就是說一旦對象創建,該值便不能改變了,但是為啥我們還能對其進行修改呢,所以必定是我們修改了之后,會創建新的地址,并賦給新的引用,我們先通過下面例子驗證一把是否引用地址發生了變化

1 public static void main(String[] args) { 2 // TxtThread tt = new TxtThread(); 3 // new Thread(tt).start(); 4 // new Thread(tt).start(); 5 // new Thread(tt).start(); 6 // new Thread(tt).start(); 7 8 Integer number = 5; 9 Integer number2 = number;10 number2--;11 System.out.println("number---"+number);12 System.out.println("number2---"+number2);13 System.out.println("number ==number2? "+(number==number2));14 }View Code這個例子中,我們定義了第一個對象,這個時候第一個對象地址沒有發生變化,這時我們創建了新對象,并指向第一個對象,這時候兩個對象的引用地址是一樣的,緊接著我們對第二個對象進行了修改,當然其值是發生了變化,其實我們可以想一下,如果地址沒有發生變化的話,5是怎么等于4的呢?所以地址必然不一樣,最后的false也就驗證了這一點。當然咱通過Integer的源代碼發現,其equals方法也是通過判斷其中的值類判斷兩個Integer是否相等的。
綜上所有事例得出結論:Integer這類對于基本數據類型的封裝類,當其值發生改變時,其引用地址也發生了變化。
新聞熱點
疑難解答