java多線程--11

多線程

進程:是一個正在執行的程序
每一個進程執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元

線程:就是進程中的一個獨立的控制單元
線程在控制着進程的執行

一個進程中至少有一個線程

如何自定義線程?
通過對API的查找,java已經提供了對線程這類事物的描述。就是Thread類

創建線程的第一種方式就是繼承Thread類
1,定義類繼承Thread
目的:將自定義的代碼存儲在run方法,讓線程運行
2,複寫Thread中的run方法
3,調用線程的start方法,
該方法有兩個作用,啓動線程,調用run方法

class Demo extends Thread
{
    public void run()
    {
        for(int x = 0;x<60;x++)
            System.out.println("Demo run---"+x);
    }
}



class ThreadDemo
{
    public static void main(String[] args)
    {

        Demo d = new Demo();//創建好一個線程
        d.start();          //開啓線程,並執行該線程的run方法
        //d.run();            //僅僅是對象調用方法,而線程創建了沒有運行

        for(int x = 0;x<60;x++)
            System.out.println("hello world----"+x);
    }
}

這裏寫圖片描述

發現運行結果每一次都不同,
因爲多個線程都獲取CPU的執行權,CPU執行到誰,誰就運行。
明確一點:在某一刻,只能有一個程序在運行。(多核除外)
CPU在做着快速的切換,以達到看上去是同時執行的效果。
我們形象的看成多線程的運行在搶奪CPU的執行權

這就是多線程的一個特性,隨機性。誰搶到就執行誰,至於執行多長,CPU說了算。

爲什麼要覆蓋run方法呢

Thread類用於描述線程
該類就定義了一個功能用於存儲線程要運行的代碼。
該存儲功能就是run方法

也就是說Thread類中的run方法,用於存儲線程要運行的代碼

練習
創建兩個線程,和主線程交替運行

原來線程都有自己默認的名稱
Thread-編號 該編號從0開始

static Thread currentThread();獲取當前線程對象
getName() 獲取線程名稱

設置線程名稱:setName或者構造函數

class Test extends Thread
{
    Test(String name)
    {
        super(name);
    }
    public void run()
    {
        for(int x=0;x<60;x++)
        {
            //System.out.println(Thread.currentThread().getName()+"run..."+x);
            System.out.println(this.getName()+"run..."+x);
        }
    }
}

class ThreadTest
{
    public static void main(String[] args)
    {
        Test t1 = new Test("one");
        Test t2 = new Test("two");
        t1.start();
        t2.start();

        for(int x=0;x<60;x++)
        {
            System.out.println("main run----"+x);
        }
    }
}

需求:簡單的賣票程序
多個窗口同時賣票

創建多線程的第二種方式:實現Runnable接口

步驟:
1,定義類實現Runnable接口
2,覆蓋Runnable接口中的run方法
將線程要運行的代碼存放在該run方法中

3,通過Thread類建立線程對象
4,將Runnable接口中的子類對象作爲實際參數傳遞給Thread類的構造函數
爲什麼要將Runnable接口的子類對象作爲實際參數傳遞給Thread的構造函數
因爲,自定義的run方法所屬的對象是Runnable接口的子類對象
所以要讓線程去指定對象的run方法,就必須明確該run方法所屬的對象
5,調用Thread類的start方法開啓線程並調用Runnable接口子類的run方法

