欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JAVA多線程線程安全性基礎(chǔ)

 更新時(shí)間:2021年08月12日 15:10:03   作者:聞人此生  
這篇文章主要介紹了如何測試Java類的線程安全性,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

線程安全性

一個(gè)對象是否需要是線程安全的,取決于它是否被多個(gè)線程訪問,而不取決于對象要實(shí)現(xiàn)的功能

什么是線程安全的代碼

核心:對 共享的 和 可變的 狀態(tài)的訪問進(jìn)行管理。防止對數(shù)據(jù)發(fā)生不受控的并發(fā)訪問。

何為對象的狀態(tài)?

狀態(tài)是指存儲在對象的狀態(tài)變量(例如實(shí)例或靜態(tài)域)中的數(shù)據(jù)。還可能包括 其他依賴對象 的域。

eg:某個(gè)HashMap的狀態(tài)不僅存儲在HashMap對象本身,還存儲在許多Map.Entry對象中。

在這里插入圖片描述

總而言之,在對象的狀態(tài)中包含了任何可能影響其外部可見行為的數(shù)據(jù)。

何為共享的?

共享的 是指變量可同時(shí)被多個(gè)線程訪問

何為可變的?

可變的 是指變量的值在其生命周期內(nèi)可以發(fā)生變化。試想,如果一個(gè)共享變量的值在其生命周期內(nèi)不會發(fā)生變化,那么在多個(gè)

線程訪問它的時(shí)候,就不會出現(xiàn)數(shù)據(jù)不一致的現(xiàn)象,自然就不存在線程安全性問題了。

什么是線程安全性

當(dāng)多個(gè)線程訪問某個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)度方式或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個(gè)類都能表現(xiàn)出正確的行為,達(dá)到預(yù)期的效果,那么就稱這個(gè)類是線程安全的。

如下啟動10個(gè)線程,每個(gè)線程對inc執(zhí)行1000次遞增,并添加一個(gè)計(jì)時(shí)線程,預(yù)期效果應(yīng)為10000,而實(shí)際輸出值為6880,是一個(gè)小于10000的值,并未達(dá)到預(yù)期效果,因此INS類不是線程安全的,整個(gè)程序也不是線程安全的。原因是遞增操作不是原子操作,并且沒有適當(dāng)?shù)耐綑C(jī)制

package hgh0808;
public class Test {
    public static void main(String[] args){
        for(int i = 0;i < 10;i++){
            Thread th = new Thread(new CThread());
            th.start();
        }
        TimeThread tt = new TimeThread();
        tt.start();
        try{
            Thread.sleep(21000);
        }catch(Exception e){
            e.printStackTrace();
        }
        System.out.println(INS.inc);
    }
}
---------------------------------------------------------------------
package hgh0808;
import java.util.concurrent.atomic.*;
public class TimeThread extends Thread{
    @Override
    public void run(){
        int count = 1;
        for(int i = 0;i < 20;i++){
            try{
                Thread.sleep(1000);
            }catch(Exception e){
                e.printStackTrace();
            }
            System.out.println(count++);
        }
    }
}
---------------------------------------------------------------------
package hgh0808;
public class CThread implements Runnable{
    @Override
    public void run(){
        for(int j = 0;j < 1000;j++){
            INS.increase();
        }
    }
}
---------------------------------------------------------------------
package hgh0808;
public class INS{
    public static volatile int inc = 0;
    public static void increase(){
            inc++;
    }
}
=====================================================================

執(zhí)行結(jié)果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
6880

通過synchronized加鎖機(jī)制,對INS類實(shí)現(xiàn)同步,如下得到了正確的運(yùn)行結(jié)果,很容易可以看出,主調(diào)代碼中并沒有任何額外的同步或協(xié)同,此時(shí)的INS類是線程安全的,整個(gè)程序也是線程安全的

package hgh0808;
public class INS{
    public static volatile int inc = 0;
    public static void increase(){
        synchronized (INS.class){
            inc++;
        }
    }
}

執(zhí)行結(jié)果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
10000

