Java多線程(一)

Java多線程(一):
https://blog.csdn.net/Veer_c/article/details/103842078
Java多線程(二):
https://blog.csdn.net/Veer_c/article/details/103842263
Java多線程(三):
https://blog.csdn.net/Veer_c/article/details/103842317
Java多線程(四):
https://blog.csdn.net/Veer_c/article/details/103842602

多線程

線程是依賴於進程而存在的。
進程:正在運行的應用程序,每一正在運行的程序都會對應一個進程。
線程:進程的執行路徑,執行單元
單線程和多線程的區別:
比如說有如下代碼:

public class Test {
    public static void main(String[] args) {
        代碼1;
        show1();
        代碼2;
        show2();
        代碼3...
    }
    public static void show1(){
         代碼11;
         代碼12;
    }
    public staic void show2(){
    代碼22;
    代碼23;
    }
}

在這裏插入圖片描述
在這裏插入圖片描述
在代碼執行的時候,由於多線程的存在,效率會很高
多線程的兩種方案:
(1)繼承Thread類:

public class MyThread extends Thread{
    //1.繼承Thread類
    //2.重寫run方法,重寫run方法中的代碼之後,當我們啓動了這個線程之後,我們的這個線程就會執行run方法中的代碼
    @Override
    public void run() {
        //需求:開啓該線程之後,執行一個for循環
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

(2)實現Runable接口:

public class MyThread implements Runnable{
//實現runnable接口
    @Override
    public void run() {
        //啓動該線程對象之後,需要執行的代碼
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }   
    }
}

多線程的問題:

啓動線程我們可以利用線程對象調用start()方法
start()和run()的區別
start()是在啓動線程後執行run方法裏裏面的代碼,而直接利用調用run()方法來執行方法,是在主線程裏面運行該方法
start():1.開啓線程 2.執行run()方法裏面的代碼
run():執行的是線程裏面執行的代碼,並不會開啓線程
爲什麼要重寫run()
因爲每個線程需要執行的代碼都是都是不一樣的,
我們需要將每個線程自己獨立執行的代碼寫到run()方法中執行
線程不可以被多次啓動,否則會出現:java.lang.IllegalThreadStateException
因爲在執行同一個線程的時候,一個線程剛開始執行,但是同時又重新開始,所以會拋出異常;
注意:如果是倆個不同的線程,可以同時執行

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //B:start()和run()的區別
        // 創建一個線程獨享
        MyThread mt = new MyThread();
        MyThread mt2 = new MyThread();
        //mt.start();//1.首先開啓了一個獨立的線程 2.讓這個獨立的線程執行run方法中的代碼
        System.out.println("--------");
        //mt.run();//1.普通的創對象,調方法  2.代碼實在主線程執行,不會開闢新線程
        //D:線程可以多次啓動嗎
        mt.start();
        //mt2.start();//如果是不同的線程對象,是可以同時開啓的
        //mt.start();//線程是不能多次啓動的
    }
}

線程的調度和控制
線程休眠(Thread.sleep(毫秒值))
線程名稱(setName(),getName();)
線程的調度及優先級setPriority(10)(注意默認值是5,區間在1-10之間)

線程優先級:
當我們多個線程開始運行的時候,線程會槍戰CPU的執行權,線程優先級就是你搶到CPU執行權的概率。

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //線程休眠(Thread.sleep(毫秒值))
            try {
                Thread.sleep(1000);//在此處出現的異常我們只能抓取,不能拋出
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            //獲取執行線程的姓名
            System.out.println(this.getName()+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //線程名稱(setName(),getName();)
        //創建3個線程對象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        //給三個線程設置姓名
        t1.setName("劉備");
        t2.setName("張飛");
        t3.setName("關羽");
        //設置線程的優先級
        //線程的調度及優先級setPriority(10)(注意默認值是5,區間在1-10之間)
        //t1.setPriority(100);//設置的區間必須在1-10之間
        t1.setPriority(10);
        //開啓線程
        t1.start();
        t2.start();
        t3.start(); 
    }
}

多線程案例
1.繼承Thread賣票。

public class MyThread extends Thread{
    //共有100張票,將ticket改爲靜態之後,被類的所有對象所共享
    static int ticket = 100;
    @Override
    public void run() {
        //用一個while true循環模擬三個窗口一直處於打開的狀態
        while (true) {
            //只有當ticket>0的時候,纔可以出售票
            if (ticket>0) {
                System.out.println(getName()+"正在出售第:"+ticket--+"張票");
            }
        }
    }
}
//5.1繼承Thread賣票
public class Test {
    public static void main(String[] args) {
        //創建三個線程模擬三個售票窗口
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        MyThread mt3 = new MyThread();
        //給線程設置名稱
        mt1.setName("窗口一");
        mt2.setName("窗口二");
        mt3.setName("窗口三");
        //啓動線程,開啓售票
        mt1.start();
        mt2.start();
        mt3.start();
    }
}

2.實現Runnable賣票:

