多線程基本概念

 

目錄

什麼是叫一個進程? 什麼叫一個線程?

進程      

線程

如何銷燬一個線程

請你說說啓動線程的三種方式?

繼承 Thread 類

實現 Runnable 接口

實現 Callable 接口

多線程API

Daemon

sleep()

yield()

interrupted() 中斷

Executor 的中斷操作

 多線程同步鎖、異步鎖

線程之間的協作

join()

wait() notify() notifyAll()

await() signal() signalAll()

J.U.C - AQS

線程池

JMH與Disruptor



什麼是叫一個進程? 什麼叫一個線程?

進程      

        做一個簡單的解釋,你的硬盤上有一個簡單的程序,這個程序叫QQ.exe,這是一個程序,這個程序是一個靜態的概念,它被扔在硬盤上也沒人理他,但是當你雙擊它,彈出一個界面輸入賬號密碼登錄進去了,OK,這個時候叫做一個進程。進程相對於程序來說它是一個動態的概念。

線程

        作爲一個進程裏面最小的執行單元它就叫一個線程,用簡單的話講一個程序裏不同的執行路徑就叫做一個線程。

如何銷燬一個線程

在java中,執行線程 Java 是沒有辦法銷燬它的

但是 當 Thread.isAlive() 返回 false 時,實際底層的 Thread 已經被銷燬了

 

請你說說啓動線程的三種方式?

1.new Thread().start();

2.new Thread(Runnable).start()

3.通過線程池也可以啓動 一個新的線程 Executors.newCachedThreadPool()或者FutureTask + Callable

繼承 Thread 類

同樣也是需要實現 run() 方法,因爲 Thread 類也實現了 Runable 接口。

當調用 start() 方法啓動一個線程時,虛擬機會將該線程放入就緒隊列中等待被調度,當一個線程被調度時會執行該線程的 run() 方法。

public class MyThread extends Thread {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyThread mt = new MyThread();
    mt.start();
}

實現 Runnable 接口

需要實現 run() 方法。

通過 Thread 調用 start() 方法來啓動線程。

public class MyRunnable implements Runnable {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    thread.start();
}

實現 Callable 接口

與 Runnable 相比,Callable 可以有返回值,返回值通過 FutureTask 進行封裝。

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

多線程API

Daemon

守護線程是程序運行時在後臺提供服務的線程,不屬於程序中不可或缺的部分。

當所有非守護線程結束時,程序也就終止,同時會殺死所有守護線程。

main() 屬於非守護線程。

在線程啓動之前使用 setDaemon() 方法可以將一個線程設置爲守護線程。

public static void main(String[] args) {
    Thread thread = new Thread(new MyRunnable());
    thread.setDaemon(true);
}

sleep()

Thread.sleep(millisec) 方法會休眠當前正在執行的線程,millisec 單位爲毫秒。

sleep() 可能會拋出 InterruptedException,因爲異常不能跨線程傳播回 main() 中,因此必須在本地進行處理。線程中拋出的其它異常也同樣需要在本地進行處理。

public void run() {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

yield()

對靜態方法 Thread.yield() 的調用聲明瞭當前線程已經完成了生命週期中最重要的部分,可以切換給其它線程來執行。該方法只是對線程調度器的一個建議,而且也只是建議具有相同優先級的其它線程可以運行。

public void run() { 
    Thread.yield();
}

interrupted() 中斷

一個線程執行完畢之後會自動結束,如果在運行過程中發生異常也會提前結束。

InterruptedException

通過調用一個線程的 interrupt() 來中斷該線程,如果該線程處於阻塞、限期等待或者無限期等待狀態,那麼就會拋出 InterruptedException,從而提前結束該線程。但是不能中斷 I/O 阻塞和 synchronized 鎖阻塞。

對於以下代碼,在 main() 中啓動一個線程之後再中斷它,由於線程中調用了 Thread.sleep() 方法,因此會拋出一個 InterruptedException,從而提前結束線程,不執行之後的語句。

public class InterruptExample {

    private static class MyThread1 extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
                System.out.println("Thread run");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new MyThread1();
    thread1.start();
    thread1.interrupt();
    System.out.println("Main run");
}

Main run
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at InterruptExample.lambda$main$0(InterruptExample.java:5)
    at InterruptExample$$Lambda$1/713338599.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:745)

