JAVA_基礎多線程的生命週期、線程安全、線程鎖、線程通訊與建立線程(二)

線程的同步

同步代碼塊實現:繼承Thread線程安全問題

① 操做共享數據的代碼,即爲須要被同步的代碼。(不能包含代碼多了,也不能包含代碼少了)
② 共享數據:多個線程共同操做的變量。好比:ticket就是共享數據。
③ 同步監視器,俗稱:鎖。任何一個類的對象,均可以充當鎖。
要求:多個線程必需要共用同一把鎖。
補充:在實現Runnable接口建立多線程的方式中,咱們能夠考慮使用this充當同步監視器。java

class RWindow2 extends Thread {
    public static int ticket = 100;
    private static Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            //正確的
//            synchronized (obj){
            synchronized (RWindow2.class) {//Class clazz = Window2.class,Window2.class只會加載一次
//            synchronized (this) {//錯誤的方式:this表明着t1,t2,t3三個對象
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":賣票,票號:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class WindowTest2 {
    public static void main(String[] args) {
        RWindow2 rWindow1 = new RWindow2();
        RWindow2 rWindow2 = new RWindow2();
        RWindow2 rWindow3 = new RWindow2();
        rWindow1.setName("窗口1");
        rWindow2.setName("窗口2");
        rWindow3.setName("窗口3");
        rWindow1.start();
        rWindow2.start();
        rWindow3.start();
    }
}

同步代碼塊實現:實現Runnable線程安全問題

class RWindow implements Runnable {
    private int ticket = 100;
//    Object obj = new Object();
    @Override
    public void run() {
        while(true) {
//            synchronized(obj) {//方法一
              synchronized(this) { //方式二:此時的this:惟一的RWindow的對象
                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) {
        RWindow rWindow = new RWindow();
        Thread thread1 = new Thread(rWindow);
        thread1.setName("窗口一");
        thread1.start();
        Thread thread2 = new Thread(rWindow);
        thread2.setName("窗口二");
        thread2.start();
        Thread thread3 = new Thread(rWindow);
        thread3.setName("窗口三");
        thread3.start();
    }
}

同步方法實現:接口Runnable線程安全問題

① 同步方法仍然涉及到同步監視器,只是不須要咱們顯式的聲明。
② 非靜態的同步方法,同步監視器是:this。
③ 靜態的同步方法,同步監視器是:當前類自己。算法

class RWindow2 implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            show();
        }
    }
    public synchronized void show() {//同步監視器: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 WindowTest2 {
    public static void main(String[] args) {
        RWindow2 rWindow = new RWindow2();
        Thread thread1 = new Thread(rWindow);
        thread1.setName("窗口一");
        thread1.start();
        Thread thread2 = new Thread(rWindow);
        thread2.setName("窗口二");
        thread2.start();
        Thread thread3 = new Thread(rWindow);
        thread3.setName("窗口三");
        thread3.start();
    }
}

同步方法實現:繼承Thread線程安全問題

class RWindow3 extends Thread {
    private static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            show();
        }
    }
      private static synchronized void show() {//同步監聽器:Window4.class
//    private synchronized void show() {//同步監聽器:rWindow1,rWindow2,rWindow3 此種解決方式是錯誤的
        if (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":賣票,票號:" + ticket);
            ticket--;
        }
    }


public class WindowTest3 {
    public static void main(String[] args) {
        RWindow3 rWindow1 = new RWindow3();
        RWindow3 rWindow2 = new RWindow3();
        RWindow3 rWindow3 = new RWindow3();
        rWindow1.setName("窗口一");
        rWindow1.start();
        rWindow2.setName("窗口二");
        rWindow2.start();
        rWindow3.setName("窗口三");
        rWindow3.start();
    }
}

線程的死鎖問題

死鎖:安全

  • 不一樣的線程分別佔用對方須要的同步資源不放棄,都在等待對方放棄 本身須要的同步資源,就造成了線程的死鎖。
  • 出現死鎖後,不會出現異常,不會出現提示,只是全部的線程都處於 阻塞狀態,沒法繼續。

解決方法:多線程

  • 專門的算法、原則。
  • 儘可能減小同步資源的定義。
  • 儘可能避免嵌套同步。
/** 演示死鎖問題 */
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.currentThread().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.currentThread().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(鎖)解決線程安全問題

解決線程安全問題方式三:Lock(鎖) ---JDK5.0新增併發

synchronizedlock的異同

相同點:兩者均可以解決線程安全問題。app

不相同synchronized機制在執行完相應的同步代碼之後,自動的釋放同步監視器,Lock須要手動的啓動同步(lock()),同時結束同步也須要手動實現(unlock())。ide

建議(優先使用順序)Lock 同步代碼塊(已經進入了方法體,分配了相應資源)同步方法 (在方法體以外)。工具