public class MyThread implements Runnable{
    int ticket = 100;
    @Override
    public void run() {
        while (true) {
            if (ticket>0) {                
            System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"張票");
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //創建MyThread這個對象
        MyThread mt = new MyThread();
        //創建3個窗口
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        Thread t3 = new Thread(mt);
        //給線程對象設置名稱
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        //啓動窗口開始售票
        t1.start();
        t2.start();
        t3.start();
    }
}

注意:在實現runnable藉口的時候,我們可以不用把ticket設置成static,因爲在實現runnable的時候,我們本來就創建了一個實現類對象,然後將是實現類對象轉換成三個不同的thread對象,所以我們的ticket本開就是共享的。

在實際生活中,我們買票的時候跟定會有時間上的延遲,所以我們給線程中加入延遲,按照真實的情景加入了延遲,卻發現出現了這樣的兩個問題:
相同的票賣了多次:CPU的一次操作必須是原子性的(操作是CPU執行一次就可以直接完成的)
出現了負數的票:隨機性和延遲導致的
出現上面的問題稱爲線程安全問題。

出現多線程安全問題的條件:
是否是多線程環境
是否有共享數據
是否有多條語句操作共享數據

如何解決多線程安全問題
線程安全執行效率會有所降低
同步代碼塊:
synchronized(對象) {
需要被同步的代碼。
}

需求:
1.測試不是同一把鎖的時候線程安全嗎?
答:當我們每個線程進入程序的時候,我們都會出創建一個把鎖,這樣執行還是會出錯的。
2.如果是同一把鎖線程安全嗎?
如果我們利用同一把鎖,則不會出現上面的問題,線程安全
1.synchronized的對象是什麼
答:任意對象 ,相當於是一把鎖,只要線程進去就把鎖鎖上
2.需要同步的代碼?
答:被線程執行的代碼

鎖對象問題:
同步代碼塊(定義一個抽象類,裏面專門定義一個鎖),任意對象

public class MyThread implements Runnable{
    //定義100張票
    int ticket = 100;
    Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            //同步代碼塊
            //synchronized (new Object()) {//t1,t2,t3三個線程不共享同一把鎖每個線程都有自己的議案鎖
            synchronized (obj) {//這樣3個線程纔可以共享同一把鎖   
            if (ticket>0) {
                    //考慮到實際的生活中,我們需要給每一個線程加入一定的延遲,模擬一下這種效果
                    try {
                        Thread.sleep(100);
                        /**
                         * 分析:爲什麼會出現兩張100張票
                         * t1搶佔到cpu的執行權,此時ticket=100,但是此刻休眠了
                         * 此時被t2搶佔到了cpu的執行權,此時ticket=100,
                         * t1,t2分別睡了100毫秒之後,分別醒來了。。
                         * t1此時出售第100張票
                         * t2此時出售第100張票
                         */
                        /**
                         * 分析:爲什麼會出現0張票和-1張票
                         * 假設此時票池中僅剩1張票了,
                         * t1進來休眠了
                         * t2進來休眠了
                         * t3進來休眠了  */
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                 System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"張票");
                    /* t1醒來,出售的是第1張票,此時tickt=0
                     * t2醒來,出售第0張票,此時ticket=-1
                     * t3醒來,出售第-1張票,此時ticket=-2              */

                    /* ticket--這個動作一共包含幾步:
                     * 1.打印出ticket此刻本身的值
                     * 2.ticket自減1
                     * 3.將自減之後的ticket的最新的值賦值給變量ticket            */
                }
            }
            //當被同步的代碼執行完畢之後,t1手裏拿着的obj這個鎖纔會被釋放,
            //t1,t2,t3重新搶佔cpu的執行權,誰搶到了繼續拿着obj這個鎖,執行同步代碼塊中的內容
        }
    }

同步方法(僅適用於實現runable接口)
public synchronized void sellTicket(){同步代碼}
//this

private static synchronized void sellTicket() { 
    if (ticket>0) {
            //考慮到實際的生活中,我們需要給每一個線程加入一定的延遲,模擬一下這種效果
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }         System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"張票");
    }
}
}

將synchronized關鍵字加到方法上

靜態同步方法
類的字節碼對象
public static synchronized void sellTicket() {
需要同步的代碼
}

匿名內部類的方式使用多線程

new Thread() {
            public void run() {
                ...
            }
        }.start();
new Thread(new Runnable(){
            public void run() {
                ...
            }
        }).start();

利用匿名內部類啓動多線程

public void run() {
        while (true) {
            if (x%2==0) {
                synchronized (MyThread.class) {//這樣3個線程纔可以共享同一把鎖    
                    if (ticket>0) {
                            //考慮到實際的生活中,我們需要給每一個線程加入一定的延遲,模擬一下這種效果
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }                  System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"張票");
            }
         }

Java多線程(一):
https://blog.csdn.net/Veer_c/article/details/103842078
Java多線程(二):
https://blog.csdn.net/Veer_c/article/details/103842263
Java多線程(三):
https://blog.csdn.net/Veer_c/article/details/103842317
Java多線程(四):
https://blog.csdn.net/Veer_c/article/details/103842602

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