Java-多線程(二)線程的狀態(tài)和線程安全問題

線程的狀態(tài)

線程的狀態(tài)

線程安全問題

  • 案例:售票的例子。

class Ticket implements Runnable
{
    //1,描述票的數(shù)量。
    private int tickets = 100;
    //2,售票的動(dòng)作,這個(gè)動(dòng)作需要被多線程執(zhí)行,那就是線程任務(wù)代碼。需要定義run方法中。
    //線程任務(wù)中通常都有循環(huán)結(jié)構(gòu)。
    private Object obj = new Object();
    public void run()
    {
        while(true)
        {
             if(tickets>0)
             {
                  //要讓線程在這里稍停,模擬問題的發(fā)生。sleep  看到了0 -1 -2 錯(cuò)誤的數(shù)據(jù),這就是傳說中的多線程安全問題。
                  try{Thread.sleep(1);}catch(InterruptedException e){}
                  //打印線程名稱。
                  System.out.println(Thread.currentThread().getName()+"....."+tickets--);
             }
         }
    }
}
class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        //1,創(chuàng)建Runnable接口的子類對(duì)象。
        Ticket t = new Ticket();

        //2,創(chuàng)建四個(gè)線程對(duì)象。并將Runnable接口的子類對(duì)象作為參數(shù)傳遞給Thread的構(gòu)造函數(shù)。
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);

        //3,開啟四個(gè)線程。
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

售票的動(dòng)作需要同時(shí)執(zhí)行,所以使用多線程技術(shù)。

發(fā)生了線程安全問題:出現(xiàn)了錯(cuò)誤的數(shù)據(jù)。0 -1 -2 

問題產(chǎn)生的原因;
1,線程任務(wù)中在操作共享的數(shù)據(jù)。
2,線程任務(wù)操作共享數(shù)據(jù)的代碼有多條(運(yùn)算有多個(gè))。

解決思路:
只要讓一個(gè)線程在執(zhí)行線程任務(wù)時(shí)將多條操作共享數(shù)據(jù)的代碼執(zhí)行完,
在執(zhí)行過程中,不要讓其他線程參與運(yùn)算。就哦了。

代碼體現(xiàn)呢?
Java中解決此問題通過代碼塊來完成的。
這個(gè)代碼塊:同步代碼塊 synchronized
格式:
synchronized(對(duì)象)
{
    //需要被同步的代碼。
}

同步好處:
解決多線程安全問題。

同步弊端:
降低了程序的性能。

同步前提:
必須保證多個(gè)線程在同步中使用的是同一個(gè)鎖。

解決了什么問題?
當(dāng)多線程安全問題發(fā)生時(shí),加入了同步后,
問題依舊,就要通過這個(gè)同步的前提來判斷同步是否寫正確。
  • 使用同步代碼塊

class Ticket implements Runnable
{
    //1,描述票的數(shù)量。
    private int tickets = 100;
    //2,售票的動(dòng)作,這個(gè)動(dòng)作需要被多線程執(zhí)行,那就是線程任務(wù)代碼。需要定義run方法中。
    //線程任務(wù)中通常都有循環(huán)結(jié)構(gòu)。
    private Object obj = new Object();
    public void run()
    {
        while(true)
        {
            synchronized(obj)
            {
                if(tickets>0)
                {
                    //要讓線程在這里稍停,模擬問題的發(fā)生。sleep  看到了0 -1 -2 錯(cuò)誤的數(shù)據(jù),這就是傳說中的多線程安全問題。
                    try{Thread.sleep(1);}catch(InterruptedException e){/*未寫處理方式,后面講*/}

                    System.out.println(Thread.currentThread().getName()+"....."+tickets--);//打印線程名稱。
                }
            }
        }
    }
}
class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        //1,創(chuàng)建Runnable接口的子類對(duì)象。
        Ticket t = new Ticket();

        //2,創(chuàng)建四個(gè)線程對(duì)象。并將Runnable接口的子類對(duì)象作為參數(shù)傳遞給Thread的構(gòu)造函數(shù)。
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);

        //3,開啟四個(gè)線程。
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
  • 使用同步函數(shù)

同步的另一種體現(xiàn)形式:同步函數(shù)。

同步函數(shù)使用的鎖是哪個(gè)?
經(jīng)過分析:大概猜的是this,因?yàn)楹瘮?shù)必須被對(duì)象調(diào)用。

驗(yàn)證:
寫一個(gè)同步代碼塊,寫一個(gè)同步函數(shù),如果同步代碼塊中的鎖對(duì)象和同步函數(shù)中的鎖對(duì)象是同一個(gè),
就同步了,就沒有錯(cuò)誤的數(shù)據(jù)了。如果不是同一個(gè)鎖對(duì)象,就不同步出現(xiàn)錯(cuò)誤數(shù)據(jù)。

