當(dāng)前位置:首頁 > IT技術(shù) > 編程語言 > 正文

Java多線程
2021-10-22 10:08:41

重點部分:線程的創(chuàng)建與使用、線程的同步

?

基本概念:程序、進程、線程
程序(program)是為完成特定任務(wù)、用某種語言編寫的一組指令的集合。即指一段靜態(tài)的代碼,靜態(tài)對象。
?
進程(process)是程序的一次執(zhí)行過程,或是正在運行的一個程序。是一個動態(tài)的過程:有它自身的產(chǎn)生、存在和消亡的過程?!芷?/span>
?
線程(thread),進程可進一步細(xì)化為線程,是一個程序內(nèi)部的一條執(zhí)行路徑。
?
并行:多個CPU同時執(zhí)行多個任務(wù)。比如:多個人同時做不同的事。
并發(fā):一個CPU(采用時間片)同時執(zhí)行多個任務(wù)。比如:秒殺、多個人做同一件事。
?
一個Java應(yīng)用程序java.exe,其實至少有三個線程:main()主線程,gc()垃圾回收線程,異常處理線程。當(dāng)然如果發(fā)生異常,會影響主線程。
?
使用多線程的優(yōu)點:
  1. 提高應(yīng)用程序的響應(yīng)。對圖形化界面更有意義,可增強用戶體驗。
  2. 提高計算機系統(tǒng)CPU的利用率
  3. 改善程序結(jié)構(gòu)。將既長又復(fù)雜的進程分為多個線程,獨立運行,利于理解和修改
?
?

多線程的創(chuàng)建與使用


?

一、Thread類
構(gòu)造器
Thread()://創(chuàng)建新的Thread對象

Thread(String threadname)://創(chuàng)建線程并指定線程實例名

Thread(Runnable target)://指定創(chuàng)建線程的目標(biāo)對象,它實現(xiàn)了Runnable接口中的run方法

Thread(Runnable target, String name)://創(chuàng)建新的Thread對象

?

二、API中創(chuàng)建線程的兩種方式

JDK1.5之前創(chuàng)建新執(zhí)行線程有兩種方法:繼承Thread類的方式
                  ?實現(xiàn)Runnable接口的方式
方式一:繼承Thread類
  1) 定義子類繼承Thread類。
  2) 子類中重寫Thread類中的run方法。
  3) 創(chuàng)建Thread子類對象,即創(chuàng)建了線程對象。
  4) 調(diào)用線程對象start方法:啟動線程,調(diào)用run方法。
/**
 * 多線程的創(chuàng)建,方式一:繼承于Thread類
 * 1. 創(chuàng)建一個繼承于Thread類的子類
 * 2. 重寫Thread類的run() --> 將此線程執(zhí)行的操作聲明在run()中
 * 3. 創(chuàng)建Thread類的子類的對象
 * 4. 通過此對象調(diào)用start()
 * <p>
 * 例子:遍歷100以內(nèi)的所有的偶數(shù)
 *
 */

//1. 創(chuàng)建一個繼承于Thread類的子類
class MyThread extends Thread {
    //2. 重寫Thread類的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}


public class ThreadTest {
    public static void main(String[] args) {
        //3. 創(chuàng)建Thread類的子類的對象
        MyThread t1 = new MyThread();

        //4.通過此對象調(diào)用start():①啟動當(dāng)前線程 ② 調(diào)用當(dāng)前線程的run()
        t1.start();
        //問題一:我們不能通過直接調(diào)用run()的方式啟動線程。用run的時候,只會啟動一個線程(main主線程),只是體現(xiàn)了方法的調(diào)用,而不是多線程
//        t1.run();

        //問題二:再啟動一個線程,遍歷100以內(nèi)的偶數(shù)。不可以還讓已經(jīng)start()的線程去執(zhí)行。會報IllegalThreadStateException
//        t1.start();
        //我們需要重新創(chuàng)建一個線程的對象
        MyThread t2 = new MyThread();
        t2.start();


        //如下操作仍然是在main線程中執(zhí)行的。
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
            }
        }
    }

}
多線程的創(chuàng)建,方式一:繼承于Thread類