如何編寫線程安全的代碼
------------------------------------------------------------------------------------------------
如果當(dāng)多個(gè)線程訪問同一個(gè)可變的狀態(tài)變量時(shí)沒有使用合適的同步,那么程序就會出現(xiàn)錯(cuò)誤,像上文中進(jìn)行同步之前的代碼
有三種方式可以修復(fù)這個(gè)問題:
*不在線程之間共享該狀態(tài)變量
*將狀態(tài)變量修改為不可變的變量
*在訪問狀態(tài)變量時(shí)使用同步
前兩種方法是針對 共享 和 不變 這兩個(gè)屬性(見上文)解決問題,在有些情境下會違背程序設(shè)計(jì)的初衷(比如上文中INS類中的inc變量不可能不變,且在多核處理器的環(huán)境下為了提高程序性能,就需要多個(gè)線程同時(shí)處理,這樣變量就必然要被多個(gè)線程共享)。
基于此,我們針對第三種方式------ 在訪問狀態(tài)變量時(shí)使用同步 展開討論
在討論第三種方式之前,我們先介紹幾個(gè)簡單的概念
原子性 :一個(gè)操作序列的所有操作要么不間斷地全部被執(zhí)行,要么一個(gè)也沒有執(zhí)行
競態(tài)條件 :當(dāng)某個(gè)計(jì)算的正確性取決于多個(gè)線程的的交替執(zhí)行時(shí)序時(shí),就會發(fā)生競態(tài)條件。通俗的說,就是某個(gè)程序結(jié)果的正確性取決于運(yùn)氣時(shí),就會發(fā)生競態(tài)條件。(競態(tài)條件并不總是會產(chǎn)生錯(cuò)誤,還需要某種不恰當(dāng)?shù)膱?zhí)行時(shí)序)
常見的競態(tài)條件類型:
*檢查–執(zhí)行(例如延遲初始化)
*讀取–修改–寫入(例如自增++操作)
針對以上兩種常見的競態(tài)條件類型,我們分別給出例子

延遲初始化(檢查--執(zhí)行)
--------------------------------------------------------------------
package hgh0808;
import java.util.ArrayList;
public class Test1 {
    public ArrayList<Ball> list;
    public ArrayList<Ball> getInstance(){
        if(list == null){
            list = new ArrayList<Ball>();
        }
        return list;
    }
}
class Ball{
}

大概邏輯是先判斷l(xiāng)ist是否為空,若為空,創(chuàng)建一個(gè)新的ArrayList對象,若不為空,則直接使用已存在的ArrayList對象,這樣可以保證在整個(gè)項(xiàng)目中l(wèi)ist始終指向同一個(gè)對象。這在單線程環(huán)境中是完全沒有問題的,但是如果在多線程環(huán)境中,list還未實(shí)例化時(shí),A線程和B線程同時(shí)執(zhí)行if語句,A和B線程都會認(rèn)為list為null,A和B線程都會執(zhí)行實(shí)例化語句,造成混亂。

自增++操作(讀取--修改--寫入)
------------------------------------------------------------------------
參考上文中為改進(jìn)之前的代碼(對INS類中inc的自增)

以上兩個(gè)例子告訴我們,必須添加適當(dāng)?shù)耐讲呗?,保證復(fù)合操作的原子性,防止競態(tài)條件的出現(xiàn)

策略一:使用原子變量類,在java.util.concurrent.atomic包中包含了一些原子變量類

在這里插入圖片描述

package hgh0808;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public class INS{
    public static AtomicInteger inc = new AtomicInteger(0);
    public static void increase(){
        inc.incrementAndGet();
    }
}

執(zhí)行結(jié)果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
10000

值得注意的是,只有一個(gè)狀態(tài)變量時(shí),可以通過原子變量類實(shí)現(xiàn)線程安全。但是如果有多個(gè)狀態(tài)變量呢?

設(shè)想一個(gè)情景

多個(gè)線程不斷產(chǎn)生1到10000的隨機(jī)數(shù)并且發(fā)送到一個(gè)計(jì)算線程,計(jì)算線程每獲取一個(gè)數(shù)字n,就計(jì)算sinx在[0,n]上的積分并打印到控制臺上,為了提高程序性能,設(shè)計(jì)一個(gè)緩存機(jī)制,保存上次的數(shù)字n和積分結(jié)果(兩個(gè)狀態(tài)變量)。如果本次的數(shù)字和上次的數(shù)字相等,直接打印積分結(jié)果,避免重復(fù)計(jì)算。

看代碼:

package hgh0808;
import java.util.concurrent.atomic.AtomicReference;