class Ticket implements Runnable
{
    private int tick = 100;
    public void run()
    {

        while(true)
        {
            if(tick>0)
            {
                System.out.println(Thread.currentThread().getName()+"...sale:"+tick--);

            }
        }
    }

}
class TicketDemo
{
    public static void main(String[] args)
    {

        Ticket t = new Ticket();

        Thread t1 = new Thread(t);//創建了一個線程
        Thread t2 = new Thread(t);//創建了一個線程
        Thread t3 = new Thread(t);//創建了一個線程
        Thread t4 = new Thread(t);//創建了一個線程

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

實現方式和繼承方式有什麼區別?

實現方式好處:避免了單繼承的侷限性
在定義線程時,建立使用實現方式

兩種方式區別:
繼承Thread:線程代碼存放在Thread子類run方法中
實現Runnable,線程存放在接口的子類的run方法中

class Ticket implements Runnable
{
    private int tick = 100;
    public void run()
    {

        while(true)
        {
            if(tick>0)
            {

                try{Thread.sleep(10);}catch(Exception e){}
    System.out.println(Thread.currentThread().getName()+"...sale:"+tick--);
            }
        }
    }
}

class TicketDemo2
{
    public static void main(String[] args)
    {


        Ticket t = new Ticket();

        Thread t1 = new Thread(t);//創建了一個線程
        Thread t2 = new Thread(t);//創建了一個線程
        Thread t3 = new Thread(t);//創建了一個線程
        Thread t4 = new Thread(t);//創建了一個線程

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

這裏寫圖片描述

通過分析,發現打印出了錯票(0,-1,-2)

多線程的運行出現了安全問題

問題原因:
當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分
還沒有執行完,另一條線程參與進來執行,導致共享數據的錯誤。

解決辦法:
對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行

java對於多線程的安全問題提供了專業的解決方式
就是同步代碼塊

synchronized(對象)
{
需要被同步的代碼
}

對象如同鎖,持有鎖的線程可以在同步中執行
沒有持有鎖的線程即使獲得CPU的執行權,也進不去,因爲沒有獲取鎖

同步的前提:
1,必須要有兩個或以上的線程
2,必須是多個線程使用同一個鎖

必須保證同步中只能有一個線程在運行

好處:解決了多線程的安全問題

弊端:多個線程需要判斷鎖,較爲消耗資源。

class Ticket implements Runnable
{
    private int tick = 100;
    Object obj  = new Object();//synchornized需要對象
    public void run()
    {

        while(true)
        {
            synchronized(obj)
            {


                if(tick>0)
                {

                    //try{Thread.sleep(10);}catch(Exception e){}

                    System.out.println(Thread.currentThread().getName()+"...sale:"+tick--);
                }
            }
        }
    }

}
class TicketDemo2
{
    public static void main(String[] args)
    {


        Ticket t = new Ticket();

        Thread t1 = new Thread(t);//創建了一個線程
        Thread t2 = new Thread(t);//創建了一個線程
        Thread t3 = new Thread(t);//創建了一個線程
        Thread t4 = new Thread(t);//創建了一個線程

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

需求:
銀行有一個金庫
有兩個儲戶分別存300,每次存一百,存三次

目的:該程序是否有安全問題,如果有,如何解決

如何找到問題?
1,明確哪些代碼是多線程運行代碼
2,明確共享數據
3,明確多線程運行代碼中哪些語句是操作共享數據的

class Bank
{
    private int sum;
    //Object obj = new Object();
    public synchronized void add(int n)//有了同步函數
    {
        //synchronized(obj)//不需要同步代碼塊
        //{
            sum = sum+n;
            try{Thread.sleep(10);}catch(Exception e){}
            System.out.println("sum="+sum);
        //}
    }
}

class Cus implements Runnable
{
    private Bank b = new Bank();
    public void run()
    {
        for(int x=0;x<3;x++)
        {
            b.add(100);
        }
    }
}

class BankDemo
{
    public static void main(String[] args)
    {
        Cus c = new Cus();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}

同步函數用的是哪一個鎖?
函數需要被對象調用,那麼函數都有一個所屬對象引用。就是this
所以同步函數使用的鎖是this

通過該程序進行驗證

使用兩個線程來賣票
一個線程在同步代碼塊中
一個線程在同步函數中
都在執行賣票動作

class Ticket implements Runnable
{
    private int tick = 100;
    Object obj  = new Object();
    boolean flag = true;
    public void run()
    {
        if(flag)
        {
            while(true)
            {
                synchronized(this)
                {
                    if(tick>0)
                    {

                        try{Thread.sleep(10);}catch(Exception e){}

                        System.out.println(Thread.currentThread().getName()+"...code:"+tick--);
                    }

                }
            }
        }
        else
            while(true)
                show();

    }
    public synchronized void show()//this
    {
        if(tick>0)
            {

                try{Thread.sleep(10);}catch(Exception e){}

                System.out.println(Thread.currentThread().getName()+"...show....:"+tick--);
            }
    }
}
class ThisLockDemo
{
    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(Exception e){}
        t.flag = false;
        t2.start();
    }
}

這裏寫圖片描述

如果同步函數被靜態修飾後,使用的鎖是什麼?

通過驗證,發現不再是this。因爲靜態方法中也不可以定義this

靜態進內存時,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象
類名.class 該對象的類型是class

靜態的同步方法,使用的那個鎖是該方法所在類的字節碼文件對象。類名.class

class Ticket implements Runnable
{
    private static int tick = 100;
    boolean flag = true;
    public void run()
    {
        if(flag)
        {
            while(true)
            {


                synchronized(Ticket.class)
                {
                    if(tick>0)
                    {

                        try{Thread.sleep(10);}catch(Exception e){}

                        System.out.println(Thread.currentThread().getName()+"...code:"+tick--);
                    }

                }
            }
        }
        else
            while(true)
                show();

    }
    public static synchronized void show()//this
    {
        if(tick>0)
            {

                try{Thread.sleep(10);}catch(Exception e){}

                System.out.println(Thread.currentThread().getName()+"...show....:"+tick--);
            }
    }


}
class StaticMethodDemo
{
    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(Exception e){}
        t.flag = false;
        t2.start();

    }
}

單例設計模式

餓漢式

class Single
{
    private static final Single s = new Single();
    //加了一個final
    private Single(){}
    public static Single getInstance()
    {
        return s;
    }

class SingleDemo
{
    public static void main(String[] args)
    {

    }
}

懶漢式

class Single
{
    private static Single s = null;
    private Single(){}


    public static  Single getInstance()
    {
        if(s==null)//減少鎖的判斷的次數
        {
            synchronized(Single.class)
            {
                if(s==null)
                    s = new Single();
            }
        }
        return s;
    }
}


class SingleDemo
{
    public static void main(String[] args)
    {

    }
}

死鎖

死鎖
同步中嵌套同步

class Ticket implements Runnable
{
    private int tick = 100;
    Object obj  = new Object();
    boolean flag = true;
    public void run()
    {
        if(flag)
        {
            while(true)
            {

                synchronized(obj)
                {
                        show();
                }
            }
        }
        else
            while(true)
                show();

    }
    public synchronized void show()//this
    {
        synchronized(obj)
        {
            if(tick>0)
            {

                try{Thread.sleep(10);}catch(Exception e){}

                System.out.println(Thread.currentThread().getName()+"...code:"+tick--);
            }
        }
    }   
}
class DeadLockDemo
{
    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(Exception e){}
        t.flag = false;
        t2.start();

    }
}

這裏寫圖片描述

實際開發中要避免死鎖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章