Java多執行緒程式設計-Thread synchronized使用(一)
Java多執行緒程式設計-Thread synchronized使用
我們在進行多執行緒開發的時候,會出現執行緒安全問題。非執行緒安全就是資料出現不一致導致的,對同一個物件中的例項變數進行併發訪問時發生。就是取到的資料是被修改過的。
執行緒主要通過共享對欄位和物件引用欄位所引用的訪問來進行通訊。 這種通訊方式非常有效,但是卻可能導致兩種錯誤:執行緒干擾和記憶體一致性錯誤。 防止這些錯誤所需的工具是同步。
但是,同步會引入執行緒爭用,當兩個或多個執行緒嘗試同時訪問同一資源並使Java執行時更慢地執行一個或多個執行緒,甚至掛起它們的執行時,就會發生執行緒爭用。 飢餓和活鎖是執行緒爭用的形式。
執行緒安全的變數(方法內的變數)
非執行緒安全只是針對例項變數,為了驗證方法內的變數是執行緒安全的,做一下實驗驗證一下:
物件類:
public class ThreadMethodVariableObj {
public void addCount(String threadName) {
try {
int count = 0;
if (threadName.equalsIgnoreCase("thread01")) {
count = 10;
System. out.println("thread01 set count end");
Thread.sleep(3000);
} else {
count = 100;
System.out.println("thread02 set count end");
}
System.out.println("thread name:" + threadName + " ,set count value:" + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執行緒1:
public class ThreadMethodVariableT1 extends Thread {
private ThreadMethodVariableObj threadMethodVariableObj;
public ThreadMethodVariableT1(ThreadMethodVariableObj threadMethodVariableObj, String threadName) {
this.threadMethodVariableObj = threadMethodVariableObj;
this.setName(threadName);
}
@Override
public void run() {
threadMethodVariableObj.addCount(Thread.currentThread().getName());
}
}
執行緒2:
public class ThreadMethodVariableT2 extends Thread {
private ThreadMethodVariableObj threadMethodVariableObj;
public ThreadMethodVariableT2(ThreadMethodVariableObj threadMethodVariableObj, String threadName) {
this.threadMethodVariableObj = threadMethodVariableObj;
this.setName(threadName);
}
@Override
public void run() {
threadMethodVariableObj.addCount(Thread.currentThread().getName());
}
}
執行類:
public class ThreadMethodVariableMain {
public static void main(String[] args) {
ThreadMethodVariableObj threadMethodVariableObj = new ThreadMethodVariableObj();
ThreadMethodVariableT1 threadMethodVariableT1 = new ThreadMethodVariableT1(threadMethodVariableObj, "thread01");
threadMethodVariableT1.start();
ThreadMethodVariableT2 threadMethodVariableT2 = new ThreadMethodVariableT2(threadMethodVariableObj, "thread02");
threadMethodVariableT2.start();
}
}
執行結果:
執行是各個執行緒的值不會影響其他的執行緒中的count值,為什麼會這樣?我們可以通過java記憶體模型進行分析,在java記憶體中,有一部分記憶體的分配是執行緒私有的。而區域性變數是儲存在本地方法棧中,是執行緒私有的。所以不會有執行緒安全問題,可是例項變數就不一樣了,我們來驗證一下:
例項物件類:
public class ThreadInstanceVariableObj {
private int count = 0;
public void addCount(String threadName) {
try {
if (threadName.equalsIgnoreCase("thread01")) {
count = 10;
System.out.println("thread01 set count end");
Thread.sleep(3000);
} else {
count = 100;
System.out.println("thread02 set count end");
}
System.out.println("thread name:" + threadName + " ,set count value:" + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執行緒1:
public class ThreadInstanceVariableT1 extends Thread {
private ThreadInstanceVariableObj threadInstanceVariableObj;
public ThreadInstanceVariableT1(ThreadInstanceVariableObj threadInstanceVariableObj, String threadName) {
this.threadInstanceVariableObj = threadInstanceVariableObj;
this.setName(threadName);
}
@Override
public void run() {
threadInstanceVariableObj.addCount(Thread.currentThread().getName());
}
}
執行緒2:
public class ThreadInstanceVariableT2 extends Thread {
private ThreadInstanceVariableObj threadInstanceVariableObj;
public ThreadInstanceVariableT2(ThreadInstanceVariableObj threadInstanceVariableObj, String threadName) {
this.threadInstanceVariableObj = threadInstanceVariableObj;
this.setName(threadName);
}
@Override
public void run() {
threadInstanceVariableObj.addCount(Thread.currentThread().getName());
}
}
執行類:
public class ThreadInstanceVariableMain {
public static void main(String[] args) {
ThreadInstanceVariableObj threadInstanceVariableObj = new ThreadInstanceVariableObj();
ThreadInstanceVariableT1 threadInstanceVariableT1 = new ThreadInstanceVariableT1(threadInstanceVariableObj, "thread01");
threadInstanceVariableT1.start();
ThreadInstanceVariableT2 threadInstanceVariableT2 = new ThreadInstanceVariableT2(threadInstanceVariableObj, "thread02");
threadInstanceVariableT2.start();
}
}
執行結果:
執行緒1和執行緒2的值一樣,執行緒1的值已經被執行緒2修改為了100,所有執行緒1獲取的值是不對的.我們只需要在例項類ThreadInstanceVariableObj 中的addCount方法中加上synchronized就可以解決,程式碼如下:
//新增同步的操作
public synchronized void addCount(String threadName) {
try {
if (threadName.equalsIgnoreCase("thread01")) {
count = 10;
System.out.println("thread01 set count end");
Thread.sleep(3000);
} else {
count = 100;
System.out.println("thread02 set count end");
}
System.out.println("thread name:" + threadName + " ,set count value:" + count);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
執行結果:
看到的資料現在正常了,沒有被汙染。看看執行是執行緒1先執行完,再執行執行緒2的。如果在web端去訪問的話,就會有請求阻塞的問題,要等一個個請求處理完,這樣會影響效能,但是可以用其他方法解決。
我們怎麼將上面的同步改為非同步的形式呢?可以定義多個ThreadInstanceVariableObj 物件進行,我們來看看
執行程式碼:
public class ThreadInstanceVariableMain {
public static void main(String[] args) {
ThreadInstanceVariableObj threadInstanceVariableObj = new ThreadInstanceVariableObj();
ThreadInstanceVariableObj threadInstanceVariableObj2 = new ThreadInstanceVariableObj();
ThreadInstanceVariableT1 threadInstanceVariableT1 = new ThreadInstanceVariableT1(threadInstanceVariableObj, "thread01");
threadInstanceVariableT1.start();
ThreadInstanceVariableT2 threadInstanceVariableT2 = new ThreadInstanceVariableT2(threadInstanceVariableObj2, "thread02");
threadInstanceVariableT2.start();
}
}
執行結果:
執行結果和上面的執行結果對比可以看出現在是使用非同步的當時進行的,這樣為什麼可以實現呢?其實就是synchronized取得的鎖都是物件鎖,而不是將一段程式碼或者方法當作鎖,哪個執行緒先取得這個鎖其他執行緒就只有等待,但是多個執行緒訪問多個物件,則會在JVM中建立多個鎖。
為了驗證鎖的是物件,我們進行如下現在:
不進行同步操作的時候:
public class ThreadObject {
public void printMethod() {
try {
System.out.println("current thread name:" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("thread is end:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執行程式碼:
public class ThreadObjectMain {
public static void main(String[] args) {
ThreadObject threadObject = new ThreadObject();
ThreadObjectT1 threadObjectT1 = new ThreadObjectT1(threadObject, "thread01");
ThreadObjectT2 threadObjectT2 = new ThreadObjectT2(threadObject, "thread02");
threadObjectT1.start();
threadObjectT2.start();
}
}
執行結果:
進行同步操作的結果:
public class ThreadObject {
public synchronized void printMethod() {
try {
System.out.println("current thread name:" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("thread is end:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結果:
呼叫synchronized宣告的方法,必須要排隊進行。
那現在我們多個執行緒同步呼叫不同的方法,一個方法宣告同步,一個方法不宣告同步看看結果
新增一個方法:printMethod2()
public void printMethod2() {
try {
System.out.println("current thread name (printMethod2):" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("thread is end (printMethod2):" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
更改執行緒2呼叫的方法,試試:
public class ThreadObjectT2 extends Thread {
private ThreadObject threadObject;
public ThreadObjectT2(ThreadObject threadObject, String threadName) {
this.threadObject = threadObject;
this.setName(threadName);
}
@Override
public void run() {
threadObject.printMethod2();
}
}
執行結果為:
由結果可以看出,當一個執行緒呼叫了同步方法,另一個執行緒呼叫非同步的方法時,執行緒1會只有object的鎖,但是執行緒2可以非同步呼叫非同步方法的。
現在全部轉為同步方法,其他的不變看看:
public synchronized void printMethod2() {
try {
System.out.println("current thread name (printMethod2):" + Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("thread is end (printMethod2):" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
執行結果為:
我們將method2改為同步的時候,多執行緒呼叫不同的方法也是同步進行的。由此可以得出相應結論:
1.當執行緒1呼叫物件的同步方法時,會獲取object物件的鎖,但是執行緒2可以非同步呼叫物件的非同步方法
2.當執行緒1呼叫了物件的同步方法時,會獲取object物件的鎖,執行緒2呼叫物件的另外的同步方法時,也需要等執行緒1釋放鎖才能再進行執行,需要等待。