1. 程式人生 > >java中實現執行緒同步的7種方法

java中實現執行緒同步的7種方法

同步的方法:

一、同步方法

  即有synchronized關鍵字修飾的方法。 由於java的每個物件都有一個內建鎖,當用此關鍵字修飾方法時, 內建鎖會保護整個方法。在呼叫該方法前,需要獲得內建鎖,否則就處於阻塞狀態。 注: synchronized關鍵字也可以修飾靜態方法,此時如果呼叫該靜態方法,將會鎖住整個類。

二、同步程式碼塊

  即有synchronized關鍵字修飾的語句塊。 被該關鍵字修飾的語句塊會自動被加上內建鎖,從而實現同步     程式碼如: 
synchronized(object){ 
}
   注:同步是一種高開銷的操作,因此應該儘量減少同步的內容。通常沒有必要同步整個方法,使用synchronized程式碼塊同步關鍵程式碼即可。  複製程式碼
    package com.xhj.thread;
 
    /**
     * 執行緒同步的運用
     * 
     * @author XIEHEJUN
     * 
     */
    public class SynchronizedThread {
 
        class Bank {
            private int account = 100;
            public int getAccount() {
                return account;
            }
 
            /**
             * 用同步方法實現
             * 
             * 
@param money */ public synchronized void save(int money) { account += money; } /** * 用同步程式碼塊實現 * * @param money */ public void save1(int money) { synchronized
(this) { account += money; } } }
複製程式碼 複製程式碼
class NewThread implements Runnable {
            private Bank bank;
 
            public NewThread(Bank bank) {
                this.bank = bank;
            }
 
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    // bank.save1(10);
                    bank.save(10);
                    System.out.println(i + "賬戶餘額為:" + bank.getAccount());
                }
            }
 
        }
 
        /**
         * 建立執行緒,呼叫內部類
         */
        public void useThread() {
            Bank bank = new Bank();
            NewThread new_thread = new NewThread(bank);
            System.out.println("執行緒1");
            Thread thread1 = new Thread(new_thread);
            thread1.start();
            System.out.println("執行緒2");
            Thread thread2 = new Thread(new_thread);
            thread2.start();
        }
 
        public static void main(String[] args) {
            SynchronizedThread st = new SynchronizedThread();
            st.useThread();
        }
 
    }
複製程式碼 =====================================

示例加講解

同步是多執行緒中的重要概念。同步的使用可以保證在多執行緒執行的環境中,程式不會產生設計之外的錯誤結果。同步的實現方式有兩種,同步方法同步塊,這兩種方式都要用到synchronized關鍵字。

同步方法:給一個方法增加synchronized修飾符之後就可以使它成為同步方法,這個方法可以是靜態方法和非靜態方法,但是不能是抽象類的抽象方法,也不能是介面中的介面方法。下面程式碼是一個同步方法的示例:

複製程式碼
public synchronized void aMethod() { 
    // do something 
} 

public static synchronized void anotherMethod() { 
    // do something 
} 
複製程式碼

執行緒在執行同步方法時是具有排它性的。當任意一個執行緒進入到一個物件的任意一個同步方法時,這個物件的所有同步方法都被鎖定了,在此期間,其他任何執行緒都不能訪問這個物件的任意一個同步方法,直到這個執行緒執行完它所呼叫的同步方法並從中退出,從而導致它釋放了該物件的同步鎖之後。在一個物件被某個執行緒鎖定之後,其他執行緒是可以訪問這個物件的所有非同步方法的。

同步塊:同步塊是通過鎖定一個指定的物件,來對同步塊中包含的程式碼進行同步;而同步方法是對這個方法塊裡的程式碼進行同步,而這種情況下鎖定的物件就是同步方法所屬的主體物件自身。如果這個方法是靜態同步方法呢?那麼執行緒鎖定的就不是這個類的物件了,也不是這個類自身,而是這個類對應的java.lang.Class型別的物件。同步方法和同步塊之間的相互制約只限於同一個物件之間,所以靜態同步方法只受它所屬類的其它靜態同步方法的制約,而跟這個類的例項(物件)沒有關係。

如果一個物件既有同步方法,又有同步塊,那麼當其中任意一個同步方法或者同步塊被某個執行緒執行時,這個物件就被鎖定了,其他執行緒無法在此時訪問這個物件的同步方法,也不能執行同步塊。

synchronized 關鍵字用於保護共享資料。請大家注意“共享資料”,你一定要分清哪些資料是共享資料,請看下面的例子:

複製程式碼
public class ThreadTest implements Runnable{

public synchronized void run(){
  for(int i=0;i<10;i++) {
    System.out.print(" " + i);
  }
}

public static void main(String[] args) {
  Runnable r1 = new ThreadTest(); //也可寫成ThreadTest r1 = new ThreadTest();
  Runnable r2 = new ThreadTest();
  Thread t1 = new Thread(r1);
  Thread t2 = new Thread(r2);
  t1.start();
  t2.start();
}}
複製程式碼

在這個程式中,run()雖然被加上了synchronized 關鍵字,但保護的不是共享資料。因為這個程式中的t1,t2 是兩個物件(r1,r2)的執行緒。而不同的物件的資料是不同的,r1,r2 有各自的run()方法,所以輸出結果無法預知。

synchronized的目的是使同一個物件的多個執行緒,在某個時刻只有其中的一個執行緒可以訪問這個物件的synchronized 資料。每個物件都有一個“鎖標誌”,當這個物件的一個執行緒訪問這個物件的某個synchronized 資料時,這個物件的所有被synchronized 修飾的資料將被上鎖(因為“鎖標誌”被當前執行緒拿走了),只有當前執行緒訪問完它要訪問的synchronized 資料時,當前執行緒才會釋放“鎖標誌”,這樣同一個物件的其它執行緒才有機會訪問synchronized 資料。

示例3:

複製程式碼
public class ThreadTest implements Runnable{

public synchronized void run(){
  for(int i=0;i<10;i++){
    System.out.print(" " + i);
  }
}

public static void main(String[] args){
  Runnable r = new ThreadTest();
  Thread t1 = new Thread(r);
  Thread t2 = new Thread(r);
  t1.start();
  t2.start();
}}
複製程式碼

如果你執行1000 次這個程式,它的輸出結果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因為這裡的synchronized 保護的是共享資料。t1,t2 是同一個物件(r)的兩個執行緒,當其中的一個執行緒(例如:t1)開始執行run()方法時,由於run()受synchronized保護,所以同一個物件的其他執行緒(t2)無法訪問synchronized 方法(run 方法)。只有當t1執行完後t2 才有機會執行。

示例4:

複製程式碼
public class ThreadTest implements Runnable{

public void run(){

    synchronized(this){
    for(int i=0;i<10;i++){
        System.out.print(" " + i);
    }
} 
}

public static void main(String[] args){
    Runnable r = new ThreadTest();
    Thread t1 = new Thread(r);
    Thread t2 = new Thread(r);
    t1.start();
    t2.start();
}
}    
複製程式碼

這個程式與示例3 的執行結果一樣。在可能的情況下,應該把保護範圍縮到最小,可以用示例4 的形式,this 代表“這個物件”。沒有必要把整個run()保護起來,run()中的程式碼只有一個for迴圈,所以只要保護for 迴圈就可以了。

示例5:

複製程式碼
public class ThreadTest implements Runnable{

public void run(){
  for(int k=0;k<5;k++){
    System.out.println(Thread.currentThread().getName()+ " : for loop : " + k);
  }

synchronized(this){
  for(int k=0;k<5;k++) {
    System.out.println(Thread.currentThread().getName()+ " : synchronized for loop : " + k);
  }} }

public static void main(String[] args){
  Runnable r = new ThreadTest();
  Thread t1 = new Thread(r,"t1_name");
  Thread t2 = new Thread(r,"t2_name");
  t1.start();
  t2.start();
} }
複製程式碼

執行結果:

t1_name : for loop : 0

t1_name : for loop : 1

t1_name : for loop : 2

t2_name : for loop : 0

t1_name : for loop : 3

t2_name : for loop : 1

t1_name : for loop : 4

t2_name : for loop : 2

t1_name : synchronized for loop : 0

t2_name : for loop : 3

t1_name : synchronized for loop : 1

t2_name : for loop : 4

t1_name : synchronized for loop : 2

t1_name : synchronized for loop : 3

t1_name : synchronized for loop : 4

t2_name : synchronized for loop : 0

t2_name : synchronized for loop : 1

t2_name : synchronized for loop : 2

t2_name : synchronized for loop : 3

t2_name : synchronized for loop : 4

第一個for 迴圈沒有受synchronized 保護。對於第一個for 迴圈,t1,t2 可以同時訪問。執行結果表明t1 執行到了k=2 時,t2 開始執行了。t1 首先執行完了第一個for 迴圈,此時t2還沒有執行完第一個for 迴圈(t2 剛執行到k=2)。t1 開始執行第二個for 迴圈,當t1的第二個for 迴圈執行到k=1 時,t2 的第一個for 迴圈執行完了。t2 想開始執行第二個for 迴圈,但由於t1 首先執行了第二個for 迴圈,這個物件的鎖標誌自然在t1 手中(synchronized 方法的執行權也就落到了t1 手中),在t1 沒執行完第二個for 迴圈的時候,它是不會釋放鎖標誌的。所以t2 必須等到t1 執行完第二個for 迴圈後,它才可以執行第二個for 迴圈。

