對線程的理解總結

說到線程,咱們必定首先想到的是線程的建立,線程的建立通常有兩種方式 一種是實現 Runnable 接口,另外一種就是 繼承 Thread 類 ,由於Java的單繼承多實現機制,因此,優先選擇 實現 Runnable 接口。java

 1 package test;
 2 
 3 class ThreadTest extends Thread{
 4 
 5 public void run(){
 6 
 7 System.out.println("實現了Thread類");
 8 
 9 }
10 
11 }
12 
13 class RunnableTest implements Runnable{
14 
15 public void run(){
16 
17 System.out.println("實現了runnable接口");
18 
19 }
20 
21 }
22 
23  
24 
25 public class ThreadStartTest {
26 
27 public static void main(String[] args) {
28 
29 //直接繼承了Thread ,建立一個Thread的實例
30 
31 ThreadTest t1 = new ThreadTest();
32 
33 //t1啓動
34 
35 t1.start();
36 
37 //若是是實現了接口的線程類,須要用對象的實例做爲Thread類構造方法的參數
38 
39 Thread t2 = new Thread(new RunnableTest());
40 
41 t2.start();
42 
43 }
44 
45 }

這兒就有一個我好久以前一直不瞭解的坑。那時由於不常用線程類,因此,對線程的開啓僅停留在有兩種方式上面。在使用繼承的方式時,經過new xxxThread()的方式調用Start()方法,但使用接口的方式時 一直也是new xxxThread()d的方式,發現調不了start()方法,就調用了run()方法。.....其實這樣是不對的,對於Java來講,經過new的方式調用內部run()方法一點問題都沒有,但並不會開啓新線程,那樣作只會使用main線程。。正確的方式爲Thread t2 = new Thread(new RunnableTest()); 而後調用start()方法。安全

總之必定要調用start()方法的。多線程

一、那線程開啓了就要考慮線程安全了併發

線程安全,說究竟是數據的安全,我可不認識線程是誰,它安不安全,跟我沒有半毛錢的關係。但數據不能不安全。這裏就要提到內存了,由於,形成數據不安全的就是內存。app

對於一個程序來講,就是一個進程,一個線程是其中的一部分。當系統爲進程分配空間的時候,就會有公共空間(堆,公共方法區),和棧等。而形成不安全的就是這塊公共的內存空間。ide

當一個線程在數據處理的過程當中有另外一個線程對數據進行了修改,就會形成數據不安全,程序混亂。這樣咱們就說這是線程不安全的。高併發

1.一、怎麼解決線程安全問題性能

解決線程安全問題,就要找到線程究竟是怎麼不安全的根本緣由。其次安全與不安全是相對的。若是你的系統只有一個線程運行,或同一時間段不可能有兩個線程同時運行。那也就不存在線程安全問題了。this

那線程不安全是怎麼形成的呢?spa

緣由一:

「每條線程有本身的工做內存,線程的工做內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對該變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存中的變量。不一樣線程之間也沒法直接訪問對方工做內存中的變量,線程間變量值的傳遞均須要經過主內存來完成。」

緣由二:線程搶奪

根本緣由:線程內的關於外部變量的語境,與真實外部語境不一致。

針對這幾個緣由,咱們來提出解決的方案。

解決方案一:拈輕怕重

對於緣由一中 線程的工做內存中保存了被該線程使用到的變量的主內存副本拷貝,那就不要拷貝,咱們全部的方法的參數都使用方法的局部變量,這樣,就不會產生從主內存拷貝的問題。當每個線程來執行方法的時候,系統都會爲該線程分配一塊屬於本身的棧內存,這樣,每一個執行這個方法的線程都會有屬於本身的局部變量,那麼操做本身的局部變量就不會產生安全問題了。

解決方法二:只讀不寫

對於緣由一種的對主內存的拷貝,有時候是不能不拷貝的那,咱們就要看看能不能只容許它讀取,不容許修改,也就是使用 final 修飾等...,這樣,你只能看看個人數據,不能修改,就不會形成安全問題了。

解決方案三:人手一份