?

方式二:實現(xiàn)Runnable接口
1) 定義子類,實現(xiàn)Runnable接口。
2) 子類中重寫Runnable接口中的run方法。
3) 通過Thread類含參構(gòu)造器創(chuàng)建線程對象。
4) 將Runnable接口的子類對象作為實際參數(shù)傳遞給Thread類的構(gòu)造器中。
5) 調(diào)用Thread類的start方法:開啟線程,調(diào)用Runnable子類接口的run方法。
/*
 * 創(chuàng)建多線程的方式二:實現(xiàn)Runnable接口
 * 1. 創(chuàng)建一個實現(xiàn)了Runnable接口的類
 * 2. 實現(xiàn)類去實現(xiàn)Runnable中的抽象方法:run()
 * 3. 創(chuàng)建實現(xiàn)類的對象
 * 4. 將此對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對象
 * 5. 通過Thread類的對象調(diào)用start()
 *
 *
 * 比較創(chuàng)建線程的兩種方式。
 * 開發(fā)中:優(yōu)先選擇:實現(xiàn)Runnable接口的方式
 * 原因:1. 實現(xiàn)的方式?jīng)]有類的單繼承性的局限性
 *          2. 實現(xiàn)的方式更適合來處理多個線程有共享數(shù)據(jù)的情況。
 *
 * 聯(lián)系:public class Thread implements Runnable
 * 相同點:兩種方式都需要重寫run(),將創(chuàng)建線程要執(zhí)行的邏輯聲明在run()中。
 */
//1. 創(chuàng)建一個實現(xiàn)了Runnable接口的類
class MThread implements Runnable{

    //2. 實現(xiàn)類去實現(xiàn)Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

        }
    }
}


public class ThreadTest1 {
    public static void main(String[] args) {
        //3. 創(chuàng)建實現(xiàn)類的對象
        MThread mThread = new MThread();
        //4. 將此對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對象
        Thread t1 = new Thread(mThread);
        t1.setName("線程1");
        //5. 通過Thread類的對象調(diào)用start():① 啟動線程 ②調(diào)用當(dāng)前線程的run()-->調(diào)用了Runnable類型的target的run()
        t1.start();

        //再啟動一個線程,遍歷100以內(nèi)的偶數(shù)
        Thread t2 = new Thread(mThread);
        t2.setName("線程2");
        t2.start();
    }

}
創(chuàng)建多線程的方式二:實現(xiàn)Runnable接口

?

public class ThreadDemo {
    public static void main(String[] args) {
//        MyThread1 m1 = new MyThread1();
//        MyThread2 m2 = new MyThread2();
//
//        m1.start();
//        m2.start();

        //創(chuàng)建Thread類的匿名子類的方式
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if(i % 2 == 0){
                        System.out.println(Thread.currentThread().getName() + ":" + i);

                    }
                }
            }
        }.start();


        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if(i % 2 != 0){
                        System.out.println(Thread.currentThread().getName() + ":" + i);

                    }
                }
            }
        }.start();

    }
}

class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);

            }
        }

    }
}


class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);

            }
        }

    }
}
練習(xí)—創(chuàng)建Thread類的匿名子類的方式

?

三、繼承方式和實現(xiàn)方式的聯(lián)系與區(qū)別

1.? 區(qū)別

  繼承Thread:線程代碼存放Thread子類run方法中。
  實現(xiàn)Runnable:線程代碼存在接口的子類的run方法。
2. 實現(xiàn)方式的好處:避免了單繼承的局限性
多個線程可以共享同一個接口實現(xiàn)類的對象,非常適合多個相同線程來處理同一份資源。



Thread中的常用方法