=====================================

三、wait與notify

wait():使一個執行緒處於等待狀態,並且釋放所持有的物件的lock。

sleep():使一個正在執行的執行緒處於睡眠狀態,是一個靜態方法,呼叫此方法要捕捉InterruptedException異常。
notify():喚醒一個處於等待狀態的執行緒,注意的是在呼叫此方法的時候,並不能確切的喚醒某一個等待狀態的執行緒,而是由JVM確定喚醒哪個執行緒,而且不是按優先順序。
Allnotity():喚醒所有處入等待狀態的執行緒,注意並不是給所有喚醒執行緒一個物件的鎖,而是讓它們競爭。

四、使用特殊域變數(volatile)實現執行緒同步

    a.volatile關鍵字為域變數的訪問提供了一種免鎖機制     b.使用volatile修飾域相當於告訴虛擬機器該域可能會被其他執行緒更新     c.因此每次使用該域就要重新計算,而不是使用暫存器中的值      d.volatile不會提供任何原子操作,它也不能用來修飾final型別的變數      例如:          在上面的例子當中,只需在account前面加上volatile修飾,即可實現執行緒同步。      程式碼例項:    複製程式碼
        //只給出要修改的程式碼,其餘程式碼與上同
        class Bank {
            //需要同步的變數加上volatile
            private volatile int account = 100;
 
            public int getAccount() {
                return account;
            }
            //這裡不再需要synchronized 
            public void save(int money) {
                account += money;
            }
        }
複製程式碼     注:多執行緒中的非同步問題主要出現在對域的讀寫上,如果讓域自身避免這個問題,則就不需要修改操作該域的方法。      用final域,有鎖保護的域和volatile域可以避免非同步的問題。 

五、使用重入鎖實現執行緒同步

    在JavaSE5.0中新增了一個java.util.concurrent包來支援同步。      ReentrantLock類是可重入、互斥、實現了Lock介面的鎖,它與使用synchronized方法和快具有相同的基本行為和語義,並且擴充套件了其能力。  ReenreantLock類的常用方法有:
ReentrantLock() : 建立一個ReentrantLock例項 
lock() : 獲得鎖 
unlock() : 釋放鎖 
注:ReentrantLock()還有一個可以建立公平鎖的構造方法,但由於能大幅度降低程式執行效率,不推薦使用      例如:          在上面例子的基礎上,改寫後的程式碼為:  複製程式碼
       //只給出要修改的程式碼,其餘程式碼與上同
        class Bank {
            
            private int account = 100;
            //需要宣告這個鎖
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //這裡不再需要synchronized 
            public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }
                
            }
        }
複製程式碼     注:關於Lock物件和synchronized關鍵字的選擇:          a.最好兩個都不用,使用一種java.util.concurrent包提供的機制,能夠幫助使用者處理所有與鎖相關的程式碼。          b.如果synchronized關鍵字能滿足使用者的需求,就用synchronized,因為它能簡化程式碼          c.如果需要更高階的功能,就用ReentrantLock類,此時要注意及時釋放鎖,否則會出現死鎖,通常在finally程式碼釋放鎖 

六、使用區域性變數實現執行緒同步

    如果使用ThreadLocal管理變數,則每一個使用該變數的執行緒都獲得該變數的副本,副本之間相互獨立,這樣每一個執行緒都可以隨意修改自己的變數副本,而不會對其他執行緒產生影響。      ThreadLocal 類的常用方法
ThreadLocal() : 建立一個執行緒本地變數 
get() : 返回此執行緒區域性變數的當前執行緒副本中的值 
initialValue() : 返回此執行緒區域性變數的當前執行緒的"初始值" 
set(T value) : 將此執行緒區域性變數的當前執行緒副本中的值設定為value
    例如:          在上面例子基礎上,修改後的程式碼為:  複製程式碼
        //只改Bank類,其餘程式碼與上同
        public class Bank{
            //使用ThreadLocal類管理共享變數account
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                account.set(account.get()+money);
            }
            public int getAccount(){
                return account.get();
            }
        }
複製程式碼     注:ThreadLocal與同步機制          a.ThreadLocal與同步機制都是為了解決多執行緒中相同變數的訪問衝突問題。          b.前者採用以"空間換時間"的方法,後者採用以"時間換空間"的方式

七、使用阻塞佇列實現執行緒同步

