1. 程式人生 > >java多執行緒之物件的併發訪問

java多執行緒之物件的併發訪問

1.synchronized同步方法

--1.1方法內的變數是執行緒安全的

解釋:由於方法內的變數是私有的,本體訪問的同時別人訪問不了,所以是執行緒安全的。

--1.2例項變數是非執行緒安全的

解釋:由於例項變數可以由多個執行緒訪問,當本體操作變數過程中,別人也可以搶佔資源操作變數,使資料不同步了,所以是非執行緒安全的。(之前博文已經實現過這種情況)

--1.3執行緒與鎖

例:

public class Service {//服務類

	private int age=0;
	public synchronized void get(String username){//同步方法
		try{
			if(username.equals("張三")){
				age=20;
				System.out.println("張三獲取年齡");
				Thread.sleep(2000);
			}else{
				age=30;
				System.out.println("李四獲年齡");
			}
			System.out.println(username+",age="+age);
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

public class MyThread1 extends Thread{//執行緒1

    private Service service;
    
    public MyThread1(Service service) {
        super();
        this.service = service;
    }
    
    @Override
    public void run() {
        service.get("張三");
    }
}

public class MyThread2 extends Thread{//執行緒2

    private Service service;

    public MyThread2(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.get("李四");
    }
}

----1.3.1多個執行緒訪問同一個鎖(前提)

測試類1:

public static void main(String[] args) {

		Service service=new Service();
		MyThread1 myThread1=new MyThread1(service);
		myThread1.start();
		MyThread2 myThread2=new MyThread2(service);
		myThread2.start();
	}

解釋:我們的前提是多個執行緒訪問同一個鎖,如果當前進來一個執行緒獲得同步方法,那麼當前這個執行緒就持有了該方法所屬物件的鎖,其他執行緒就只能處於等待態。

結果:

張三獲取年齡
張三,age=20
李四獲年齡
李四,age=30

----1.3.2多個執行緒訪問多個鎖

public static void main(String[] args) {

		Service service1=new Service();
		Service service2=new Service();
		MyThread1 myThread1=new MyThread1(service1);
		myThread1.start();
		MyThread2 myThread2=new MyThread2(service2);
		myThread2.start();
	}

結果:

張三獲取年齡
李四獲年齡
李四,age=30
張三,age=20

解釋:我們的前提是多個執行緒訪問多個鎖,在當前執行緒獲取其中一個鎖時,對於同步方法內的程式碼是執行緒安全的。結果的順序看出是非同步的,在於不同鎖之間的執行緒是不同步的。

----1.3.3總結:

(1).對於多個執行緒訪問共享資源,才需要使用鎖來進行同步化訪問讀寫。並且對於synchronized修飾的方法為同步方法,是物件級別上的鎖,多個執行緒訪問時,必須"排隊執行"

(2).對於多個執行緒訪問物件,如果執行緒1獲取了該物件的同步方法的鎖,那麼其他物件只能排隊等到執行緒1執行完釋放鎖,才可以再獲取鎖去執行同步方法。如果該物件中有多個同步方法,那麼其他執行緒只能排隊等執行緒1執行完所有的同步方法釋放鎖,其他執行緒才可獲取鎖再執行同步方法。如果該物件中既有同步方法又有非同步的方法,那麼執行緒1執行同步方法的同時,其他執行緒是可以以非同步的方式執行非同步的方法。

--1.4synchronized鎖的重入

解釋:就是當前執行緒已經得到了一個物件鎖之後,再次請求此物件的鎖可以再次得到該物件的鎖。也就是說,synchronized方法體或程式碼塊中再次呼叫類中其他synchronized方法和塊時,同樣也可獲取鎖。

例:

public class Service {

	public synchronized void getAge(String username){
		
			if(username.equals("張三")){
				System.out.println("張三年齡20");
				getSex(username);
			}else{
				System.out.println("李四年齡30");
				getSex(username);
			}
	}
	public synchronized void getSex(String username){
			if(username.equals("張三")){
				System.out.println("張三性別男!");
			}else{
				System.out.println("李四性別女!");
			}
	}
}

public class MyThread1 extends Thread{

    private Service service;
    private String username;
    public MyThread1(Service service ,String username) {
        super();
        this.service = service;
        this.username=username;
    }
    
    @Override
    public void run() {
        service.getAge(username);
    }
}

public class Test {

    public static void main(String[] args) {

        Service service=new Service();
        MyThread1 myThread1=new MyThread1(service,"張三");
        myThread1.start();
    }
} 

結果:

張三年齡20
張三性別男!

----1.4.1總結:

鎖的重入也可用於父子類繼承的環境中。子類完全可以通過鎖的重入帶哦用父類的同步方法。(節省篇幅,例子就不粘了)

--1.5出現異常,鎖自動釋放

解釋:如果當前執行緒執行程式碼時出現了異常,那麼當前執行緒所持有的鎖也將會自動釋放。

--1.6同步不具有繼承性

解釋:同步不可以繼承,雖然父類聲明瞭同步方法,但是子類重寫父類的方法不具有同步的性質。

2.synchronized同步程式碼塊

--2.1synchronized同步程式碼塊

synchronized方法對當前物件進行加鎖,synchronized程式碼塊是對某一個物件進行加鎖。

比較同步方法和同步程式碼塊:

同步方法的特點就是隻要是當前物件的同步方法,當前執行緒沒執行完,其他執行緒只能等著。而同步程式碼塊則是對於不同物件的同步程式碼塊,執行緒1執行同步程式碼塊1時,執行緒2也可以執行同步程式碼塊2,這樣效率會有所提升。而在我看來實際上兩種方式只是細粒度上進一步得到了提升。同步程式碼塊可以進行更加細微的操作。

--2.2半同步半非同步

就是在synchronized程式碼塊中就同步執行,不在synchronized程式碼塊中就非同步執行。

--2.3synchronized程式碼塊的同步特點

synchronized程式碼塊的同步性與synchronized同步方法是一樣的,當一個執行緒訪問物件的synchronized(this)程式碼塊時,說明當前執行緒獲取了物件級別的鎖,那麼其他的同步程式碼塊synchronized(this)訪問將被阻塞,就是不能訪問,得需要當前執行緒釋放物件鎖,才可以訪問。可以看出synchronized(this)程式碼塊監視的是當前物件

例:

public class Service {

	public void getAge(String username){
		synchronized (this) {
			try {
				System.out.println(username);
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(username+"執行完畢!");
		}
	}
}

public class MyThread1 extends Thread{

    private Service service;
    private String username;
    public MyThread1(Service service ,String username){
        this.service=service;
        this.username=username;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.getAge(username);
    }
}

public class MyThread2 extends Thread{

    private Service service;
    private String username;
    public MyThread2(Service service ,String username){
        this.service=service;
        this.username=username;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.getAge(username);
    }
}

public class Test {

    public static void main(String[] args) {

        Service service=new Service();
        MyThread1 myThread1=new MyThread1(service,"張三");
        myThread1.start();
        MyThread2 myThread2=new MyThread2(service,"李四");
        myThread2.start();
    }
}

結果:

張三
張三執行完畢!
李四
李四執行完畢!

----2.3.1總結:

多個執行緒呼叫同一個物件的不同synchronized同步方法或者是synchronized(this)同步程式碼塊時,呼叫都是順序執行,也就是所謂的同步的,阻塞的。

<1>synchronized同步方法:

①.對於其他的synchronized同步方法或synchronized(this)呼叫都是呈阻塞狀態。

.同一個時間只能有一個執行緒獲取鎖物件,去執行synchronized同步方法中程式碼。

<2>synchronized(this)同步程式碼塊:

①.對於其他的synchronized同步方法或synchronized(this)呼叫都是呈阻塞狀態。

.同一個時間只能有一個執行緒獲取鎖物件,去執行synchronized(this)同步程式碼塊中程式碼。

--2.4將任意物件作為物件監視器(synchronized(Object)形式):

在多個執行緒持有"物件監視器"為同一物件的前提下,同一時間只能有一個執行緒執行synchronized(非this物件)同步程式碼塊中的程式碼。

採用這種方式的優點:

如果一個類中有很多synchronized方法,雖然表面上實現了同步,但是由於同步產生的阻塞,效率很低。但是如果使用synchronized(Object)程式碼塊其實與其他synchronized方法是非同步的,不需要與其他鎖this同步方法爭搶this鎖,可以提高效率。

例:

public class Service {

	private String keyObject=new String();
	public void startupThread1(){
		try{
			synchronized (keyObject) {
				System.out.println("thread1 begin!");
				Thread.sleep(3000);
				System.out.println("thread1 end");
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	public synchronized void startupThread2(){
		System.out.println("thread2 begin!");
		System.out.println("thread2 end");
	}
}

public class MyThread1 extends Thread{

    private Service service;
    public MyThread1(Service service){
        this.service=service;

    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.startupThread1();
    }
}

public class MyThread2 extends Thread{

    private Service service;
    public MyThread2(Service service){
        this.service=service;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.startupThread2();
    }
}

public class MyThread2 extends Thread{

    private Service service;
    public MyThread2(Service service){
        this.service=service;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.startupThread2();
    }
}
結果:

thread1 begin!
thread2 begin!
thread2 end
thread1 end

----2.4.1總結

同步程式碼塊放在非同步synchronized方法中進行宣告使用,不能保證呼叫方法的執行緒的執行同步/順序性,也就是執行緒呼叫方法的順序是無序的。

--2.5執行緒同步總結
<1>當多個執行緒同時執行synchronized(Object非this){......... }同步程式碼塊時呈同步效果。

<2>當其他執行緒執行當前物件中synchronized同步方法時呈同步效果。

<3>當其他執行緒執行當前物件方法裡面的synchronized(this)程式碼塊時也呈現同步效果。

--2.6靜態同步synchronized方法和synchronized(Class)程式碼塊(類鎖)

這兩種給類上鎖效果都是一樣的。但是與非靜態方法卻有所不同。synchronized關鍵字修飾靜態方法是給Class類上鎖,而synchronized修飾非靜態方法是給物件上鎖。

3.volatile關鍵字使用

volatile作用使變數在多個執行緒間可見。

--3.1死迴圈例子

public class MyThread1 extends Thread{

	private boolean isRunning=true;
	
	public void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}

	@Override
	public void run() {
		System.out.println("進入run了");
		while(isRunning ==true){
			
		}
		System.out.println("執行緒被停止了!");
	}
}

public class Test {
    public static void main(String[] args) {
        try{
            MyThread1 myThread1=new MyThread1();
            myThread1.start();
            Thread.sleep(1000);
            myThread1.setRunning(false);
            System.out.println("已經賦值為false");
        }catch(Exception e){
            e.printStackTrace();
        }
    }
} 
結果:


解釋:雖然執行緒一直在私有堆疊中取得isRunning的值為true並且也執行了setRunning(false);但是卻把公共堆疊的isRunning變數值更新為false,所以還是一直死迴圈。主要由於私有堆疊和公共堆疊裡面的值不同步引起的。而volatile關鍵字修飾的變數,作用就是強制性地在公共堆疊中取值。也就解決上面的死迴圈。

private volatile boolean isRunning=true;

結果:


--3.2synchronized和volatile的區別

<1>volatile關鍵字是執行緒輕量級實現,volatile只能修飾於變數。

<2>多執行緒訪問volatile不會阻塞(因為volatile不保證原子性操作),而synchronized會出現阻塞。

<3>volatile不保證資料的原子性操作,可以保證資料的可見性(可以強制從公共堆疊取值)。而synchronized可以保證原子性(前面說同步了),也可以間接的保證可見性,就讓公共堆疊和私有堆疊的資料同步。

----3.2.1總結:

我們使用volatile關鍵字來解決變數在多個執行緒之間 "可見",而synchronized關鍵字來保證多個執行緒之間 "同步"

--3.3synchronized關鍵字實現間接可見性

死迴圈例子:

public class Service {
	private boolean isContinueRun=true;
	public void runMethod(){
		while(isContinueRun){
			
		}
		System.out.println("已經停下來了!");
	}
	public void stopMethod(){
		isContinueRun=false;
	}
}

public class MyThread1 extends Thread{

    private Service service;
    public MyThread1(Service service){
        this.service=service;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.runMethod();
    }
}

public class MyThread2 extends Thread{

    private Service service;
    public MyThread2(Service service){
        this.service=service;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        super.run();
        service.stopMethod();
    }
}

public class Test {
    public static void main(String[] args) {
        try{
            Service service=new Service();
            MyThread1 myThread1=new MyThread1(service);
            myThread1.start();
            Thread.sleep(1000);
            MyThread2 myThread2=new MyThread2(service);
            myThread2.start();
            System.out.println("已經發起停止命令了!");
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

結果:


解釋:這種死迴圈,雖然MyThread2的呼叫了stopMethod();方法停止執行緒,但是根本沒起作用。原因是MyThread1執行緒進入死迴圈中,一直在迴圈體未出來,MyThread1沒看見MyThread2送過來的false,就是差在各個執行緒之間沒有可見性。

為了保持執行緒之間的可見性,使用synchronized關鍵字,讓兩個執行緒都持有鑰匙進入同步程式碼塊。這樣就可以保證兩個執行緒之間可見了。

重新編寫Service類:

public class Service {
	private boolean isContinueRun=true;
	public void runMethod(){
		while(isContinueRun){
			String keyString =new String();
			synchronized (keyString) {
				
			}
		}
		System.out.println("已經停下來了!");
	}
	public void stopMethod(){
		isContinueRun=false;
	}
}
解釋:這樣就實現了間接可見性。

-----------------------------------------------

4.總結:

關於執行緒的安全主要就是搞同步和可見兩個方面。

相關推薦

java執行物件併發訪問

1.synchronized同步方法 --1.1方法內的變數是執行緒安全的 解釋:由於方法內的變數是私有的,本體訪問的同時別人訪問不了,所以是執行緒安全的。 --1.2例項變數是非執行緒安全的 解釋:

Java執行物件及變數的併發訪問

Java物件及變數的併發訪問 當多個執行緒同時對同一個物件中的例項變數進行併發訪問時可能會產生執行緒安全問題。產生的後果就是”髒讀”,即收到的資料其實是被更改過的。 如果訪問的是方法中的變數,則不存在”非執行緒安全”問題 可以通過以下幾種方式來解決,在對物

Java執行執行訪問共享物件和資料的方式

1.如果每個執行緒執行的程式碼相同,可以使用同一個Runable物件,這個Runable物件中有那個共享資料,例如賣票系統就可以這樣做。 package javaplay.test; public class MulteThreadShareData { publi

java執行併發集合(CopyOnWriteArrayList)

CopyOnWriteArrayList:CopyOnWriteArrayList這是一個ArrayList的執行緒安全的變體,其原理大概可以通俗的理解為:初始化的時候只有一個容器,很常一段時間,這個容器資料、數量等沒有發生變化的時候,大家(多個執行緒),都是讀取(假設這段時

java執行併發集合(BlockingQueue)

簡介 實現 package com.np.ota.test.queue; import java.util.concurrent.BlockingQueue; import java.ut

Java執行併發安全經典案例-賣票

執行緒相關知識 1.建立執行緒的兩種方式 繼承Thread類。 實現Runnable介面。(這種方式較為常用) 2.實現Runnable介面的好處 將執行緒的任務從執行緒的子類中分

Java執行併發工具類

一、總論:在JDK中提供了幾種併發工具類 1)CountDownLatch(同步倒數計數器:等待多執行緒(或者多步驟)完成) 2)CyclicBarrier(迴圈屏障:同步屏障) 3)Semaphore(訊號量:控制併發程序數) 主要參考資料

JAVA執行Synchronized關鍵字--物件鎖的特點

一,介紹 本文介紹JAVA多執行緒中的synchronized關鍵字作為物件鎖的一些知識點。 所謂物件鎖,就是就是synchronized 給某個物件 加鎖。關於 物件鎖 可參考:這篇文章 二,分析 synchronized可以修飾例項方法,如下形式: 1 public class My

Java執行併發包,併發佇列

目錄 1 併發包 2 併發佇列 1 併發包 1.1同步容器類 1.1.1Vector與ArrayList區別 1.1.1.1 ArrayList是最常用的List實現類,內部是通過陣列實現的,它允許對

[C#學習筆記執行2]執行同步與併發訪問共享資源工具—Lock、Monitor、Mutex、Semaphore

“執行緒同步”的含義         當一個程序啟動了多個執行緒時,如果需要控制這些執行緒的推進順序(比如A執行緒必須等待B和C執行緒執行完畢之後才能繼續執行),則稱這些執行緒需要進行“執行緒同步(thread synchronization)”。         執行緒

Java執行執行併發庫條件阻塞Condition的應用

鎖(Lock/synchronized)只能實現互斥不能實現通訊,Condition的功能類似於在傳統的執行緒技術中的,Object.wait()和Object.notify()的功能,在等待Condition時,允許發生"虛假喚醒",這通常作為對基礎平臺語義的讓步,對於大多

Java執行join()方法

概要 本章,會對Thread中join()方法進行介紹。涉及到的內容包括: 1. join()介紹 2. join()原始碼分析(基於JDK1.7.0_40) 3. join()示例 來源:http://www.cnblogs.com/skywang12345/p/34792

白話理解java執行join()方法

join字面意思是加入,我理解為插隊. 舉例:媽媽在炒菜,發現沒喲醬油了,讓兒子去打醬油,兒子打完醬油,媽媽炒完菜,全家一起吃 package cn.yh.thread01; /** * * 打醬油的例子 */ public class Demo03 { public stat

細說Java 執行記憶體可見性

前言: 討論學習Java中的記憶體可見性、Java記憶體模型、指令重排序、as-if-serial語義等多執行緒中偏向底層的一些知識,以及synchronized和volatile實現記憶體可見性的原理和方法。 1、可見性介紹 可見性:一個執行緒對共用變數值的修改,能夠及時地被其他執行緒

JAVA執行(四) Executor併發框架向RabbitMQ推送訊息

github程式碼地址:https://github.com/showkawa/springBoot_2017/tree/master/spb-demo  假設一個需求使用者點選某個頁面,我們後臺需要向MQ推送信資訊 1,模擬的MQ服務,我這邊使用RabbitMQ (關於MQ 傳送和監聽訊息可以

java執行 執行協作

也是網上看的一道題目:關於假如有Thread1、Thread2、Thread3、Thread4四條執行緒分別統計C、D、E、F四個盤的大小,所有執行緒都統計完畢交給Thread5執行緒去做彙總,應當如何實現? 蒐集整理了網上朋友提供的方法,主要有: 1. 多執行緒都是Thread或

java執行鎖機制二

網上看到一個題目,題目是這樣:Java多執行緒,啟動四個執行緒,兩個執行加一,另外兩個執行減一。 針對該問題寫了一個程式,測試通過,如下: class Sync { static int count = 0; public void add() {

java執行鎖機制一

網上看了一篇關於java synchronized關鍵字使用的很好的文章,現將其簡要總結一下,加深理解。 先總結兩個規則: synchronized鎖住的是括號裡的物件,而不是程式碼。對於非static的synchronized方法,鎖的就是物件本身也就是this。 多個執行緒

java執行Phaser

java多執行緒技術提供了Phaser工具類,Phaser表示“階段器”,用來解決控制多個執行緒分階段共同完成任務的情景問題。其作用相比CountDownLatch和CyclicBarrier更加靈活,例如有這樣的一個題目:5個學生一起參加考試,一共有三道題,要求所有學生到齊才能開始考試,全部同學都

Java執行——ThreadLocal

ThreadLocal是什麼:每一個ThreadLocal能夠放一個執行緒級別的變數,也就是說,每一個執行緒有獨自的變數,互不干擾。以此達到執行緒安全的目的,並且一定會安全。 實現原理: 要了解實現原理,我們先看set方法 public void set(T value) { T