interrupted()

如果一個線程的 run() 方法執行一個無限循環,並且沒有執行 sleep() 等會拋出 InterruptedException 的操作,那麼調用線程的 interrupt() 方法就無法使線程提前結束。

但是調用 interrupt() 方法會設置線程的中斷標記,此時調用 interrupted() 方法會返回 true。因此可以在循環體中使用 interrupted() 方法來判斷線程是否處於中斷狀態,從而提前結束線程

public class InterruptExample {

    private static class MyThread2 extends Thread {
        @Override
        public void run() {
            while (!interrupted()) {
                // ..
            }
            System.out.println("Thread end");
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    Thread thread2 = new MyThread2();
    thread2.start();
    thread2.interrupt();
}

Thread end

Executor 的中斷操作

調用 Executor 的 shutdown() 方法會等待線程都執行完畢之後再關閉,但是如果調用的是 shutdownNow() 方法,則相當於調用每個線程的 interrupt() 方法。

以下使用 Lambda 創建線程,相當於創建了一個匿名內部線程。

public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    executorService.execute(() -> {
        try {
            Thread.sleep(2000);
            System.out.println("Thread run");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    executorService.shutdownNow();
    System.out.println("Main run");
}
Main run
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at ExecutorInterruptExample.lambda$main$0(ExecutorInterruptExample.java:9)
    at ExecutorInterruptExample$$Lambda$1/1160460865.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

如果只想中斷 Executor 中的一個線程,可以通過使用 submit() 方法來提交一個線程,它會返回一個 Future<?> 對象,通過調用該對象的 cancel(true) 方法就可以中斷線程。

Future<?> future = executorService.submit(() -> {
    // ..
});
future.cancel(true);

 多線程同步鎖、異步鎖

一、同步鎖:當在一個java虛擬機多個線程操作一個變量的時候就會出現線程安全問題,這個時候就會用到同步鎖。

同步鎖的解決方式:

synchronized、ReentrantLock等

 

二、異步鎖:就是多個java 虛擬機或者說是服務器,操作同一個變量是,會出現線程安全問題,使用需要使用異步鎖來處理。

1)數據庫  樂觀鎖 悲觀鎖 唯一標示  不推薦使用,容易出現鎖表,出現死鎖。

2)Redis 分佈式鎖: 就是設置一個flag標識,當一個服務拿到鎖以後立即把對應的標識設置爲false  用完後釋放鎖,並把標識修改爲true。(圖片來源於網上)

 

3)使用dubbo  zookeeper (共享鎖,排它鎖),這裏就根據自己的情況,共享鎖還是會出現阻塞的情況,排它鎖就是會生成很多臨時的節點,誰先獲取最小的序號標識誰就先獲取到鎖。

 

線程之間的協作

當多個線程可以一起工作去解決某個問題時,如果某些部分必須在其它部分之前完成,那麼就需要對線程進行協調。

join()

在線程中調用另一個線程的 join() 方法,會將當前線程掛起,而不是忙等待,直到目標線程結束。

對於以下代碼,雖然 b 線程先啓動,但是因爲在 b 線程中調用了 a 線程的 join() 方法,b 線程會等待 a 線程結束才繼續執行,因此最後能夠保證 a 線程的輸出先於 b 線程的輸出。

public class JoinExample {

    private class A extends Thread {
        @Override
        public void run() {
            System.out.println("A");
        }
    }

    private class B extends Thread {

        private A a;

        B(A a) {
            this.a = a;
        }

        @Override
        public void run() {
            try {
                a.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B");
        }
    }