前面5種同步方式都是在底層實現的執行緒同步,但是我們在實際開發當中,應當儘量遠離底層結構。 使用javaSE5.0版本中新增的java.util.concurrent包將有助於簡化開發。 本小節主要是使用LinkedBlockingQueue<E>來實現執行緒的同步 LinkedBlockingQueue<E>是一個基於已連線節點的,範圍任意的blocking queue。 佇列是先進先出的順序(FIFO),關於佇列以後會詳細講解~LinkedBlockingQueue 類常用方法 LinkedBlockingQueue() : 建立一個容量為Integer.MAX_VALUE的LinkedBlockingQueue put(E e) : 在隊尾新增一個元素,如果佇列滿則阻塞 size() : 返回佇列中的元素個數 take() : 移除並返回隊頭元素,如果佇列空則阻塞程式碼例項: 實現商家生產商品和買賣商品的同步

注:BlockingQueue<E>定義了阻塞佇列的常用方法,尤其是三種新增元素的方法,我們要多加註意,當佇列滿時:

  add()方法會丟擲異常

  offer()方法返回false

  put()方法會阻塞

7.使用原子變數實現執行緒同步

需要使用執行緒同步的根本原因在於對普通變數的操作不是原子的。

那麼什麼是原子操作呢?原子操作就是指將讀取變數值、修改變數值、儲存變數值看成一個整體來操作即-這幾種行為要麼同時完成,要麼都不完成。在java的util.concurrent.atomic包中提供了建立了原子型別變數的工具類,使用該類可以簡化執行緒同步。其中AtomicInteger 表可以用原子方式更新int的值,可用在應用程式中(如以原子方式增加的計數器),但不能用於替換Integer;可擴充套件Number,允許那些處理機遇數字類的工具和實用工具進行統一訪問。

AtomicInteger類常用方法:

AtomicInteger(int initialValue) : 建立具有給定初始值的新的

AtomicIntegeraddAddGet(int dalta) : 以原子方式將給定值與當前值相加

get() : 獲取當前值

程式碼例項:

只改Bank類,其餘程式碼與上面第一個例子同

複製程式碼
class Bank {
    private AtomicInteger account = new AtomicInteger(100);
    public AtomicInteger getAccount() {
        return account; 
    } 
    public void save(int money) {
        account.addAndGet(money);
    }
}
複製程式碼

補充--原子操作主要有:  

對於引用變數和大多數原始變數(long和double除外)的讀寫操作;  

對於所有使用volatile修飾的變數(包括long和double)的讀寫操作。

相關推薦

java實現執行同步7方法

同步的方法: 一、同步方法   即有synchronized關鍵字修飾的方法。 由於java的每個物件都有一個內建鎖,當用此關鍵字修飾方法時, 內建鎖會保護整個方法。在呼叫該方法前,需要獲得內建鎖,否則就處於阻塞狀態。 注: synchronized關鍵字也可以修飾靜態方法

Java實現執行同步的幾常用方式

首先講一下為什麼要實現執行緒同步: java允許多執行緒併發控制,當多個執行緒同時操作一個可共享的資源變數時(如資料的增刪改查),  將會導致資料不準確,相互之間產生衝突,因此加入同步鎖以避免在該執行緒沒有完成操作之前,被其他執行緒的呼叫, 從而保證了該變數的唯一性和準

java建立執行的三方法以及區別

Java使用Thread類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的例項。Java可以用三種方式來建立執行緒,如下所示: 1)繼承Thread類建立執行緒 2)實現Runnable介面建立執行緒 3)使用Callable和Future建立執行緒 下面

Java建立執行的幾方式以及執行同步的幾方式

執行緒同步自己及基本就用過Thread和Runnable這兩種方式,還有其他很多方式如下: Executor框架簡介 建立執行緒有幾種不同的方式?你喜歡哪一種?為什麼? 而執行緒同步會用的方式就更少了,只會synchronized,其他方式如下: 關於執

java執行同步實現方法一(將方法設定為synchronized)

一. 簡要說明:  對於java中的執行緒同步來說,可以用synchronized關鍵字來修飾,既可以對方法進行修飾,也可以對變數進行修飾,而二者都可以實現執行緒的同步。本篇說的是第一種方法,第二種方法在下一篇中說明。 二. 例子:     AccountRunnable.

Java結束執行的三方式

原文地址:http://blog.csdn.net/anhuidelinger/article/details/11746365     有三種方法可以使終止執行緒。      1.  使用退出標誌,使執行緒正常退出,也就是當run方法完成後執行緒終止。      2.  使用stop方法強行終止執行緒(

