在學習線程的時候,確定會遇到線程同步問題java
多個併發線程之間按照某種機制協調前後次序執行,當有一個線程在對內存進行操做時,其餘線程都不能夠對這個內存地址進行操做,直到該線程完成操做編程
根據上面的定義咱們能夠知道,線程同步問題主要是多線程併發狀況下如何互斥訪問共享資源;在多個線程對同一變量進行讀寫操做時,若是沒有原子性,就可能產生髒數據。實現線程同步有不少的方式,好比同步方法,鎖,阻塞隊列等。bash
那麼java中的線程是如何訪問資源的呢,在學習了一段時間的jvm後,對於線程訪問共享資源有了更深的理解多線程
Java虛擬機在運行Java程序時,會把它所管理的內存區域劃分爲若干個不一樣的區域,每一個區域都有各自的用途;下圖是經典的JVM內存佈局 併發
程序計數器 (PC): 當前線程所執行的字節碼的行號指示器,用於線程切換後能恢復到正確的執行位置jvm
Java虛擬機棧: 每一個線程都有一個本身的Java棧, 描述了Java方法運行時的內存模型:每一個方法在執行時都會建立一個棧幀用於儲存局部變量表、操做數棧、動態連接、方法出口等信息;每一個方法的從開始調用到執行完成的過程,就是棧幀從入棧到出棧的過程ide
本地方法棧:與JAVA棧相似,區別是使用的對象不同,本地方法棧是給Native方法使用的,JAVA虛擬機棧是給JAVA方式使用的函數
在沒有任何同步機制的狀況下,多個線程共享一塊內存區域,對其操做;佈局
不使用共享內存,每一個線程內存空間相互獨立;學習
多線程共享一塊內存區域,可是對這塊共享區域加鎖訪問;
對靜態變量sum進行操做,靜態變量保存在線程共享的內存區域中,多個線程能夠同時訪問
package com.thread;
/**
* @Author: ranjun
* @Date: 2019/7/22 14:13
*/
public class AddTest {
private static int sum = 0;
/**
* 對靜態變量sum進行累加操做
* @param n
* @return
*/
public static int sum(int n){
sum = 0;
for(int i = 0; i <= n; i++){
sum += i;
//讓出cpu資源,讓其餘線程執行;
//能夠將下面這段註釋掉,若是累加次數足夠少時,仍然會獲得正確結果(當前線程能夠在當前時間片中完成累加操做,並返回值)
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return sum;
}
}
複製代碼
寫個main函數,定義四個線程,每一個線程都調用上面的靜態方法,觀察運行結果,基本都是錯誤的:
package com.thread;
/**
* @Author: ranjun
* @Date: 2019/7/22 14:17
*/
public class MainTest {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
while(true){
System.out.println(Thread.currentThread().getName() +":" +AddTest.sum(100));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
複製代碼
部分運行結果,都是錯的
Thread-3:20153
Thread-1:19953
Thread-2:19953
Thread-0:20153
Thread-1:18510
Thread-3:18510
複製代碼
緣由:多個線程同時對靜態全局變量s進行操做致使;
ps:這裏的例子是靜態全局變量s,其實有不少種狀況會引發結果異常問題,如在main方法中new出了一個對象,new出來的對象是存放在堆中的,多個線程共享,此時若是多線程同時操做該對象的話,也是有可能產生錯誤結果;
修改靜態sum方法,使用局部變量sum,以下:
public static int sum(int n){
int sum = 0;
for(int i = 0; i <= n; i++){
sum += i;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return sum;
}
複製代碼
運行程序,結果正確:
Thread-3:5050
Thread-1:5050
Thread-2:5050
Thread-0:5050
Thread-0:5050
Thread-1:5050
Thread-2:5050
Thread-3:5050
複製代碼
public synchronized static int sum(int n){
sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return sum;
}
複製代碼
運行程序,結果正確:
Thread-1:5050
Thread-3:5050
Thread-2:5050
Thread-0:5050
Thread-2:5050
複製代碼
從上面的例子能夠看到,多線程對共享資源的操做結果是難以控制的;因此在多線程編程過程當中,咱們要知道哪些資源是線程共享的,哪些是線程本地私有的,而後合理的對共享資源進行協調。