public class Calculate extends Thread{
    private final AtomicReference<Double> lastNumber  = new AtomicReference<Double>();  //緩存機(jī)制,原子變量類
    private final AtomicReference<Double> lastRes = new AtomicReference<Double>();      //緩存機(jī)制,原子變量類
    private static final double N = 100000;    //將區(qū)間[0,e]分成100000份,方便定積分運(yùn)算
    public void service() throws Exception{
        getData();
        Thread.sleep(10000);   //等待MyQueue隊(duì)列中有一定數(shù)量的元素后,再開始從其中取元素
        while(true){
            Double e;
                if(!MyQueue.myIsEmpty()){
                     e = MyQueue.myRemove();
                }else{
                    return;
                }
            if(e.equals(lastNumber.get())){
                System.out.println(lastNumber.get()+" "+lastRes.get());
            }else{
                Double temp = integral(e);
                lastNumber.set(e);
                lastRes.set(temp);
                System.out.println(e+" "+temp);
            }
            Thread.sleep(2000);
        }
    }
    public void getData(){   //創(chuàng)建并啟動四個(gè)獲取隨機(jī)數(shù)的線程,這四個(gè)線程交替向MyQueue隊(duì)列中添加元素
        Thread1 th1 = new Thread1();
        Thread2 th2 = new Thread2();
        Thread3 th3 = new Thread3();
        Thread4 th4 = new Thread4();
        th1.start();
        th2.start();
        th3.start();
        th4.start();
    }
    public Double integral(double e){    //計(jì)算定積分
        double step = (e-0)/N;
        double left = 0,right = step;
        double sum = 0;
        while(right <= e){
            double mid = left+(right-left)/2;
            sum+=Math.sin(mid);
            left+=step;
            right+=step;
        }
        sum*=step;
        return sum;
    }
}
---------------------------------------------------------------------
package hgh0808;
import java.util.LinkedList;
public class MyQueue {      //由于LinkedList是線程不安全的,因此需要將其改寫為線程安全類
    private static LinkedList<Double> queue = new LinkedList<>();
    synchronized public static void myAdd(Double e){
        queue.addLast(e);
    }
    synchronized public static void myClear(){
        queue.clear();
    }
    synchronized public static int mySize(){
        return queue.size();
    }
    synchronized public static boolean myIsEmpty(){
        return queue.isEmpty();
    }
    synchronized public static double myRemove(){
        return queue.removeFirst();
    }
}
-----------------------------------------------------------------------
package hgh0808;
import java.util.Random;
public class Thread1 extends Thread{
    private double data;
    @Override
    public void run(){
        while(true){
            Random random = new Random();
            data = (double) (random.nextInt(10000)+1);
            if(MyQueue.mySize() > 10000){     //由于從隊(duì)列中取元素的速度低于四個(gè)線程向隊(duì)列中加元素的速度,因此隊(duì)列的長度是趨于擴(kuò)張的,當(dāng)達(dá)到一定程度時(shí),清空隊(duì)列
                MyQueue.myClear();
            }
            MyQueue.myAdd(data);
            try {
                Thread.sleep(1000);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}
------------------------------------------------------------------------
package hgh0808;
import java.util.Random;
public class Thread2 extends Thread{
    private double data;
    @Override
    public void run(){
        while(true){
            Random random = new Random();
            data = (double) (random.nextInt(10000)+1);
            if(MyQueue.mySize() > 10000){
                MyQueue.myClear();
            }
            MyQueue.myAdd(data);
            try {
                Thread.sleep(1000);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}
-----------------------------------------------------------------------
package hgh0808;
import java.util.Random;
public class Thread3 extends Thread{
    private double data;
    @Override
    public void run(){
        while(true){
            Random random = new Random();
            data = (double) (random.nextInt(10000)+1);
            if(MyQueue.mySize() > 10000){
                MyQueue.myClear();
            }
            MyQueue.myAdd(data);
            try {
                Thread.sleep(1000);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}
------------------------------------------------------------------------
package hgh0808;
import java.util.Random;
public class Thread4 extends Thread{
    private double data;
    @Override
    public void run(){
        while(true){
            Random random = new Random();
            data = (double) (random.nextInt(10000)+1);
            if(MyQueue.mySize() > 10000){
                MyQueue.myClear();
            }
            MyQueue.myAdd(data);
            try {
                Thread.sleep(1000);
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

只看Calculate線程,不看其他線程和MyQueue中的鎖機(jī)制,本問題的焦點(diǎn)在于Calculate線程中對多個(gè)狀態(tài)變量的同步策略

存在問題:

盡管對lastNumber和lastRes的set方法的每次調(diào)用都是原子的,但仍然無法同時(shí)更新lastNumber和lastRes;如果只修改了其中一個(gè)變量,那么在這兩次修改操作之間,其它線程將發(fā)現(xiàn)不變性條件被破壞了。換句話說,就是沒有足夠的原子性

**當(dāng)在不變性條件中涉及多個(gè)變量時(shí),各個(gè)變量間并不是彼此獨(dú)立的,而是某個(gè)變量的值會對其它變量的值產(chǎn)生約束。因此當(dāng)更新某一個(gè)變量時(shí),需要在同一個(gè)原子操作中對其他變量同時(shí)進(jìn)行更新。

改進(jìn) ================>加鎖機(jī)制 內(nèi)置鎖 synchronized

之所以每個(gè)對象都有一個(gè)內(nèi)置鎖,只是為了免去顯式地創(chuàng)建鎖對象

synchronized修飾方法就是橫跨整個(gè)方法體的同步代碼塊

非靜態(tài)方法的鎖-----方法調(diào)用所在的對象

靜態(tài)方法的鎖-----方法所在類的class對象

public class Calculate extends Thread{
    private final AtomicReference<Double> lastNumber  = new AtomicReference<Double>();  //緩存機(jī)制,原子變量類
    private final AtomicReference<Double> lastRes = new AtomicReference<Double>();      //緩存機(jī)制,原子變量類
    private static final double N = 100000;    //將區(qū)間[0,e]分成100000份,方便定積分運(yùn)算
    public void service() throws Exception{
        getData();
        Thread.sleep(10000);   //等待MyQueue隊(duì)列中有一定數(shù)量的元素后,再開始從其中取元素
        while(true){
            Double e;
            synchronized (this){    //檢查--執(zhí)行 使用synchronized同步,防止出現(xiàn)競態(tài)條件
                if(!MyQueue.myIsEmpty()){
                     e = MyQueue.myRemove();
                }else{
                    return;
                }
            }
            if(e.equals(lastNumber.get())){
                System.out.println(lastNumber.get()+" "+lastRes.get());
            }else{
                Double temp = integral(e);
                synchronized (this) {     //兩個(gè)狀態(tài)變量在同一個(gè)原子操作中更新
                    lastNumber.set(e);
                    lastRes.set(temp);
                }
                System.out.println(e+" "+temp);
            }
            Thread.sleep(2000);
        }
    }
    public void getData(){   //創(chuàng)建并啟動四個(gè)獲取隨機(jī)數(shù)的線程,這四個(gè)線程交替向MyQueue隊(duì)列中添加元素
        Thread1 th1 = new Thread1();
        Thread2 th2 = new Thread2();
        Thread3 th3 = new Thread3();
        Thread4 th4 = new Thread4();
        th1.start();
        th2.start();
        th3.start();
        th4.start();
    }
    public Double integral(double e){    //計(jì)算定積分
        double step = (e-0)/N;
        double left = 0,right = step;
        double sum = 0;
        while(right <= e){
            double mid = left+(right-left)/2;
            sum+=Math.sin(mid);
            left+=step;
            right+=step;
        }
        sum*=step;
        return sum;
    }
}

對于包含多個(gè)變量的不變性條件中,其中涉及的所有變量都需要由同一個(gè)鎖來保護(hù)

synchronized (this) {     //兩個(gè)狀態(tài)變量在同一個(gè)原子操作中更新
                    lastNumber.set(e);
                    lastRes.set(temp);
                }

鎖的重入

如果某個(gè)線程試圖獲得一個(gè)已經(jīng)由它自己持有的鎖,那么這個(gè)請求就會成功,“重入”意味著獲取鎖的操作的粒度是‘線程',而不是‘調(diào)用'。

重入的一種實(shí)現(xiàn)方式 :

為每個(gè)鎖關(guān)聯(lián)一個(gè)獲取計(jì)數(shù)值和一個(gè)所有者線程。當(dāng)計(jì)數(shù)值為0時(shí),這個(gè)鎖就被認(rèn)為是沒有被任何線程持有。當(dāng)線程請求一個(gè)未被持有的鎖時(shí),JVM將記下鎖的持有者,并且將獲取計(jì)數(shù)值置為1。如果同一個(gè)線程再次獲取這個(gè)鎖,計(jì)數(shù)值將遞增,當(dāng)線程退出同步代碼塊時(shí),計(jì)數(shù)器會相應(yīng)地遞減。當(dāng)計(jì)數(shù)值為0時(shí),這個(gè)鎖將被釋放。

如果內(nèi)置鎖不可重入,那么以下這段代碼將發(fā)生死鎖(每個(gè)doSomething方法在執(zhí)行前都會獲取Father上的內(nèi)置鎖)
----------------------------------------------------------------------
public class Father{
  public synchronized void doSomething(){
  }
}

public class Son extends Father{
   @Override
   public synchronized void doSomething(){
       System.out.println("重寫");
       super.doSomething();
   }
}

線程安全性與性能和活躍性之間的平衡

活躍性:是否會發(fā)生死鎖饑餓等現(xiàn)象
性能:線程的并發(fā)度
不良并發(fā)的應(yīng)用程序:可同時(shí)調(diào)用的線程數(shù)量,不僅受到可用處理資源的限制,還受到應(yīng)用程序本身結(jié)構(gòu)的限制。幸運(yùn)的是,通過縮小同步代碼塊的作用范圍,可以平衡這個(gè)問題。
縮小作用范圍的原則====>當(dāng)執(zhí)行時(shí)間較長的計(jì)算或者可能無法快速完成的操作時(shí),一定不能持有鎖?。。?/p>

總結(jié)

本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • IDEA調(diào)試技巧條件斷點(diǎn)實(shí)現(xiàn)步驟詳解

    IDEA調(diào)試技巧條件斷點(diǎn)實(shí)現(xiàn)步驟詳解

    這篇文章主要介紹了IDEA調(diào)試技巧條件斷點(diǎn)實(shí)現(xiàn)步驟詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • springboot使用jasypt對配置文件加密加密數(shù)據(jù)庫連接的操作代碼

    springboot使用jasypt對配置文件加密加密數(shù)據(jù)庫連接的操作代碼

    這篇文章主要介紹了springboot使用jasypt對配置文件加密加密數(shù)據(jù)庫連接的操作代碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-01-01
  • 使用Autowired為什么會被IDEA警告最佳修改方法

    使用Autowired為什么會被IDEA警告最佳修改方法

    這篇文章主要介紹了使用Autowired為什么會被IDEA警告,應(yīng)該怎么修改最佳,除了使用@Autowired以外,我們其實(shí)也有幾種好用的方式,使用@Resource替代@Autiwired方法是其中一種,只需要改變一個(gè)注解,這里就不展示了,需要的朋友可以參考下
    2023-02-02
  • Mybatis Generator自動生成對應(yīng)文件的實(shí)現(xiàn)方法

    Mybatis Generator自動生成對應(yīng)文件的實(shí)現(xiàn)方法

    這篇文章主要介紹了Mybatis Generator自動生成對應(yīng)的文件的實(shí)現(xiàn)方法,需要的朋友可以參考下
    2017-09-09
  • java?緩沖流的概念使用方法以及實(shí)例詳解

    java?緩沖流的概念使用方法以及實(shí)例詳解

    這篇文章主要為大家介紹了java?緩沖流的概念使用方法以及實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • java使用websocket,并且獲取HttpSession 源碼分析(推薦)

    java使用websocket,并且獲取HttpSession 源碼分析(推薦)

    這篇文章主要介紹了java使用websocket,并且獲取HttpSession,通過使用配置源碼分析了各方面知識點(diǎn),具體操作步驟大家可查看下文的詳細(xì)講解,感興趣的小伙伴們可以參考一下。
    2017-08-08
  • 聊聊springboot?整合?hbase的問題

    聊聊springboot?整合?hbase的問題

    這篇文章主要介紹了springboot?整合?hbase的問題,文中給大家提到配置linux服務(wù)器hosts及配置window?hosts的相關(guān)知識,需要的朋友可以參考下
    2021-11-11
  • Java修飾符abstract與static及final的精華總結(jié)

    Java修飾符abstract與static及final的精華總結(jié)

    abstract、static、final三個(gè)修飾符是經(jīng)常會使用的,對他們的概念必須非常清楚,弄混了會產(chǎn)生些完全可以避免的錯(cuò)誤,比如final和abstract不能一同出現(xiàn),static和abstract不能一同出現(xiàn),下面我們來詳細(xì)了解
    2022-04-04
  • intellij IDEA配置springboot的圖文教程

    intellij IDEA配置springboot的圖文教程

    Spring Boot是由Pivotal團(tuán)隊(duì)提供的全新框架,其設(shè)計(jì)目的是用來簡化新Spring應(yīng)用的初始搭建以及開發(fā)過程。接下來通過本文給大家介紹intellij IDEA配置springboot的圖文教程,感興趣的朋友一起看看吧
    2018-03-03
  • Java文件分級目錄打包下載zip的實(shí)例代碼

    Java文件分級目錄打包下載zip的實(shí)例代碼

    這篇文章主要介紹了Java文件分級目錄打包下載zip的實(shí)例代碼,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08

最新評論