測試Thread中的常用方法:
  1. start():啟動當(dāng)前線程;調(diào)用當(dāng)前線程的run()
  2. run(): 通常需要重寫Thread類中的此方法,將創(chuàng)建的線程要執(zhí)行的操作聲明在此方法中
  3. currentThread():靜態(tài)方法,返回執(zhí)行當(dāng)前代碼的線程
  4. getName():獲取當(dāng)前線程的名字
  5. setName():設(shè)置當(dāng)前線程的名字
  6. yield():釋放當(dāng)前cpu的執(zhí)行權(quán)
  7. join():在線程a中調(diào)用線程b的join(),此時線程a就進入阻塞狀態(tài),直到線程b完全執(zhí)行完以后,線程a才結(jié)束阻塞狀態(tài)。
  8. stop():已過時。當(dāng)執(zhí)行此方法時,強制結(jié)束當(dāng)前線程。
  9. sleep(long millitime):讓當(dāng)前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內(nèi),當(dāng)前線程是阻塞狀態(tài)。
  10. isAlive():判斷當(dāng)前線程是否存活

線程的優(yōu)先級:
  1.
  MAX_PRIORITY:10
  MIN _PRIORITY:1
  NORM_PRIORITY:5 -->默認(rèn)優(yōu)先級
  2.如何獲取和設(shè)置當(dāng)前線程的優(yōu)先級:
getPriority():獲取線程的優(yōu)先級
setPriority(int p):設(shè)置線程的優(yōu)先級

說明:高優(yōu)先級的線程要搶占低優(yōu)先級線程cpu的執(zhí)行權(quán)。但是只是從概率上講,高優(yōu)先級的線程高概率的情況下被執(zhí)行。并不意味著只有當(dāng)高優(yōu)先級的線程執(zhí)行完以后,低優(yōu)先級的線程才執(zhí)行。
class HelloThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){

//                try {
//                    sleep(10);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }

                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
            }

//            if(i % 20 == 0){
//                yield();
//            }

        }

    }

    public HelloThread(String name){
        super(name);
    }
}


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

        HelloThread h1 = new HelloThread("Thread:1");

//        h1.setName("線程一");
        //設(shè)置分線程的優(yōu)先級
        h1.setPriority(Thread.MAX_PRIORITY);

        h1.start();

        //給主線程命名
        Thread.currentThread().setName("主線程");
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
            }

//            if(i == 20){
//                try {
//                    h1.join();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }

        }

//        System.out.println(h1.isAlive());

    }
}
方法的使用

?

?

?

線程的生命周期


?

?

?

?

?

線程的同步
在Java中,我們通過同步機制,來解決線程的安全問題。

方式一: 同步代碼塊
//1. 同步代碼塊:

synchronized (同步監(jiān)視器(對象)){
//操作共享數(shù)據(jù)的代碼;
}

//2. synchronized還可以放在方法聲明中,表示整個方法為同步方法。
例如:
public synchronized void show (String name){ 
….
}

說明:1.操作共享數(shù)據(jù)的代碼,即為需要被同步的代碼。 -->不能包含代碼多了,也不能包含代碼少了。
  2.共享數(shù)據(jù):多個線程共同操作的變量。比如:ticket就是共享數(shù)據(jù)。一個線程操作的時候,其他的線程不能操作。
  3.同步監(jiān)視器,俗稱:鎖。任何一個類的對象,都可以充當(dāng)鎖。
要求:多個線程必須要共用同一把鎖。

補充:在實現(xiàn)Runnable接口創(chuàng)建多線程的方式中,我們可以考慮使用this充當(dāng)同步監(jiān)視器。但是慎用。

方式二:同步方法。
如果操作共享數(shù)據(jù)的代碼完整的聲明在一個方法中,我們不妨將此方法聲明同步的。