就是把變量分給每個線程,讓他們獨立運行。在是實際的開發當中咱們可能會遇到變量在線程中共享的需求。這時咱們可使用 ThreadLocal 定義線程的變量,使用ThreadLocal 定義的變量只在本線程中有效,這樣也不會有安全問題。

 1 @RestController
 2 @RequestMapping("/test")
 3 public class TestController {
 4  8 
 9     static class MyThread implements Runnable {
10         Test test;
11 
12         public MyThread(Test test) {
13             this.test = test;
14         }
15 
16         @Override
17         public void run() {
18             for (int i = 0; i < 3; i++) {
19                 test.dd();
20             }
21 
22         }
23 
24     }
25 
26     static class Test {
27         ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
28 
29         public void dd() {
30             try {
31                 if (threadLocal.get() == null)
32                     threadLocal.set(0);
33                 Integer tt = threadLocal.get();
34                 tt += 60;
35                 threadLocal.set(tt);
36                 System.out.println(Thread.currentThread().getName() + "輸出值爲:" + threadLocal.get());
37 
38                 Thread.sleep(100);
39             } catch (InterruptedException e) {
40                 e.printStackTrace();
41             }
42         }
43     }
44 
45     public static void main(String[] args) {
46         Test test = new Test();
47         for (int i = 0; i < 3; i++) {
48             MyThread myThread = new MyThread(test);
49             new Thread(myThread).start();
50 
51         }
52     }
53 }

在本例中有三個線程,變量使用 ThreadLocal 定義,經過 Test test = new Test(); 對線程傳入相同的 Test 實例。這樣避免使用不一樣的Test 實例 產生不一樣的ThreadLocal 變量對象。進而在 每一個線程中循環3次進行 ThreadLocal 累加

上例運行結果以下:

能夠看到線程之間沒有產生累加,但同一線程中進行了累加。

 

解決方案四:加悲觀鎖

對於以上方法都不能知足咱們的需求,那咱們就只能採起更加嚴格的方式了,那就是加鎖,只要加鎖,那就要產生線程的阻塞,性能就會打折扣了。悲觀鎖就是悲觀的認爲只要我不加鎖,那個人數據就會被其餘線程修改,因此每次操做都要加鎖,直到操做完成。

下面我將上面的代碼修改一下,成爲加悲觀鎖的狀況:

 1 public class TestController {
 2 
 3     static class MyThread implements Runnable{
 4         Test test;
 5         public MyThread(Test test){
 6             this.test=test;
 7         }
 8         @Override
 9         public void run() {
10             for (int i = 0; i <3 ; i++) {
11                 test.dd();
12             }
13 
14         }
15 
16     }
17     static class Test{
18         Integer threadLocal=0;
19         Lock lock=new ReentrantLock();
20         public  void dd(){
21             try {
22                 lock.lock();
23 //            Integer tt=threadLocal.get();
24                 threadLocal+=60;
25 //            threadLocal.set(tt);
26                 System.out.println(Thread.currentThread().getName()+ "輸出值爲:"+    threadLocal);
27 
28                 Thread.sleep(100);
29             } catch (InterruptedException e) {
30                 e.printStackTrace();
31             }finally {
32                 lock.unlock();
33             }
34         }
35     }
36 
37     public static void main(String[] args) {
38         Test test=new Test();
39         for (int i = 0; i <3 ; i++) {
40             MyThread myThread=new MyThread(test);
41             new Thread(myThread).start();
42 
43         }
44     }
45 }

在這個例子中咱們把 ThreadLocal 替換爲了普通的 Integer 變量,並使用了 Lock 進行加鎖。咱們一樣開啓三個線程,並在線程中進行3次循環。並執行累加,沒有ThreadLocal 全部的線程公用一個變量,結果以下:

能夠看到線程執行的順序不必定,但輸出的結果,沒有出現錯誤。

 解決方案五:加樂觀鎖

樂觀鎖與悲觀鎖相對,樂觀鎖認爲,大機率沒有線程會修改個人數據,若是修改了那就只能從新執行操做。若是在高併發狀況下使用樂觀鎖,可能會更加浪費系統資源。那具體怎麼操做呢?

  • synchronized是悲觀鎖,這種線程一旦獲得鎖,其餘須要鎖的線程就掛起的狀況就是悲觀鎖。
  • CAS操做的就是樂觀鎖,每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。

參考:Java:CAS(樂觀鎖)

volatile 關鍵字

咱們知道volatile關鍵字的做用是保證變量在多線程之間的可見性,它是java.util.concurrent包的核心,沒有volatile就沒有這麼多的併發類給咱們使用。

相關文章
相關標籤/搜索