    public void test() {
        A a = new A();
        B b = new B(a);
        b.start();
        a.start();
    }
}
public static void main(String[] args) {
    JoinExample example = new JoinExample();
    example.test();
}

wait() notify() notifyAll()

調用 wait() 使得線程等待某個條件滿足,線程在等待時會被掛起,當其他線程的運行使得這個條件滿足時,其它線程會調用 notify() 或者 notifyAll() 來喚醒掛起的線程。

它們都屬於 Object 的一部分,而不屬於 Thread。

只能用在同步方法或者同步控制塊中使用,否則會在運行時拋出 IllegalMonitorStateException。

使用 wait() 掛起期間,線程會釋放鎖。這是因爲,如果沒有釋放鎖,那麼其它線程就無法進入對象的同步方法或者同步控制塊中,那麼就無法執行 notify() 或者 notifyAll() 來喚醒掛起的線程,造成死鎖。

public class WaitNotifyExample {

    public synchronized void before() {
        System.out.println("before");
        notifyAll();
    }

    public synchronized void after() {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after");
    }
}
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    WaitNotifyExample example = new WaitNotifyExample();
    executorService.execute(() -> example.after());
    executorService.execute(() -> example.before());
}

wait() 和 sleep() 的區別

wait() 是 Object 的方法,而 sleep() 是 Thread 的靜態方法;

wait() 會釋放鎖,sleep() 不會。

wait() notify 必需學會使用,面試過程中會經常問道:java多線程交替打印數字和字母 

await() signal() signalAll()

java.util.concurrent 類庫中提供了 Condition 類來實現線程之間的協調,可以在 Condition 上調用 await() 方法使線程等待,其它線程調用 signal() 或 signalAll() 方法喚醒等待的線程。

相比於 wait() 這種等待方式,await() 可以指定等待的條件,因此更加靈活。

使用 Lock 來獲取一個 Condition 對象。

public class AwaitSignalExample {

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void before() {
        lock.lock();
        try {
            System.out.println("before");
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void after() {
        lock.lock();
        try {
            condition.await();
            System.out.println("after");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    AwaitSignalExample example = new AwaitSignalExample();
    executorService.execute(() -> example.after());
    executorService.execute(() -> example.before());
}

 

J.U.C - AQS

java.util.concurrent(J.U.C)[JAVA併發包]大大提高了併發性能,AQS 被認爲是 J.U.C 的核心。

CAS

AQS

Synchronized

CountDownLatch

用來控制一個或者多個線程等待多個線程。

維護了一個計數器 cnt,每次調用 countDown() 方法會讓計數器的值減 1,減到 0 的時候,那些因爲調用 await() 方法而在等待的線程就會被喚醒。

CyclicBarrier

 

Semaphore

 

併發容器
Collection
1.List
    CopyOnWriteList
    Vector Stack
    ArrayList
    LinkedList
2.Set 
    HashSet        LinkedHashSet
    SorteSet    TreeSet
    EnumSet
    CopyOnWriteArraySet
    ConcurrentSkipListSet
3.Queue
    Deque
        ArrayDeque
        BlockingDeque    LinkedBlockingDeque
    BlockingQueue
        ArrayBlockingQueue
        PrioityBlockingQueue
        LinkedBlockingQueue
        TransferQueue        LinkedTransferQueue
        SynchronousQueue
    PriorityQueue
    ConcurrentLinkedQueue
    DelayQueue

Map
1.HashMap  LinkedHashMap
2.TreeMap 
3.IdentityHashMap
4.ConcurrentHashMap
5.ConcurrentSkipListMap

 

線程池

1.ThreadPoolExecutor

    多個線程共享同一個任務隊列

2.ForkJoinPool

    每個線程有自己的任務隊列

    -分解總的任務

    -用很少的線程可以執行很多的任務(子任務)TPE做不到先執行子任務

    -CPU密集型

JMH與Disruptor

JMH -java Microbenchmark Harness
微基準測試,他是測的某一個方法的性能到底是好或者不好,換了方法的實現之後他的性能到底好還是
不好。

 

 

 

 

 

 

 

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