5.同步的方式,解決了線程的安全問題。---好處
操作同步代碼時,只能有一個線程參與,其他線程等待。相當(dāng)于是一個單線程的過程,效率低。 ---局限性
/**
 * 例子:創(chuàng)建三個窗口賣票,總票數(shù)為100張.使用實現(xiàn)Runnable接口的方式
 *
 * 1.問題:賣票過程中,出現(xiàn)了重票、錯票 -->出現(xiàn)了線程的安全問題
 * 2.問題出現(xiàn)的原因:當(dāng)某個線程操作車票的過程中,尚未操作完成時,其他線程參與進來,也操作車票。
 * 3.如何解決:當(dāng)一個線程a在操作ticket的時候,其他線程不能參與進來。直到線程a操作完ticket時,其他
 *            線程才可以開始操作ticket。這種情況即使線程a出現(xiàn)了阻塞,也不能被改變。
 *
 */
class Window1 implements Runnable{

    private int ticket = 100;
//    Object obj = new Object();
//    Dog dog = new Dog();
    @Override
    public void run() {
//        Object obj = new Object();
        while(true){
            synchronized (this){//此時的this:唯一的Window1的對象   //方式二:synchronized (dog) {

                if (ticket > 0) {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);


                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}


public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}


class Dog{

}
線程安全舉例

?

package com.atguigu.java;

/**
 * 使用同步方法解決實現(xiàn)Runnable接口的線程安全問題
 *
 *
 *  關(guān)于同步方法的總結(jié):
 *  1. 同步方法仍然涉及到同步監(jiān)視器,只是不需要我們顯式的聲明。
 *  2. 非靜態(tài)的同步方法,同步監(jiān)視器是:this
 *     靜態(tài)的同步方法,同步監(jiān)視器是:當(dāng)前類本身
 *
 */


class Window3 implements Runnable {

    private int ticket = 100;

    @Override
    public void run() {
        while (true) {

            show();
        }
    }

    private synchronized void show(){//同步監(jiān)視器:this
        //synchronized (this){

            if (ticket > 0) {

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);

                ticket--;
            }
        //}
    }
}


public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w = new Window3();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}
使用同步方法解決實現(xiàn)Runnable接口的線程安全問題

?

/**
 * 使用同步方法處理繼承Thread類的方式中的線程安全問題
 */
class Window4 extends Thread {


    private static int ticket = 100;

    @Override
    public void run() {

        while (true) {

            show();
        }

    }
    private static synchronized void show(){//同步監(jiān)視器:Window4.class
        //private synchronized void show(){ //同步監(jiān)視器:t1,t2,t3。此種解決方式是錯誤的
        if (ticket > 0) {

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
            ticket--;
        }
    }
}


public class WindowTest4 {
    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

    }
}
使用同步方法處理繼承Thread類的方式中的線程安全問題

?

/**
 * 使用同步機制將單例模式中的懶漢式改寫為線程安全的
 */
public class BankTest {

}

class Bank{

    private Bank(){}

    private static Bank instance = null;

    public static Bank getInstance(){
        //方式一:效率稍差
//        synchronized (Bank.class) {
//            if(instance == null){
//
//                instance = new Bank();
//            }
//            return instance;
//        }
        //方式二:效率更高
        if(instance == null){

            synchronized (Bank.class) {
                if(instance == null){

                    instance = new Bank();
                }

            }
        }
        return instance;
    }

}
使用同步機制將單例模式中的懶漢式改寫為線程安全的

?

?

死鎖


死鎖:不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖。

?

解決方法:
  專門的算法、原則
  盡量減少同步資源的定義
  盡量避免嵌套同步