JAVA實現執行相互呼叫或回撥

使用場景: 在工作中,遇到同時兩個執行緒A和B,按照通常的理解,A呼叫B以後A繼續執行,但是在我目前的情況下需要B執行完畢以後才能繼續執行A後面的程式,於是有了此文章。 請看程式碼: 首先定義一個介面

Java執行同步跟定時任務

執行緒的同步 在一般情況下,建立一個執行緒是不能提高程式的執行效率的,所以要建立多個執行緒。但是多個執行緒同時執行的時候可能呼叫執行緒函式,在多個執行緒同時對同一個記憶體地址進行寫入,由於CPU時間

java執行同步5方法

一、引言前幾天面試,被大師虐殘了,好多基礎知識必須得重新拿起來啊。閒話不多說,進入正題。二、為什麼要執行緒同步因為當我們有多個執行緒要同時訪問一個變數或物件時,如果這些執行緒中既有讀又有寫操作時,就會導致變數值或物件的狀態出現混亂,從而導致程式異常。舉個例子,如果一個銀行賬戶

java執行同步問題 模擬出售火車票

/*  功能:模擬火車售票視窗  Thread.currentThread().getName()//獲取到當前執行緒的名稱  1.解決所有執行緒共享 tickets。  解決思路:①將tickets的資料型別改為static。建立了3個視窗, 每一個視窗代表一個執行緒。

Java實現執行的方式

Java中實現執行緒的方式 Java中實現多執行緒的方式的方式中最核心的就是 run()方法,不管何種方式其最終都是通過run()來執行。 Java剛釋出時也就是JDK 1.0版本提供了兩種實現方式,一個是繼承Thread類,一個是實現Runnable介面。兩種方式都是去重寫run()方法,在run(

用程式碼說話:如何在Java實現執行

併發程式設計是Java語言的重要特性之一,“如何在Java中實現執行緒”是學習併發程式設計的入門知識,也是Java工程師面試必備的基礎知識。本文從執行緒說起,然後用程式碼說明如何在Java中實現執行緒。 一、什麼是執行緒? 執行緒是作業系統能夠進行運算排程的最小單位,它被包含在程序之中,是程序中的實際運作單位

Java基礎-建立執行的三方法

1.繼承Thread類 1)定義Thread類的子類,並重寫run方法,run方法就是執行緒要執行的任務,將其稱為執行體。 2)建立Thread類子類的物件,即建立了執行緒物件。 3)呼叫執行緒物件的start()方法來啟動該執行緒。 此方法需要覆蓋掉其中的run()方法。 1 public

java建立一個執行的兩方法及區別

第一種方法:繼承Thread類 public class NewThread extends Thread { public void run() { for(int i=0;i<20;i++) { System.out.println(i); } } }

Java停止一個執行的幾方法

Java中停止一個執行緒有三種方法,分別是stop,interrupt和設定標誌位,我們依次來看一下這三種方法。 首先不推薦使用stop方法,原因有兩點: 1、原則上只要一呼叫thread.stop()方法,執行緒就會立即停止,並丟擲ThreadDeath error,查看

Java建立多執行的三方法

Java多執行緒實現方式主要有三種:繼承Thread類、實現Runnable介面、使用ExecutorService、Callable、Future實現有返回結果的多執行緒。其中前兩種方式執行緒執行完後都沒有返回值,只有最後一種是帶返回值的。1、繼承Thread類實現多執行緒

java實現定時器的四方法(轉載)

轉載java中實現定時器的方法,記錄方便檢視。package com.wxltsoft.tool; import org.junit.Test; import java.util.Calendar; import java.util.Date; import java.u

java自定義執行(兩方法

知識點總結 java中的執行緒模型:一個虛擬cpu,多個執行緒 static start方法:啟動執行緒而非執行執行緒(僅僅告訴虛擬機器,該執行緒已經準備完畢,cup決定執行緒執行的時刻) static sleep方法:在制定的毫秒數

【譯】在React實現條件渲染的7方法

原文地址:https://scotch.io/tutorials/7-ways-to-implement-conditional-rendering-in-react-applications 藉助React,我們可以構建動態且高度互動的單頁應用程式,充分利用這種互動性的一種方法是通過條件渲染。 目錄

java筆記--關於執行同步7同步方式)

關於執行緒同步(7種方式) 為何要使用同步?     java允許多執行緒併發控制,當多個執行緒同時操作一個可共享的資源變數時(如資料的增刪改查),      將會導致資料不準確,相互之間產生衝突,因此加入同步鎖以避免在該執行緒沒有完成操作之前,被其他執行緒的呼叫,      從而保證了該變數的唯一性