class Window implements Runnable {
    private int ticket = 100;
    //1.實例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock(true);
    @Override
    public void run() {
        while (true) {
            try {
                //2.調用clock()
                lock.lock();
                //2.調用Lock()
                if (ticket > 0) {
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":售票,票號爲" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                //3.調用解鎖方法:unLock
                lock.unlock();
            }
        }
    }
}
public class LockTest {
    public static void main(String[] args) {
        Window window = new Window();
        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

線程通訊

涉及到的三個方法:性能

  • wait():一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器。
  • notify():一旦執行此方法,就會喚醒被wait的一個線程。若是有多個線程被wait,就喚醒優先級高的那個。
  • notifyAll():一旦執行此方法,就會喚醒全部被wait的線程。

1)wait()notify()notifyAll()三個方法必須使用在同步代碼塊或同步方法中。
2)wait()notify()notifyAll()三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器。不然,會出現IllegalMonitorStateException異常。
3)wait()notify()notifyAll()三個方法是定義在java.lang.Object類中。this

sleep() 和 wait()的異同?

相同點:
1) 一旦執行方法,均可以使得當前的線程進入阻塞狀態。

不一樣點:
1)兩個方法聲明的位置不一樣:Thread類中聲明sleep() , Object類中聲明wait()
2)調用的要求不一樣:sleep()能夠在任何須要的場景下調用。 wait()必須使用在同步代碼塊或同步方法中。
3)關因而否釋放同步監視器:若是兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。

class Number {
    private int number = 1;
    private Object obj = new Object();
    public Number(int number) {
        this.number = number;
    }
    public int getNumber() {
        return number;
    }
    // 累加數字
    public void printer(int number){
        synchronized (obj) {
            obj.notify();
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "添加成功。當前數字:" + this.number);
                try {
                    Thread.currentThread().sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.number += number;
                try {
                    //使得調用以下wait() 方法的線程進入阻塞狀態
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class Preson implements Runnable {
    private Number number;

    public Preson(Number number) {
        this.number = number;
    }
    @Override
    public void run() {
        while (true) {
            if (this.number.getNumber() <= 100) {
                number.printer(1);
            }else {
                break;
            }
        }
    }
}
public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number(1);
        Preson preson = new Preson(number);
        Thread thread = new Thread(preson);
        thread.setName("甲");
        thread.start();
        Thread thread2 = new Thread(preson);
        thread2.setName("乙");
        thread2.start();
    }
}

JDK5.0新增線程建立方式

新增方式一:實現Callable接口

1)與使用Runnable相比, Callable功能更強大些。
2)相比run()方法,能夠有返回值 。
3)方法能夠拋出異常。
4)支持泛型的返回值 。
5)須要藉助FutureTask類,好比獲取返回結果。

class NumberThread implements Callable{
    /** 2.實現Call方法,將此線程須要執行的操做聲明在此方法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.建立Callable接口實現類的對象 */
        NumberThread numberThread = new NumberThread();
        /** 4.將此Callable接口實現類的對象做爲參數傳遞到FutureTask構造器中,建立FutureTaskd的對象 */
        FutureTask futureTask = new FutureTask(numberThread);
        /** 5.將此FutureTask的對象做爲參數傳遞到Thread類的構造器中,建立Thread對象,並調用start()方法 */
        new Thread(futureTask).start();
        try {
            /** 6.獲取Callable中call() 方法的返回值 */
            /** get()返回值即爲FutureTask構造器參數Callable實現類重寫的call()的返回值 */
            Object sum = futureTask.get();
            System.out.println("總和爲:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

如何理解實現Callable接口的方式建立多線程比實現Runnable接口建立多線程方式強大?

call()能夠有返回值。
call()能夠拋出異常,被外面的操做捕獲,獲取異常的信息。
Callable是支持泛型的

新增方式二:使用線程池

背景:常常建立和銷燬、使用量特別大的資源,好比並髮狀況下的線程, 對性能影響很大。

思路:提早建立好多個線程,放入線程池中,使用時直接獲取,使用完 放回池中。能夠避免頻繁建立銷燬、實現重複利用。相似生活中的公共交 通工具。

好處
1)提升響應速度(減小了建立新線程的時間)。
2)下降資源消耗(重複利用線程池中線程,不須要每次都建立)。
3)便於線程管理 :
corePoolSize:核心池的大小 。
maximumPoolSize:最大線程數 。
keepAliveTime:線程沒有任務時最多保持多長時間後會終止。

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);
            }
        }
    }
}
class NumberThread2 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.提供指定線程數量的線程池 */
        ExecutorService service = Executors.newFixedThreadPool(10);
        /** 設置線程池的屬性 */
        /** 因爲service是ExecutorService接口,須要使用service的實現類對他進行屬性設置 */
        System.out.println(service.getClass());//查看service的實現類
        /** 強轉方式一: */
        ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
//        service1.setCorePoolSize(15);
        /** 強轉方式二: */
//        ((ThreadPoolExecutor) service).setKeepAliveTime(1000);
        /** 2.執行指定的線程的操做。須要提供實現Runnable接口或Callable接口實現類的對象 */
        service.execute(new NumberThread1());//適合使用於Runnable
        service.execute(new NumberThread2());//適合使用於Runnable
        service.shutdown();
    }
}
相關文章
相關標籤/搜索