讓兩個(gè)線程,一個(gè)線程在同步代碼塊中執(zhí)行,一個(gè)線程在同步函數(shù)中執(zhí)行。
  • 非靜態(tài)同步函數(shù)使用的鎖是this
class Ticket implements Runnable
{
    private int tickets = 100;
    private Object obj = new Object();
    boolean flag = true;
    public void run()
    {
        if(flag){
            while(true){
                //驗(yàn)證同步函數(shù)使用的鎖對(duì)象是this
                synchronized(this){
                    if(tickets>0){
                        try{Thread.sleep(10);}catch(InterruptedException e){}
                        System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印線程名稱。
                    }
                }
            }
        }
        else{
            while(true){
                this.sale();
            }
        }
    }

    public synchronized void sale()//同步函數(shù),使用的鎖對(duì)象 this。
    {
        if(tickets>0)
        {
            try{Thread.sleep(10);}catch(InterruptedException e){}
            System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印線程名稱。
        }
    }
}
class ThreadDemo4 
{
    public static void main(String[] args) 
    {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);

        t1.start();
        try{Thread.sleep(10);}catch(InterruptedException e){}
        //切換標(biāo)記,之前,讓主線程停一會(huì),這時(shí)就只有一個(gè)t1線程在,它就會(huì)執(zhí)行同步代碼塊。
        t.flag = false;

        t2.start();
    }
}
  • 靜態(tài)同步函數(shù)使用的鎖是字節(jié)碼文件對(duì)象, 類名.class
class Ticket implements Runnable
{
    private static int tickets = 100;
    private Object obj = new Object();
    boolean flag = true;
    public void run()
    {
        if(flag){
            while(true){
                synchronized(Ticket.class){
                    if(tickets>0){
                        try{Thread.sleep(10);}catch(InterruptedException e){}
                        System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印線程名稱。
                    }
                }
            }
        }
        else{
            while(true){
                this.sale();
            }
        }
    }

    public static synchronized void sale()//
    {
        if(tickets>0)
        {
            try{Thread.sleep(10);}catch(InterruptedException e){}
            System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印線程名稱。
        }
    }
}
class ThreadDemo5 
{
    public static void main(String[] args) 
    {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);

        t1.start();
        try{Thread.sleep(10);}catch(InterruptedException e){}
        //切換標(biāo)記,之前,讓主線程停一會(huì),這時(shí)就只有一個(gè)t1線程在,它就會(huì)執(zhí)行同步代碼塊。
        t.flag = false;

        t2.start();
    }
}
  • 總結(jié)
總結(jié):非靜態(tài)同步函數(shù)使用的鎖是this。
     靜態(tài)同步函數(shù)使用的鎖是字節(jié)碼文件對(duì)象, 類名.class

同步函數(shù)和同步代碼塊有什么區(qū)別嗎?

同步函數(shù)使用的鎖是固定的this或 類名.class。當(dāng)線程任務(wù)只需要一個(gè)同步時(shí)完全可以使用同步函數(shù)。
同步代碼塊使用的鎖可以是任意對(duì)象。當(dāng)線程任務(wù)中需要多個(gè)同步時(shí),必須通過鎖來區(qū)分,這時(shí)必須使用同步代碼塊。
同步代碼塊較為常用。

synchronized的一點(diǎn)思考

在run方法上加上synchronized后變成同步函數(shù),就是讓整個(gè)程序變成單線程,降低了程序的效率,所以只在操作共享數(shù)據(jù)的代碼加上synchronized就可以了。

class Ticket implements Runnable
{
    //1,描述票的數(shù)量。
    private int tickets = 100;
    //2,售票的動(dòng)作,這個(gè)動(dòng)作需要被多線程執(zhí)行,那就是線程任務(wù)代碼。需要定義run方法中。
    //線程任務(wù)中通常都有循環(huán)結(jié)構(gòu)。
    private Object obj = new Object();
    public synchronized void run()
    {
        while(true)
        {
             if(tickets>0)
             {
                  //要讓線程在這里稍停,模擬問題的發(fā)生。sleep  看到了0 -1 -2 錯(cuò)誤的數(shù)據(jù),這就是傳說中的多線程安全問題。
                  try{Thread.sleep(1);}catch(InterruptedException e){}
                  //打印線程名稱。
                  System.out.println(Thread.currentThread().getName()+"....."+tickets--);
             }
         }
    }
}
class ThreadDemo3 
{
    public static void main(String[] args) 
    {
        //1,創(chuàng)建Runnable接口的子類對(duì)象。
        Ticket t = new Ticket();

        //2,創(chuàng)建四個(gè)線程對(duì)象。并將Runnable接口的子類對(duì)象作為參數(shù)傳遞給Thread的構(gòu)造函數(shù)。
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);

        //3,開啟四個(gè)線程。
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
最后編輯于
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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