/*
                         * 演示線程的死鎖問題
                         *
                         * 1.死鎖的理解:不同的線程分別占用對方需要的同步資源不放棄,
                         * 都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖
                         *
                         * 2.說明:
                         * 1)出現(xiàn)死鎖后,不會出現(xiàn)異常,不會出現(xiàn)提示,只是所有的線程都處于阻塞狀態(tài),無法繼續(xù)
                         * 2)我們使用同步時,要避免出現(xiàn)死鎖。
                         *
                         */
                        public class ThreadTest {

                            public static void main(String[] args) {

                                StringBuffer s1 = new StringBuffer();
                                StringBuffer s2 = new StringBuffer();


                                new Thread(){
                                    @Override
                                    public void run() {

                                        synchronized (s1){

                                            s1.append("a");
                                            s2.append("1");

                                            try {
                                                Thread.sleep(100);
                                            } catch (InterruptedException e) {
                        e.printStackTrace();
                    }


                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }


                }

            }
        }.start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){

                    s1.append("c");
                    s2.append("3");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");

                        System.out.println(s1);
                        System.out.println(s2);
                    }


                }



            }
        }).start();


    }


}
演示線程的死鎖問題

?

?

解決線程安全問題的方式三:Lock(鎖)


從JDK 5.0開始,Java提供了更強大的線程同步機制——通過顯式定義同步鎖對象來實現(xiàn)同步。同步鎖使用Lock對象充當(dāng)。
//java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的工具

class A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();
try{
//保證線程安全的代碼; 
}
finally{
lock.unlock(); 
  } 
 } 
}
//注意:如果同步代碼有異常,要將unlock()寫入finally語句塊

?

?

/**
 * 解決線程安全問題的方式三:Lock鎖  --- JDK5.0新增
 *
 * 1. 面試題:synchronized 與 Lock的異同?
 *   相同:二者都可以解決線程安全問題
 *   不同:synchronized機制在執(zhí)行完相應(yīng)的同步代碼以后,自動的釋放同步監(jiān)視器
 *        Lock需要手動的啟動同步(lock()),同時結(jié)束同步也需要手動的實現(xiàn)(unlock())
 *
 * 2.優(yōu)先使用順序:
 * Lock ? 同步代碼塊(已經(jīng)進入了方法體,分配了相應(yīng)資源) ? 同步方法(在方法體之外)
 *
 *
 *  面試題:如何解決線程安全問題?有幾種方式?
 *          同步代碼塊、同步方法、lock鎖
 */
class Window implements Runnable{

    private int ticket = 100;
    //1.實例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            try{

                //2.調(diào)用鎖定方法lock()
                lock.lock();

