1. 程式人生 > >Java多執行緒操作區域性變數與全域性變數

Java多執行緒操作區域性變數與全域性變數

在這篇文章裡,我們首先闡述什麼是同步,不同步有什麼問題,然後討論可以採取哪些措施控制同步,接下來我們會仿照回顧網路通訊時那樣,構建一個伺服器端的“執行緒池”,JDK為我們提供了一個很大的concurrent工具包,最後我們會對裡面的內容進行探索。

  為什麼要執行緒同步?

  說到執行緒同步,大部分情況下, 我們是在針對“單物件多執行緒”的情況進行討論,一般會將其分成兩部分,一部分是關於“共享變數”,一部分關於“執行步驟”。

  共享變數

  當我們線上程物件(Runnable)中定義了全域性變數,run方法會修改該變數時,如果有多個執行緒同時使用該執行緒物件,那麼就會造成全域性變數的值被同時修改,造成錯誤。我們來看下面的程式碼:

複製程式碼
 1 class MyRunner implements Runnable
 2 {
 3     public int sum = 0;
 4     
 5     public void run() 
 6     {
 7         System.out.println(Thread.currentThread().getName() + " Start.");
 8         for (int i = 1; i <= 100; i++)
 9         {
10             sum += i;
11         }
12         try
{ 13 Thread.sleep(500); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 System.out.println(Thread.currentThread().getName() + " --- The value of sum is " + sum); 18 System.out.println(Thread.currentThread().getName() + " End.");
19 } 20 } 21 22 23 private static void sharedVaribleTest() throws InterruptedException 24 { 25 MyRunner runner = new MyRunner(); 26 Thread thread1 = new Thread(runner); 27 Thread thread2 = new Thread(runner); 28 thread1.setDaemon(true); 29 thread2.setDaemon(true); 30 thread1.start(); 31 thread2.start(); 32 thread1.join(); 33 thread2.join(); 34 }
複製程式碼

  這個示例中,執行緒用來計算1到100的和是多少,我們知道正確結果是5050(好像是高斯小時候玩過這個?),但是上述程式返回的結果是10100,原因是兩個執行緒同時對sum進行操作。

  執行步驟

  我們在多個執行緒執行時,可能需要某些操作合在一起作為“原子操作”,即在這些操作可以看做是“單執行緒”的,例如我們可能希望輸出結果的樣子是這樣的:

複製程式碼
1 執行緒1:步驟1
2 執行緒1:步驟2
3 執行緒1:步驟3
4 執行緒2:步驟1
5 執行緒2:步驟2
6 執行緒2:步驟3
複製程式碼

  如果同步控制不好,出來的樣子可能是這樣的:

執行緒1:步驟1
執行緒2:步驟1
執行緒1:步驟2
執行緒2:步驟2
執行緒1:步驟3
執行緒2:步驟3

  這裡我們也給出一個示例程式碼:

複製程式碼
 1 class MyNonSyncRunner implements Runnable
 2 {
 3     public void run() {
 4         System.out.println(Thread.currentThread().getName() + " Start.");
 5         for(int i = 1; i <= 5; i++)
 6         {
 7             System.out.println(Thread.currentThread().getName() + " Running step " + i);
 8             try
 9             {
10                 Thread.sleep(50);
11             }
12             catch(InterruptedException ex)
13             {
14                 ex.printStackTrace();
15             }
16         }
17         System.out.println(Thread.currentThread().getName() + " End.");
18     }
19 }
20 
21 
22 private static void syncTest() throws InterruptedException
23 {
24     MyNonSyncRunner runner = new MyNonSyncRunner();
25     Thread thread1 = new Thread(runner);
26     Thread thread2 = new Thread(runner);
27     thread1.setDaemon(true);
28     thread2.setDaemon(true);
29     thread1.start();
30     thread2.start();
31     thread1.join();
32     thread2.join();
33 }
複製程式碼

  如何控制執行緒同步

  既然執行緒同步有上述問題,那麼我們應該如何去解決呢?針對不同原因造成的同步問題,我們可以採取不同的策略。

  控制共享變數

  我們可以採取3種方式來控制共享變數。

  將“單物件多執行緒”修改成“多物件多執行緒”  

  上文提及,同步問題一般發生在“單物件多執行緒”的場景中,那麼最簡單的處理方式就是將執行模型修改成“多物件多執行緒”的樣子,針對上面示例中的同步問題,修改後的程式碼如下:

複製程式碼
 1 private static void sharedVaribleTest2() throws InterruptedException
 2 {
 3     Thread thread1 = new Thread(new MyRunner());
 4     Thread thread2 = new Thread(new MyRunner());
 5     thread1.setDaemon(true);
 6     thread2.setDaemon(true);
 7     thread1.start();
 8     thread2.start();
 9     thread1.join();
10     thread2.join();
11 }
複製程式碼

  我們可以看到,上述程式碼中兩個執行緒使用了兩個不同的Runnable例項,它們在執行過程中,就不會去訪問同一個全域性變數。

  將“全域性變數”降級為“區域性變數”

  既然是共享變數造成的問題,那麼我們可以將共享變數改為“不共享”,即將其修改為區域性變數。這樣也可以解決問題,同樣針對上面的示例,這種解決方式的程式碼如下:

複製程式碼
 1 class MyRunner2 implements Runnable
 2 {
 3     public void run() 
 4     {
 5         System.out.println(Thread.currentThread().getName() + " Start.");
 6         int sum = 0;
 7         for (int i = 1; i <= 100; i++)
 8         {
 9             sum += i;
10         }
11         try {
12             Thread.sleep(500);
13         } catch (InterruptedException e) {
14             e.printStackTrace();
15         }
16         System.out.println(Thread.currentThread().getName() + " --- The value of sum is " + sum);
17         System.out.println(Thread.currentThread().getName() + " End.");
18     }
19 }
20 
21 
22 private static void sharedVaribleTest3() throws InterruptedException
23 {
24     MyRunner2 runner = new MyRunner2();
25     Thread thread1 = new Thread(runner);
26     Thread thread2 = new Thread(runner);
27     thread1.setDaemon(true);
28     thread2.setDaemon(true);
29     thread1.start();
30     thread2.start();
31     thread1.join();
32     thread2.join();
33 }
複製程式碼

  我們可以看出,sum變數已經由全域性變數變為run方法內部的區域性變量了。

  使用ThreadLocal機制

  ThreadLocal是JDK引入的一種機制,它用於解決執行緒間共享變數,使用ThreadLocal宣告的變數,即使線上程中屬於全域性變數,針對每個執行緒來講,這個變數也是獨立的。

  我們可以用這種方式來改造上面的程式碼,如下所示:

複製程式碼
 1 class MyRunner3 implements Runnable
 2 {
 3     public ThreadLocal<Integer> tl = new ThreadLocal<Integer>();
 4     
 5     public void run() 
 6     {
 7         System.out.println(Thread.currentThread().getName() + " Start.");
 8         for (int i = 0; i <= 100; i++)
 9         {
10             if (tl.get() == null)
11             {
12                 tl.set(new Integer(0));
13             }
14             int sum = ((Integer)tl.get()).intValue();
15             sum+= i;
16             tl.set(new Integer(sum));
17             try {
18                 Thread.sleep(10);
19             } catch (InterruptedException e) {
20                 e.printStackTrace();
21             }
22         }
23         
24         System.out.println(Thread.currentThread().getName() + " --- The value of sum is " + ((Integer)tl.get()).intValue());
25         System.out.println(Thread.currentThread().getName() + " End.");
26     }
27 }
28 
29 
30 private static void sharedVaribleTest4() throws InterruptedException
31 {
32     MyRunner3 runner = new MyRunner3();
33     Thread thread1 = new Thread(runner);
34     Thread thread2 = new Thread(runner);
35     thread1.setDaemon(true);
36     thread2.setDaemon(true);
37     thread1.start();
38     thread2.start();
39     thread1.join();
40     thread2.join();
41 }
複製程式碼

  綜上三種方案,第一種方案會降低多執行緒執行的效率,因此,我們推薦使用第二種或者第三種方案。

  控制執行步驟

  說到執行步驟,我們可以使用synchronized關鍵字來解決它。

複製程式碼
 1 class MySyncRunner implements Runnable
 2 {
 3     public void run() {
 4         synchronized(this)
 5         {
 6             System.out.println(Thread.currentThread().getName() + " Start.");
 7             for(int i = 1; i <= 5; i++)
 8             {
 9                 System.out.println(Thread.currentThread().getName() + " Running step " + i);
10                 try
11                 {
12                     Thread.sleep(50);
13                 }
14                 catch(InterruptedException ex)
15                 {
16                     ex.printStackTrace();
17                 }

            
           

相關推薦

Java執行操作區域性變數全域性變數

在這篇文章裡,我們首先闡述什麼是同步,不同步有什麼問題,然後討論可以採取哪些措施控制同步,接下來我們會仿照回顧網路通訊時那樣,構建一個伺服器端的“執行緒池”,JDK為我們提供了一個很大的concurrent工具包,最後我們會對裡面的內容進行探索。   為什麼要執

java執行操作兩個資料庫.

package com.dinglin; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; i

Java執行2.4.生產者消費者之間的關係3

生產者與消費者之間的關係 1、執行緒間通訊舉例的問題解決2 (1)建立學生類 package cn.itcast_05; public class Student { String name; int age; boolean flag; // 預設情況是沒有

java執行通訊(伺服器客戶端)

基於TCP的多執行緒通訊 伺服器執行緒: package com.netproject1; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOExc

Java執行之Callable介面Runnable的實現以及選擇

通過實現Runnable介面的實現 package Thread; import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors; public class RunnableThreadDemo { pr

Java執行初探——yield()方法join()方法

一、執行緒與程序 1、程序是程式(任務)執行過程,持有資源(共享記憶體,共享檔案)和執行緒,程序是動態性的,如果程式沒有執行就不算一個程序。 2、執行緒是系統中最小的執行單元,同一程序中有多個執行緒,執行緒共享程序的資源 Java中建立現成的方式就不再贅述了,有兩種:(1

Java執行的悲觀鎖樂觀鎖

首先介紹一些樂觀鎖與悲觀鎖:   悲觀鎖:總是假設最壞的情況,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會阻塞直到它拿到鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖

java 執行操作 安全操作 synchronized

java 多執行緒操作 安全操作 synchronized 記錄程式碼如下 Thread myThread = new Thread(new Runnable() { @Override public void run() { // synchro

應用java執行實現伺服器端客戶端之間的通訊

package test.concurrent.socket; import java.io.*; import java.net.Socket; /** * Created by dong on 15-6-22. * 伺服器端執行緒處理類 */ public class ServerThread

Java執行之三volatile等待通知機制示例

原子性,可見性與有序性 在多執行緒中,執行緒同步的時候一般需要考慮原子性,可見性與有序性 原子性 原子性定義:一個操作或者多個操作在執行過程中要麼全部執行完成,要麼全部都不執行,不存在執行一部分的情況。 以我們在Java程式碼中經常用到的自增操作i++為例,i++實際上並不是一步操作,而是首先對i的值加一,然

java執行操作例子-(以銀行存錢取錢為例)

     以前一直沒有養成一個梳理知識的習慣,今天覆習到多執行緒的知識的,想到的經典的存錢取錢多執行緒同步機制,今天在這裡梳理下,錯的地方請各位大佬指正1:多執行緒同步的方法1:使用synchronized關鍵字修飾方法和程式碼塊(這就是兩種了)2:使用特殊變數volatit

java執行--ReentrantLock實現生產者消費者模式

一.本例實現 :一對一交替列印, 一.生產者邏輯 :每次只允許一個生產者來進行生產操作(生產者之間互斥訪問倉庫),必須等消費者取走資料之後,才能進行下一次的生產 二.消費者邏輯 :每次只允許一個消費者來進行生產操作(消費者之間互斥訪問倉庫),必須等生產者生產資料之後,才能

Java 執行同步 鎖機制synchronized

要是很多人在等這把鑰匙,等鑰匙還回來以後,誰會優先得到鑰匙?Not guaranteed。象前面例子裡那個想連續使用兩個上鎖房間的傢伙,他中間還鑰匙的時候如果還有其他人在等鑰匙,那麼沒有任何保證這傢伙能再次拿到。 (JAVA規範在很多地方都明確說明不保證,象Thread.sleep()休息後多久會返回執行,相

Java執行探究-Lock物件鎖條件變數

Lock鎖的條件變數 設想這樣的一種情況,現在有一個盤子,一個執行緒負責往盤子裡放一個蘋果,一個執行緒從盤子取一個蘋果,如何保證執行緒A放一個蘋果,執行緒B就把這個蘋果取了,不會出現已經放了好幾個了,執行緒B才一個一個的取,現在限定一個條件,盤子裡每次只

[java執行]高併發ListMap

public static List<ThreadInfo> threads = new ArrayList&

java執行總結-同步容器併發容器的對比介紹

目錄 1 容器集簡單介紹 2 同步容器 3 併發容器 4 案例講解 4.1 Map/Set 4.2 List 4.3 Queue 4.3.1 C

java執行2:區域性變數執行安全,實列變數的非執行安全

java多執行緒2:區域性變數的執行緒安全,實列變數的非執行緒安全 “非執行緒安全“就是在多個執行緒訪問同一個物件的例項變數進行併發訪問時候發生,產生的後果就是”髒讀“,也就是取到的資料其實是被修改過的。 a.多執行緒訪問區域性變數是執行緒安全的。 package multiThread

Java執行(二)之Atomic:原子變數原子類

一、何謂Atomic?  Atomic一詞跟原子有點關係,後者曾被人認為是最小物質的單位。計算機中的Atomic是指不能分割成若干部分的意思。如果一段程式碼被認為是Atomic,則表示這段程式碼在

java執行環境下對變數的讀寫操作的原子性問題

本文轉載自:http://www.cnblogs.com/qlee/archive/2011/09/13/2174434.html 以下多執行緒對int型變數x的操作,哪幾個需要進行同步:( )A. x=y; B. x++; C. ++x; D. x=1;從表面看上去實在

Java執行】共享變數&同步-非同步容器&執行區域性變數

共享變數 (Volatile Atomic) volatile:當多個執行緒訪問一個成員變數的時候,需要這個變數在多個執行緒中可見。 Atomic:Atomic方法對該變數的操作是原子性操作,顆粒度是到對這個變數的一次操作。 private stati