JAVA多線程-什么是線程安全

什么是線程

線程是比進(jìn)程更小的運(yùn)行單位,它被包含在進(jìn)程之中,是進(jìn)程實(shí)際的運(yùn)行單位,一個(gè)線程是指進(jìn)程單一的控制流,一個(gè)進(jìn)程可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù)。(來源百度)

  • 并發(fā):同一時(shí)間段內(nèi),多個(gè)任務(wù)都在執(zhí)行(單位時(shí)間內(nèi)不一定同時(shí)執(zhí)行)
    -并行:單位時(shí)間內(nèi),多個(gè)任務(wù)同時(shí)執(zhí)行
Java中如何創(chuàng)建線程

Java中創(chuàng)建線程的方式有三種

  • 通過implement Runable 接口
  • 通過extents Thread 類
  • 通過Callable和Future創(chuàng)建線程
    1.implement Runnable方式
public class threadSafe{
public void static main(String[] args){
 Thread thread=new Thread(new Runnable() { //第一種方式 直接new一個(gè)Runnable 實(shí)例
              @Override
              public void run() {
                  System.out.println("this is a thread");
              }
          });
          thread.start();
      mythread mythread=new mythread();
     Thread   thread2=new Thread(mythread);//通過創(chuàng)建mythread實(shí)例創(chuàng)建線程
}
}
class mythread implements Runnable{
  @Override
    public void run() {
        for(int i=0;i<10;i++){
            a++;
            System.out.println("running thread"+" a:"+a);
           }
    }
}

后兩者有興趣的同學(xué)可去菜鳥教程上學(xué)習(xí),在這就不寫了.
https://www.runoob.com/java/java-multithreading.html

什么是線程安全

一個(gè)類是線程安全的,是指被多個(gè)線程訪問時(shí),類可以持續(xù)進(jìn)行正確的行為.
當(dāng)多個(gè)線程訪問一個(gè)對(duì)象時(shí),如果我們不考慮線程在運(yùn)行環(huán)境下的交替執(zhí)行和調(diào)度,并且不需要額外的同步及在調(diào)用方代碼不必做其它的協(xié)調(diào),那么我們稱這個(gè)類是線程安全的(來自java并發(fā)編程實(shí)戰(zhàn))
多線程中,程序的執(zhí)行順序我們是不知道的,比如什么時(shí)候執(zhí)行這段代碼


threadSafe.PNG

讓我們看看實(shí)際代碼中的輸出

生產(chǎn)者

class  product implements Runnable{
    private int DEAFULT_CUSOTME_NUM=5;
    private Thread thread;
    private String ThreadName;
    private goods goods;
    /**
     * none constructor
     */
    product(){}

    /**
     *
     * @param ThreadName
     * Get threadName for this thread
     */
    product(String ThreadName,goods goods){
        this.ThreadName=ThreadName;
        this.goods=goods;
    }
    public void start(){
        if(thread==null){
            thread =new Thread(this,ThreadName);
            thread.start();
        }
    }
    @Override
    public void run() {
        SimpleDateFormat formatter=new SimpleDateFormat("yyyy/MM/dd hh:mm:ss:SSS");
            for(int i=0;i<5;i++){
                    System.out.println("thread name :" + thread.getName() + "consume goods:"
                            + DEAFULT_CUSOTME_NUM + "remain goods:" +
                            goods.consumeGoods(DEAFULT_CUSOTME_NUM) + "   current time"
                            + formatter.format(new Date()) + "\n i:" + i);
                    ; //default product five goods;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

    }
}

消費(fèi)者

class  custome implements Runnable{
    private int DEAFULT_CUSOTME_NUM=5;  //default good num
    private Thread thread;
    private String ThreadName;
    private goods goods;

    /**
     * none constructor
     */
    custome(){}

    /**
     *
     * @param ThreadName
     * Get threadName for this thread
     */
    custome(String ThreadName,goods goods){
        this.ThreadName=ThreadName;
        this.goods=goods;
    }
    public void start(){
        if(thread==null){
            thread =new Thread(this,ThreadName);
        thread.start();
        }
        }
@Override
public void run() {
        SimpleDateFormat formatter=new SimpleDateFormat("yyyy/MM/dd hh:mm:ss:SSS");
        for(int i=0;i<5;i++){
        System.out.println("thread name :"+thread.getName()
        +"  product goods:"+DEAFULT_CUSOTME_NUM+"remain goods:"+
        goods.addGoods(DEAFULT_CUSOTME_NUM)+" current time"
        +formatter.format(new Date())+"\n i:"+i);  //default product five goods
 
        }
        }

main方法

public class threadSafe {
    static int DEFAULT_GOODS_NUM=0;
    public static void main(String[] args) {
         goods goods=new goods(DEFAULT_GOODS_NUM);
         custome custome2=new custome("product ",goods);
         product product1=new product("consume ",goods);
        custome custome1=new custome("product 1",goods);
        product product2=new product("consume 2",goods);
         custome2.start();
         product1.start();
         custome1.start();
         product2.start();
     }
}

我們來觀察一下輸出


threadSafeprint.PNG

由于我加了threadSleep,所以會(huì)按從0-5的順序執(zhí)行
雖然i:0的時(shí)候,輸出順序是0->10->5->5,結(jié)果是沒有錯(cuò)誤的,因?yàn)槲覀儫o法控制線程什么時(shí)候開始輸出,比如執(zhí)行操作的時(shí)候,線程一起執(zhí)行,實(shí)際上讀入變量順序是這樣的,produc->consumer 2-> consume->product 1
但是執(zhí)行 i:1的時(shí)候,出現(xiàn)了問題,product 和product 1同時(shí)輸出了5。說明這兩個(gè)線程發(fā)生了像上面一樣的圖的情況,在product和product 1 執(zhí)行前,先執(zhí)行了consume方法,此時(shí)變量為0,當(dāng)product和product1執(zhí)行時(shí),同時(shí)讀入變量值0,執(zhí)行addgood方法,得到了一樣的結(jié)果。
這就出現(xiàn)了線程不安全的情況

如果將它變?yōu)榫€程安全

1.將goodnum設(shè)置為volatile.
為什么設(shè)置volatile有效呢,volatile的作用到底是什么
在當(dāng)前java內(nèi)存模型下,線程可以把變量保存到本地內(nèi)存,而不是在主存中進(jìn)行讀寫,這就可能造成一個(gè)線程在主存中修改了變量的值,而另一個(gè)線程還在使用它在寄存器中變量值的拷貝,造成數(shù)據(jù)的不一致


2.PNG

使用volatile聲明變量,目的是告訴jvm,這個(gè)變量是不穩(wěn)定的,每次使用它都應(yīng)該去主存中讀寫


3.PNG

volatilel 聲明的變量除了保證可見性,還能防止指令重排
2.在修改goodnum的方法中加入 synchronized
synchronized可以保證修飾的代碼中,任何時(shí)刻只能有一個(gè)線程執(zhí)行。
class goods {
    private  volatile int num;

    /**
     * none constructor
     */
    goods(){

    }
    /**
     *
     */
    goods(int num){
        this.num=num;
    }
    public void setNum(int num){
        this.num=num;
    }
    public synchronized  int getNum(){  //該代碼塊為同步代碼塊
        return this.num;
    }
    public  synchronized int addGoods(int num){
      return   this.num+=num;
    }

    public  int consumeGoods(int num){
        return  this.num-=num;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容