                if(ticket > 0){

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":售票,票號為:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally {
                //3.調(diào)用解鎖方法:unlock()
                lock.unlock();
            }

        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
解決線程安全問題的方式三:Lock鎖

?

synchronized 與 Lock 的對比
1. Lock是顯式鎖(手動開啟和關(guān)閉鎖,別忘記關(guān)閉鎖),synchronized是隱式鎖,出了作用域自動釋放
2. Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
3. 使用Lock鎖,JVM將花費較少的時間來調(diào)度線程,性能更好。并且具有更好的擴展性(提供更多的子類)
優(yōu)先使用順序:
Lock --> 同步代碼塊(已經(jīng)進入了方法體,分配了相應(yīng)資源)---> 同步方法(在方法體之外)


線程的通信
一、wait() 與 notify() 和 notifyAll()
1.? wait():令當(dāng)前線程掛起并放棄CPU、同步資源并等待,使別的線程可訪問并修改共享資源,而當(dāng)前線程排隊等候其他線程調(diào)用notify()或notifyAll()方法喚醒,喚醒后等待重新獲得對監(jiān)視器的所有權(quán)后才能繼續(xù)執(zhí)行。
2.? notify():喚醒正在排隊等待同步資源的線程中優(yōu)先級最高者結(jié)束等待
3.? notifyAll ():喚醒正在排隊等待資源的所有線程結(jié)束等待.
這三個方法只有在synchronized方法或synchronized代碼塊中才能使用,否則會報java.lang.IllegalMonitorStateException異常。
因為這三個方法必須有鎖對象調(diào)用,而任意對象都可以作為synchronized的同步鎖,因此這三個方法只能在Object類中聲明。

wait() 方法
  在當(dāng)前線程中調(diào)用方法: 對象名.wait()
  使當(dāng)前線程進入等待(某對象)狀態(tài) ,直到另一線程對該對象發(fā)出 notify(或notifyAll) 為止。
  調(diào)用方法的必要條件:當(dāng)前線程必須具有對該對象的監(jiān)控權(quán)(加鎖)
  調(diào)用此方法后,當(dāng)前線程將釋放對象監(jiān)控權(quán) ,然后進入等待
  在當(dāng)前線程被notify后,要重新獲得監(jiān)控權(quán),然后從斷點處繼續(xù)代碼的執(zhí)行。

notify()/notifyAll()
  在當(dāng)前線程中調(diào)用方法: 對象名.notify()
  功能:喚醒等待該對象監(jiān)控權(quán)的一個/所有線程。
  調(diào)用方法的必要條件:當(dāng)前線程必須具有對該對象的監(jiān)控權(quán)(加鎖)

/**
 * 線程通信的例子:使用兩個線程打印 1-100。線程1, 線程2 交替打印
 *
 * 涉及到的三個方法:
 * wait():一旦執(zhí)行此方法,當(dāng)前線程就進入阻塞狀態(tài),并釋放同步監(jiān)視器。
 * notify():一旦執(zhí)行此方法,就會喚醒被wait的一個線程。如果有多個線程被wait,就喚醒優(yōu)先級高的那個。
 * notifyAll():一旦執(zhí)行此方法,就會喚醒所有被wait的線程。
 *
 * 說明:
 * 1.wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中。
 * 2.wait(),notify(),notifyAll()三個方法的調(diào)用者必須是同步代碼塊或同步方法中的同步監(jiān)視器。
 *    否則,會出現(xiàn)IllegalMonitorStateException異常
 * 3.wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中。
 *
 * 面試題:sleep() 和 wait()的異同?
 * 1.相同點:一旦執(zhí)行方法,都可以使得當(dāng)前的線程進入阻塞狀態(tài)。
 * 2.不同點:1)兩個方法聲明的位置不同:Thread類中聲明sleep() , Object類中聲明wait()
 *          2)調(diào)用的要求不同:sleep()可以在任何需要的場景下調(diào)用。 wait()必須使用在同步代碼塊或同步方法中
 *          3)關(guān)于是否釋放同步監(jiān)視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。
 */
class Number implements Runnable{
    private int number = 1;
    private Object obj = new Object();
    @Override
    public void run() {

        while(true){

            synchronized (obj) {

                obj.notify();

                if(number <= 100){

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

                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;

                    try {
                        //使得調(diào)用如下wait()方法的線程進入阻塞狀態(tài)
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }else{
                    break;
                }
            }

        }

    }
}


public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("線程1");
        t2.setName("線程2");

        t1.start();
        t2.start();
    }
}
線程通信的例子+面試題

?



JDK5.0新增線程創(chuàng)建方式
新增方式一:實現(xiàn)Callable接口
  與使用Runnable相比, Callable功能更強大些
    相比run()方法,可以有返回值
    方法可以拋出異常
    支持泛型的返回值
    需要借助FutureTask類,比如獲取返回結(jié)果

?Future接口
  可以對具體Runnable、Callable任務(wù)的執(zhí)行結(jié)果進行取消、查詢是否完成、獲取結(jié)果等。
  FutrueTask是Futrue接口的唯一的實現(xiàn)類
  FutureTask 同時實現(xiàn)了Runnable, Future接口。它既可以作為Runnable被線程執(zhí)行,又可以作為Future得到Callable的返回值
package com.atguigu.java2;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 創(chuàng)建線程的方式三:實現(xiàn)Callable接口。 --- JDK 5.0新增
 *
 *
 * 如何理解實現(xiàn)Callable接口的方式創(chuàng)建多線程比實現(xiàn)Runnable接口創(chuàng)建多線程方式強大?
 * 1. call()可以有返回值的。
 * 2. call()可以拋出異常,被外面的操作捕獲,獲取異常的信息
 * 3. Callable是支持泛型的
 *
 */
//1.創(chuàng)建一個實現(xiàn)Callable的實現(xiàn)類
class NumThread implements Callable{
    //2.實現(xiàn)call方法,將此線程需要執(zhí)行的操作聲明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}


public class ThreadNew {
    public static void main(String[] args) {
        //3.創(chuàng)建Callable接口實現(xiàn)類的對象
        NumThread numThread = new NumThread();
        //4.將此Callable接口實現(xiàn)類的對象作為傳遞到FutureTask構(gòu)造器中,創(chuàng)建FutureTask的對象
        FutureTask futureTask = new FutureTask(numThread);
        //5.將FutureTask的對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread對象,并調(diào)用start()
        new Thread(futureTask).start();

        try {
            //6.獲取Callable中call方法的返回值
            //get()返回值即為FutureTask構(gòu)造器參數(shù)Callable實現(xiàn)類重寫的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("總和為:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}
創(chuàng)建線程的方式三:實現(xiàn)Callable接口。 --- JDK 5.0新增

?



新增方式二:使用線程池
提前創(chuàng)建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中??梢员苊忸l繁創(chuàng)建銷毀、實現(xiàn)重復(fù)利用。類似生活中的公共交通工具。
?
好處:
  提高響應(yīng)速度(減少了創(chuàng)建新線程的時間)
  降低資源消耗(重復(fù)利用線程池中線程,不需要每次都創(chuàng)建)
  便于線程管理
  corePoolSize:核心池的大小
  maximumPoolSize:最大線程數(shù)
  keepAliveTime:線程沒有任務(wù)時最多保持多長時間后會終止
?
JDK 5.0起提供了線程池相關(guān)API:ExecutorService 和 Executors
  ExecutorService:真正的線程池接口。常見子類ThreadPoolExecutor
  void execute(Runnable command) :執(zhí)行任務(wù)/命令,沒有返回值,一般用來執(zhí)行Runnable
  <T> Future<T> submit(Callable<T> task):執(zhí)行任務(wù),有返回值,一般又來執(zhí)行Callable
  
void shutdown() :關(guān)閉連接池
Executors:工具類、線程池的工廠類,用于創(chuàng)建并返回不同類型的線程池
Executors.newCachedThreadPool():創(chuàng)建一個可根據(jù)需要創(chuàng)建新線程的線程池
Executors.newFixedThreadPool(n); 創(chuàng)建一個可重用固定線程數(shù)的線程池
Executors.newSingleThreadExecutor() :創(chuàng)建一個只有一個線程的線程池
Executors.newScheduledThreadPool(n):創(chuàng)建一個線程池,它可安排在給定延遲后運行命令或者定期地執(zhí)行。

/**
 * 創(chuàng)建線程的方式四:使用線程池
 *
 * 好處:
 * 1.提高響應(yīng)速度(減少了創(chuàng)建新線程的時間)
 * 2.降低資源消耗(重復(fù)利用線程池中線程,不需要每次都創(chuàng)建)
 * 3.便于線程管理
 *      corePoolSize:核心池的大小
 *      maximumPoolSize:最大線程數(shù)
 *      keepAliveTime:線程沒有任務(wù)時最多保持多長時間后會終止
 *
 *
 * 面試題:創(chuàng)建多線程有幾種方式?四種!
 */

class NumberThread implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args) {
        //1. 提供指定線程數(shù)量的線程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //設(shè)置線程池的屬性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();


        //2.執(zhí)行指定的線程的操作。需要提供實現(xiàn)Runnable接口或Callable接口實現(xiàn)類的對象
        service.execute(new NumberThread());//適合適用于Runnable
        service.execute(new NumberThread1());//適合適用于Runnable

//        service.submit(Callable callable);//適合使用于Callable
        //3.關(guān)閉連接池
        service.shutdown();
    }

}
創(chuàng)建線程的方式四:使用線程池

?











本文摘自 :https://www.cnblogs.com/

開通會員,享受整站包年服務(wù)立即開通 >