線程,JVM鎖整理

一、線程的等待和通知java

首先wait()和notify(),notifyAll()方法必定是通常對象方法,他們並不屬於線程對象方法,必定是跟synchronized(監視器鎖)結伴出現的。wait()方法執行時會釋放獲取的監視器鎖,線程進入休眠等待狀態。而notify()執行時,會隨機喚醒一個等待狀態的線程,並從新獲取監視器鎖,而後再繼續執行。notifyAll()方法是喚醒全部的相同對象的等待線程,再去競爭獲取監視器鎖。多線程

public class SimpleWN {
    final static Object object = new Object();
    public static class T1 implements Runnable {
        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + ":T1 start!");
                try {
                    System.out.println(System.currentTimeMillis() + ":T1 wait for object");
                    object.wait();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ":T1 end!");
            }
        }
    }
    public static class T2 implements Runnable {
        public void run() {
            synchronized (object) {
                try {
                    //讓線程T1先執行,本身先睡2秒
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread");
                object.notify();
                System.out.println(System.currentTimeMillis() + ":T2 end!");

            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new T1());
        Thread t2 = new Thread(new T2());
        t1.start();
        t2.start();
    }
}

執行結果dom

1538646195634:T1 start!
1538646195635:T1 wait for object
1538646197635:T2 start! notify one thread
1538646197635:T2 end!
1538646197635:T1 end!ide

若是註釋掉Thread.sleep(2000)代碼塊,則可能T2線程先執行,T1後執行,整個程序進入堵塞狀態,沒法喚醒!工具

二、等待線程結束性能

join()方法是執行一個wait()方法做用於當前線程,進行等待,若是當前線程是主線程則會使主線程等待。ui

public class JoinMain {
    public volatile static int i = 0;
    public static class AddThread implements Runnable {
        @Override
        public void run() {
            for (i = 0;i < 10000000;i++);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread at = new Thread(new AddThread());
        at.start();
        at.join();
        System.out.println(i);
    }
}

執行結果this

10000000spa

若是註釋掉at.join(),主線程輸出值爲0,主線程執行打印時,線程at還未執行。線程

三、守護線程

守護線程的做用就是全部用戶線程(包含主線程)都結束了,該線程也天然結束了。

public class FIndReady {
    private static int num;
    private static boolean ready;
    private static class ReaderThread extends Thread {
        public void run() {
            while (ready) {
                System.out.println(num);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new ReaderThread();
        //設置守護線程
        t.setDaemon(true);
        t.start();
        num = 45;
        ready = true;
    }
}

這段代碼若是不設置守護線程t.setDaemon(true),則會無限打印45,但設置了守護線程後,主線程結束後,就會中止打印45.

再來講說volatile,volatile原本是設置寄存器到內存的複製到全部線程可見的,不過寄存器到內存的複製以如今的電腦性能實在是太快了,因此我以爲volatile的意義已經不大了。

不過即使是設置了守護線程,若是加入了join()方法,主線程依然會等待守護線程執行完,這樣就會無限打印45.

public class FIndReady {
    private static volatile int num;
    private static boolean ready;
    private static class ReaderThread extends Thread {
        public void run() {
            while (ready) {
                System.out.println(num);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new ReaderThread();
        //設置守護線程
        t.setDaemon(true);
        t.start();
        num = 45;
        ready = true;
        t.join();
    }
}

四、重入鎖

重入鎖指的是當一個線程申請得到一次加鎖以後,當釋放鎖後再次獲取該鎖將無需再次申請,節省開銷。

用加鎖來實現多線程累加

public class VolatileQuestion {
    private static volatile Integer i = 0;
    private static Lock lock = new ReentrantLock();

    public static class PlusTask implements Runnable {

        public void run() {
            for (int k = 0; k < 10000; k++) {
                add();
            }
        }

        private static void add() {
            lock.lock();
            try {
                i++;
            }finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for (int j = 0;j < 10;j++) {
            threads[j] = new Thread(new PlusTask());
            threads[j].start();
        }
        for (int j = 0;j< 10;j++) {
            threads[j].join();
        }
        System.out.println(i);
    }
}

固然還有兩種方式能夠達到一樣的效果

public class VolatileQuestion {
    private static volatile Integer i = 0;
//    private static Lock lock = new ReentrantLock();

    public static class PlusTask implements Runnable {

        public void run() {
            for (int k = 0; k < 10000; k++) {
                add();
            }
        }

        private static synchronized void add() {
            i++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for (int j = 0;j < 10;j++) {
            threads[j] = new Thread(new PlusTask());
            threads[j].start();
        }
        for (int j = 0;j< 10;j++) {
            threads[j].join();
        }
        System.out.println(i);
    }
}

原子類無鎖

public class VolatileQuestion {
    private static AtomicInteger i = new AtomicInteger(0);
//    private static Lock lock = new ReentrantLock();

    public static class PlusTask implements Runnable {

        public void run() {
            for (int k = 0; k < 10000; k++) {
                i.getAndIncrement();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for (int j = 0;j < 10;j++) {
            threads[j] = new Thread(new PlusTask());
            threads[j].start();
        }
        for (int j = 0;j< 10;j++) {
            threads[j].join();
        }
        System.out.println(i);
    }
}

運行結果都同樣

100000

五、優先中斷

優先中斷並非以獲取鎖爲目的,而是以優先獲取中斷爲目標

把一個死鎖的例子逐步改爲非死鎖

public class InLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    private int lock;
    public InLock(int lock) {
        this.lock = lock;
    }
    public void run() {
        try {
            if (lock == 1) {
                lock1.lock();
//                lock1.lockInterruptibly();
//                lock1.tryLock();
                try {
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getId() + "獲取鎖");
                }catch (InterruptedException e) {}
                lock2.lock();
//                lock2.lockInterruptibly();
//                lock2.tryLock();
            }else {
                lock2.lock();
//                lock2.lockInterruptibly();
//                lock2.tryLock();
                try {
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getId() + "獲取鎖");
                }catch (InterruptedException e) {}
                lock1.lock();
//                lock1.lockInterruptibly();
//                lock1.tryLock();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //lock1是否獲取鎖
            if (lock1.isHeldByCurrentThread()) {
                lock1.unlock();
            }
            if (lock2.isHeldByCurrentThread()) {
                lock2.unlock();
            }
            System.out.println(Thread.currentThread().getId() + ":線程退出");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        InLock r1 = new InLock(1);
        InLock r2 = new InLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
//        Thread.sleep(1000);
        //t2.interrupt();
    }
}

運行結果

12獲取鎖
13獲取鎖

這是一個死鎖,t1,t2線程都分別獲取了lock1,lock2的鎖以後在未解鎖的狀況下,去獲取對方的鎖,誰也得不到對方的鎖而出現死鎖,程序堵塞。

程序修改爲中斷t2,t1能夠獲取鎖,程序執行完畢,拋出一箇中斷異常

public class InLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    private int lock;
    public InLock(int lock) {
        this.lock = lock;
    }
    public void run() {
        try {
            if (lock == 1) {
//                lock1.lock();
                lock1.lockInterruptibly();
//                lock1.tryLock();
                try {
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getId() + "獲取鎖");
                }catch (InterruptedException e) {}
//                lock2.lock();
                lock2.lockInterruptibly();
//                lock2.tryLock();
            }else {
//                lock2.lock();
                lock2.lockInterruptibly();
//                lock2.tryLock();
                try {
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getId() + "獲取鎖");
                }catch (InterruptedException e) {}
//                lock1.lock();
                lock1.lockInterruptibly();
//                lock1.tryLock();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //lock1是否獲取鎖
            if (lock1.isHeldByCurrentThread()) {
                lock1.unlock();
            }
            if (lock2.isHeldByCurrentThread()) {
                lock2.unlock();
            }
            System.out.println(Thread.currentThread().getId() + ":線程退出");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        InLock r1 = new InLock(1);
        InLock r2 = new InLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        t2.interrupt();
    }
}

lock2.lockInterruptibly()會優先響應t2.interrupt()發生中斷,拋出中斷異常,lock2自動解鎖,運行結果

12獲取鎖
13獲取鎖
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
    at com.guanjian.InLock.run(InLock.java:38)
    at java.lang.Thread.run(Thread.java:745)
13:線程退出
12:線程退出

再修改爲嘗試獲取鎖

public class InLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    private int lock;
    public InLock(int lock) {
        this.lock = lock;
    }
    public void run() {
        try {
            if (lock == 1) {
//                lock1.lock();
//                lock1.lockInterruptibly();
                lock1.tryLock();
                try {
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getId() + "獲取鎖");
                }catch (InterruptedException e) {}
//                lock2.lock();
//                lock2.lockInterruptibly();
                if (lock2.tryLock()) {
                    System.out.println("1獲取成功");
                }
            }else {
//                lock2.lock();
//                lock2.lockInterruptibly();
                lock2.tryLock();
                try {
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread().getId() + "獲取鎖");
                }catch (InterruptedException e) {}
//                lock1.lock();
//                lock1.lockInterruptibly();
                if (lock1.tryLock()) {
                    System.out.println("2獲取成功");
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //lock1是否獲取鎖
            if (lock1.isHeldByCurrentThread()) {
                System.out.println(Thread.currentThread().getId() + "解鎖");
                lock1.unlock();
            }
            if (lock2.isHeldByCurrentThread()) {
                System.out.println(Thread.currentThread().getId() + "解鎖");
                lock2.unlock();
            }
            System.out.println(Thread.currentThread().getId() + ":線程退出");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        InLock r1 = new InLock(1);
        InLock r2 = new InLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
//        Thread.sleep(1000);
//        t2.interrupt();
    }
}

tryLock()在拿不到鎖的時候能夠立刻返回false,不會堵塞,能夠大大減小死鎖的可能性,運行結果以下

12獲取鎖
13獲取鎖
12解鎖
12:線程退出
13解鎖
13:線程退出

咱們能夠看到他們都沒有拿到對方的鎖,可是沒有死鎖堵塞。

tryLock()能夠設等待時間,等待時間後再返回

public class TimeLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public void run() {
        try {
            if (lock.tryLock(7, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(6000);
            }else {
                System.out.println("get lock failed");
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TimeLock tl = new TimeLock();
        Thread t1 = new Thread(tl);
        Thread t2 = new Thread(tl);
        t1.start();
        t2.start();

    }
}

一個線程拿到鎖之後睡眠6秒解鎖,另外一個線程等待7秒拿鎖,結果2個線程都拿到了鎖

運行結果

Thread-0
Thread-1

若是等待時間小於睡眠時間,則拿不到鎖

public class TimeLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public void run() {
        try {
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName());
                Thread.sleep(6000);
            }else {
                System.out.println("get lock failed");
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TimeLock tl = new TimeLock();
        Thread t1 = new Thread(tl);
        Thread t2 = new Thread(tl);
        t1.start();
        t2.start();

    }
}

運行結果

Thread-0
get lock failed

六、公平鎖

讓全部參與的線程都可以依次公平的獲取鎖,成本高,性能底下

public class FairLock implements Runnable {
    //設置公平鎖
    public static ReentrantLock lock = new ReentrantLock(true);
    public void run() {
        while (true) {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "得到鎖");
            }finally {
                lock.unlock();
                //break;
            }
        }
    }

    public static void main(String[] args) {
        FairLock r1 = new FairLock();
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r1);
        t1.start();
        t2.start();
    }
}

運行結果(截取部分)

Thread-0得到鎖
Thread-1得到鎖
Thread-0得到鎖
Thread-1得到鎖
Thread-0得到鎖
Thread-1得到鎖
Thread-0得到鎖
Thread-1得到鎖
Thread-0得到鎖
Thread-1得到鎖
Thread-0得到鎖

從結果能夠看出,兩個線程之間老是交替獲取鎖。

取消公平鎖

public class FairLock implements Runnable {
    //設置公平鎖
    public static ReentrantLock lock = new ReentrantLock();
    public void run() {
        while (true) {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "得到鎖");
            }finally {
                lock.unlock();
                //break;
            }
        }
    }

    public static void main(String[] args) {
        FairLock r1 = new FairLock();
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r1);
        t1.start();
        t2.start();
    }
}

運行結果(截取部分)

Thread-0得到鎖
Thread-0得到鎖
Thread-0得到鎖
Thread-0得到鎖
Thread-0得到鎖
Thread-0得到鎖
Thread-0得到鎖
Thread-0得到鎖
Thread-0得到鎖
Thread-1得到鎖
Thread-1得到鎖
Thread-1得到鎖
Thread-1得到鎖
Thread-1得到鎖

由結果能夠看出,獲取過一次鎖的線程老是更容易獲取下一次鎖,是非公平的。

七、與重入鎖結伴的等待與通知

await()方法,singal()方法與singalAll()方法相似於Object的wait(),notify(),notifyAll()方法。

public class ReenterLockCondition implements Runnable{
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();
    public void run() {
        try {
            lock.lock();
            condition.await();
            System.out.println("Thread is going on");
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReenterLockCondition tl = new ReenterLockCondition();
        Thread t1 = new Thread(tl);
        t1.start();
        System.out.println("喚醒前先嗨2秒");
        Thread.sleep(2000);
        lock.lock();
        condition.signal();
        lock.unlock();
    }
}

運行結果

喚醒前先嗨2秒
Thread is going on

八、信號量

信號量的理解就是若是有不少線程須要執行,而每次僅容許幾個線程執行,只有其中有線程執行完畢才容許後面的線程進入執行,但總執行線程數不能多於限制數。

public class SemaphoreDemo {
    private Semaphore smp = new Semaphore(3,true); //公平策略
    private Random rnd = new Random();

    class Task implements Runnable{
        private String id;
        Task(String id){
            this.id = id;
        }

        public void run(){
            try {
                //阻塞,等待信號
                smp.acquire();
                //smp.acquire(int permits);//使用有參數方法可使用permits個許可
                System.out.println("Thread " + id + " is working");
                System.out.println("在等待的線程數目:"+ smp.getQueueLength());
                work();
                System.out.println("Thread " + id + " is over");
            } catch (InterruptedException e) {
            }
            finally
            {
                //釋放信號
                smp.release();
            }
        }

        public void work() {//僞裝在工做,實際在睡覺
            int worktime = rnd.nextInt(1000);
            System.out.println("Thread " + id + " worktime  is "+ worktime);
            try {
                Thread.sleep(worktime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args){
        SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
        ExecutorService se = Executors.newCachedThreadPool();
        se.submit(semaphoreDemo.new Task("a"));
        se.submit(semaphoreDemo.new Task("b"));
        se.submit(semaphoreDemo.new Task("c"));
        se.submit(semaphoreDemo.new Task("d"));
        se.submit(semaphoreDemo.new Task("e"));
        se.submit(semaphoreDemo.new Task("f"));
        se.shutdown();
    }
}

運行結果

Thread b is working
在等待的線程數目:0
Thread b worktime  is 860
Thread a is working
Thread c is working
在等待的線程數目:1
在等待的線程數目:1
Thread a worktime  is 445
Thread c worktime  is 621
Thread a is over
Thread d is working
在等待的線程數目:2
Thread d worktime  is 237
Thread c is over
Thread e is working
在等待的線程數目:1
Thread e worktime  is 552
Thread d is over
Thread f is working
在等待的線程數目:0
Thread f worktime  is 675
Thread b is over
Thread e is over
Thread f is over

結果解讀:a,b,c三個線程進入工做,其餘線程沒法進入,a線程執行完,空出一個線程位,d線程進入工做,c線程執行完,又空出一個線程位,e線程進入工做,d線程執行完,f線程進入工做,b線程執行完,e線程執行完,f線程執行完。

九、讀寫鎖

當全部的寫鎖釋放後,全部的讀鎖將並行執行,不然讀鎖和寫鎖都將進行一一鎖定。

public class ReadWriteLockDemo {
    private static Lock lock = new ReentrantLock();
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = reentrantReadWriteLock.readLock();
    private static Lock writeLock = reentrantReadWriteLock.writeLock();
    private volatile int value;
    public Object handleRead(Lock lock) throws InterruptedException {
        try {
            lock.lock();
            Thread.sleep(1000);
            return "讀" + Thread.currentThread().getName() + " " + value;
        }finally {
            lock.unlock();
        }
    }
    public void handleWrite(Lock lock,int index) throws InterruptedException {
        try {
            lock.lock();
            Thread.sleep(1000);
            value = index;
            System.out.println("寫" + Thread.currentThread().getName() +" " + value);
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final ReadWriteLockDemo demo = new ReadWriteLockDemo();
        Runnable readRunnable = new Runnable() {
            public void run() {
                try {
                    System.out.println(demo.handleRead(readLock));
//                    System.out.println(demo.handleRead(lock));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable writeRunnable = new Runnable() {
            public void run() {
                try {
                    demo.handleWrite(writeLock,new Random().nextInt(100));
//                    demo.handleWrite(lock,new Random(100).nextInt());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0;i < 2;i++) {
            new Thread(writeRunnable).start();
        }
        for (int i = 0;i < 18;i++) {
            new Thread(readRunnable).start();
        }
    }
}

運行結果

讀Thread-2 0
讀Thread-3 0
讀Thread-4 0
寫Thread-0 82
寫Thread-1 5
讀Thread-5 5
讀Thread-10 5
讀Thread-9 5
讀Thread-8 5
讀Thread-6 5
讀Thread-7 5
讀Thread-13 5
讀Thread-15 5
讀Thread-18 5
讀Thread-16 5
讀Thread-12 5
讀Thread-19 5
讀Thread-11 5
讀Thread-14 5
讀Thread-17 5

運行結果解讀:在讀Thread-5 5以前,每秒出一個結果,從讀Thread-5 5開始到讀Thread-17 5沒有1秒停頓,並行同時執行,說明在讀Thread-17 5以後沒有鎖競爭。

若是把讀寫鎖換成可重入鎖

public class ReadWriteLockDemo {
    private static Lock lock = new ReentrantLock();
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = reentrantReadWriteLock.readLock();
    private static Lock writeLock = reentrantReadWriteLock.writeLock();
    private volatile int value;
    public Object handleRead(Lock lock) throws InterruptedException {
        try {
            lock.lock();
            Thread.sleep(1000);
            return "讀" + Thread.currentThread().getName() + " " + value;
        }finally {
            lock.unlock();
        }
    }
    public void handleWrite(Lock lock,int index) throws InterruptedException {
        try {
            lock.lock();
            Thread.sleep(1000);
            value = index;
            System.out.println("寫" + Thread.currentThread().getName() +" " + value);
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final ReadWriteLockDemo demo = new ReadWriteLockDemo();
        Runnable readRunnable = new Runnable() {
            public void run() {
                try {
//                    System.out.println(demo.handleRead(readLock));
                    System.out.println(demo.handleRead(lock));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable writeRunnable = new Runnable() {
            public void run() {
                try {
//                    demo.handleWrite(writeLock,new Random().nextInt(100));
                    demo.handleWrite(lock,new Random().nextInt(100));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int i = 0;i < 2;i++) {
            new Thread(writeRunnable).start();
        }
        for (int i = 0;i < 18;i++) {
            new Thread(readRunnable).start();
        }
    }
}

雖然運行結果同樣,可是結果是1秒出一個,說明次次都是被鎖鎖了1秒。

十、倒計時器

倒計時器的做用是讓參與的線程挨個執行,其餘線程等待,到計時器計時完畢,其餘線程才能夠繼續執行。

public class CountDownLatchDemo implements Runnable {
    //設定計時器
    static final CountDownLatch end = new CountDownLatch(10);
    private static AtomicInteger i = new AtomicInteger(10);
    public void run() {
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
            i.getAndDecrement();
            System.out.println("check complete,剩餘次數" + i.toString());
            //計時器中的一個線程完成,計時器-1
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        CountDownLatchDemo demo = new CountDownLatchDemo();
        for (int i = 0;i < 10;i++) {
            exec.submit(demo);
        }
        //讓主線程等待計時器倒數完成才容許繼續執行
        end.await();
        System.out.println("Fire!");
        exec.shutdown();
    }
}

運行結果

check complete,剩餘次數9
check complete,剩餘次數8
check complete,剩餘次數7
check complete,剩餘次數6
check complete,剩餘次數5
check complete,剩餘次數4
check complete,剩餘次數3
check complete,剩餘次數2
check complete,剩餘次數1
check complete,剩餘次數0
Fire!

若是咱們把static final CountDownLatch end = new CountDownLatch(10);改爲小於10的數,好比3

public class CountDownLatchDemo implements Runnable {
    //設定計時器
    static final CountDownLatch end = new CountDownLatch(3);
    private static AtomicInteger i = new AtomicInteger(10);
    public void run() {
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println("check complete,剩餘次數" + i.decrementAndGet());
            //計時器中的一個線程完成,計時器-1
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        CountDownLatchDemo demo = new CountDownLatchDemo();
        for (int i = 0;i < 10;i++) {
            exec.submit(demo);
        }
        //讓主線程等待計時器倒數完成才容許繼續執行
        end.await();
        System.out.println("Fire!");
        exec.shutdown();
    }
}

這裏面咱們作了一點小小的調整,就是原子類打印System.out.println("check complete,剩餘次數" + i.decrementAndGet())而並非i.getAndDecrement();   System.out.println("check complete,剩餘次數" + i.toString());這個改動是爲了讓打印不會打印出相同的數,不然即使是原子類,這也是兩步操做,依然會打印出相同的數,緣由能夠本身思考。

運行結果

check complete,剩餘次數9
check complete,剩餘次數7
check complete,剩餘次數8
Fire!
check complete,剩餘次數6
check complete,剩餘次數4
check complete,剩餘次數3
check complete,剩餘次數5
check complete,剩餘次數2
check complete,剩餘次數1
check complete,剩餘次數0

從結果能夠看出,線程demo只並行執行了3次,主線程就繼續執行了。而剩餘次數混亂說明是並行執行,而不是依次執行。

本人以前博客《靜態變量的多線程同步問題》有一個countDown()方法和await()方法調換位置的樣例,目的是爲了讓全部的任務線程等待(此時不一樣的任務線程已經生成),直到主線程countDown()的時候,任務線程才能夠繼續執行,從而不會出現單獨線程搶佔,讓不一樣的線程都可以產生訂單號。有興趣的朋友能夠查看以前的該博文。

十一、循環柵欄

循環柵欄跟倒計時器最大的不一樣就是倒計時器當計數減到0的時候,開始容許其餘線程執行,倒計時器不可再使用,而循環柵欄則不管多少線程執行,只要到了設置的限制數,就會執行綁定的線程方法,能夠循環使用。若是到不了設置的限制數就會進行堵塞。

總共10個士兵,每5個士兵集合,進行一次報告共報告2次,所有集合後,每5個士兵執行任務進行一次報告共報告2次。

public class CyclicBarrierDemo {
    //任務線程
    public static class Soldier implements Runnable {
        private String soldier;
        private final CyclicBarrier cyclic;

        public Soldier(CyclicBarrier cyclic,String soldierName) {
            this.cyclic = cyclic;
            this.soldier = soldierName;
        }

        public void run() {
            try {
                //等待全部士兵到齊
                cyclic.await();
                doWork();
                //等待全部士兵完成任務
                cyclic.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
        private void doWork() {
            try {
                Thread.sleep(new Random().nextInt(1000));
                BarrierRun.flag = true;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soldier + ":任務完成");
        }
    }
    //跟循環柵欄綁定的線程
    public static class BarrierRun implements Runnable {
        int N;
        public static volatile boolean flag;
        public BarrierRun(int N) {
            this.N = N;
        }

        public void run() {
            if (flag) {
                System.out.println("司令:[士兵" + N + "個,任務完成!]");
            }else {
                System.out.println("司令:[士兵" + N + "個,集合完畢!]");
            }
        }
    }

    public static void main(String[] args) {
        final int N = 5;
        Thread[] allSoldier = new Thread[10];
        BarrierRun.flag = false;
        //定義循環柵欄
        CyclicBarrier cyclic = new CyclicBarrier(N,new BarrierRun(N));
        System.out.println("集合隊伍");
        for (int i = 0;i < 10;i++) {
            System.out.println("士兵" + i + "報道!");
            allSoldier[i] = new Thread(new Soldier(cyclic,"士兵 " + i));
            allSoldier[i].start();
        }
    }
}

運行結果

集合隊伍
士兵0報道!
士兵1報道!
士兵2報道!
士兵3報道!
士兵4報道!
士兵5報道!
司令:[士兵5個,集合完畢!]
士兵6報道!
士兵7報道!
士兵8報道!
士兵9報道!
司令:[士兵5個,集合完畢!]
士兵 6:任務完成
士兵 5:任務完成
士兵 4:任務完成
士兵 0:任務完成
士兵 2:任務完成
司令:[士兵5個,任務完成!]
士兵 8:任務完成
士兵 3:任務完成
士兵 9:任務完成
士兵 7:任務完成
士兵 1:任務完成
司令:[士兵5個,任務完成!]

十二、線程中斷、阻塞

暴力中止線程stop()方法,該方法無視任何加鎖狀況是否執行完畢,直接把線程中止,會出現數據不一致的狀況,在生產環境中禁止使用

public class StopThreadUnsafe {
    public static User u = new User();
    public static class User {
        private int id;
        private String name;
        public User() {
            id = 0;
            name = "0";
        }

        public int getId() {
            return id;
        }

        public void setId(int id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "User [id=" + id + ",name=" + name +"]";
        }
    }
    public static class ChangeObjectThread implements Runnable {
        public void run() {
            while (true) {
                synchronized (u) {
                    int v = (int)(System.currentTimeMillis() / 1000);
                    u.setId(v);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    u.setName(String.valueOf(v));
                }
                //線程讓步
                Thread.yield();
            }
        }
    }
    public static class ReadObjectThread implements Runnable {
        public void run() {
            while (true) {
                synchronized (u) {
                    if (u.getId() != Integer.parseInt(u.getName())) {
                        System.out.println(u.toString());
                    }
                }
                Thread.yield();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ReadObjectThread()).start();
        while (true) {
            Thread t = new Thread(new ChangeObjectThread());
            t.start();
            Thread.sleep(150);
            t.stop();
        }
    }
}

運行結果(截取部分)

User [id=1538981520,name=1538981519]
User [id=1538981520,name=1538981519]
User [id=1538981520,name=1538981519]
User [id=1538981520,name=1538981519]
User [id=1538981520,name=1538981519]
User [id=1538981520,name=1538981519]
User [id=1538981520,name=1538981519]
User [id=1538981523,name=1538981522]
User [id=1538981523,name=1538981522]
User [id=1538981523,name=1538981522]

結果解讀:爲何會出現這樣的打印呢,按道理user的id跟name賦值都是相同的,其實就是t.stop()出的問題,t.stop()在線程t尚未執行完畢時強行結束線程,使得user的id可能更新,name仍是上一次循環的值,沒有賦新值,因此出現不等,讀取線程就會判斷不等進行打印.

通知中斷interrupt(),該方法並不能立刻讓線程中止,只是一個通知,目標線程接到通知後如何處理,徹底由目標線程自行決定,若是無條件退出,則會碰到stop()的老問題.

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("Interruted!");
                        break;
                    }
                    Thread.yield();
                }
            }
        };
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
}

運行結果

Interruted!

當發出中斷通知後,只有遇到Thread.currentThread().isInterrupted()代碼段時,纔會響應中斷,並做出本身的處理,不然不會有任何響應.好比

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
//                    if (Thread.currentThread().isInterrupted()) {
//                        System.out.println("Interruted!");
//                        break;
//                    }
                    Thread.yield();
                }
            }
        };
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
}

註釋掉該段代碼,程序將永續運行,並不會產生任何中斷.

中斷通知可讓sleep()方法在休眠時產生中斷異常,捕獲這個異常能夠手動讓線程產生中斷.

public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("Interruted!");
                        break;
                    }
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        System.out.println("Interruted When Sleep");
                        Thread.currentThread().interrupt();
                    }
                    Thread.yield();
                }
            }
        };
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
}

運行結果

Interruted When Sleep
Interruted!

掛起和繼續執行

suspend()和resume()方法,一旦resume()方法在suspend()方法以前執行,將永遠被掛起,沒法釋放鎖,程序被堵塞,這是極度危險的,不要在生產環境中使用這兩個方法.

public class BadSuspend {
    public static Object u = new Object();
    static Thread t1 = new Thread(new ChangeObjectThreed(),"t1");
    static Thread t2 = new Thread(new ChangeObjectThreed(),"t2");
    public static class ChangeObjectThreed implements Runnable {
        public void run() {
            synchronized (u) {
                System.out.println("in " + Thread.currentThread().getName());
                Thread.currentThread().suspend();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(100);
        t2.start();
        t1.resume();
        t2.resume();
        t1.join();
        t2.join();
    }
}

運行結果(程序堵塞,沒法結束)

in t1
in t2

結果解讀:t1在得到監視器鎖以後被掛起,t2沒法拿到鎖,t1.resume();t2.resume();執行後,t1能夠繼續執行,釋放鎖,t2拿到鎖,被掛起,因爲t2.resume()已經執行過了,t2沒法繼續執行,沒法釋放鎖,致使程序出現及嚴重的錯誤.

修改方法,不管是否被掛起,等待,都不能影響鎖的釋放.

public class GoodSuspend {
    public static Object u = new Object();
    public static volatile boolean suspendme = false;
    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(Runnable changeObjectRunnable) {
            super(changeObjectRunnable);
        }

        public void suspendMe() {
            suspendme = true;
        }
        public void resumeMe() {
            suspendme = false;
            synchronized (this) {
                notify();
            }
        }
    }
    public static class ChangeObjectRunnable implements Runnable {
        public void run() {
            while (true) {
                synchronized (Thread.currentThread()) {
                    while (suspendme) {
                        try {
                            Thread.currentThread().wait();
                        }catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                synchronized (u) {
                    System.out.println("in ChangeObjectThread");
                }
                Thread.yield();
            }
        }
    }
    public static class ReadObjectRunnable implements Runnable {

        public void run() {
            while (true) {
                synchronized (u) {
                    System.out.println("in ReadObjectThread");
                }
                Thread.yield();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ChangeObjectThread t1 = new ChangeObjectThread(new ChangeObjectRunnable());
        Thread t2 = new Thread(new ReadObjectRunnable());
        t1.start();
        t2.start();
        Thread.sleep(1000);
        t1.suspendMe();
        System.out.println("suspend t1 2 sec");
        Thread.sleep(2000);
        System.out.println("resume t1");
        t1.resumeMe();
    }
}

運行結果(截取部分)

結果解讀:兩個線程啓動時,會並行執行,會出現相似於這種

in ChangeObjectThread
in ReadObjectThread
in ChangeObjectThread
in ReadObjectThread
in ChangeObjectThread
in ReadObjectThread
in ChangeObjectThread
in ReadObjectThread
in ChangeObjectThread
in ReadObjectThread
in ChangeObjectThread
in ReadObjectThread

中間會有2秒鐘,所有都是讀線程的,相似於這種

in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread
in ReadObjectThread

這是由於t1被等待wait()了2秒,而後被喚醒notify(),以後就是並行執行了.其實這樣寫的好處就是即使t1未被喚醒,也不會佔用鎖而不釋放.

線程阻塞工具類

LockSupport彌補了suspend()方法的不足,resume()在前發生,致使線程沒法繼續執行的狀況,與Object.wait()比,不須要先得到對象鎖.同時支持中斷通知處理.

public class LockSupportDemo {
    public static Object u = new Object();
    static Thread t1 = new Thread(new ChangeObjectThreed(),"t1");
    static Thread t2 = new Thread(new ChangeObjectThreed(),"t2");
    public static class ChangeObjectThreed implements Runnable {
        public void run() {
            synchronized (u) {
                System.out.println("in " + Thread.currentThread().getName());
                LockSupport.park();
                if (Thread.interrupted()) {
                    System.out.println(Thread.currentThread().getName() + "被中斷了");
                }
            }
            System.out.println(Thread.currentThread().getName() + "執行結束!");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(100);
        t2.start();
//        t1.interrupt();
        LockSupport.unpark(t1);
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}

運行結果:(運行結束,無堵塞)

in t1
t1執行結束!
in t2
t2執行結束!

這裏不管LockSupport.unpark(t2)先執行仍是後執行,都會給LockSupport.park()一個許可,讓掛起能夠繼續執行.不會進行堵塞.若是有鎖則會釋放鎖.

public class LockSupportDemo {
    public static Object u = new Object();
    static Thread t1 = new Thread(new ChangeObjectThreed(),"t1");
    static Thread t2 = new Thread(new ChangeObjectThreed(),"t2");
    public static class ChangeObjectThreed implements Runnable {
        public void run() {
            synchronized (u) {
                System.out.println("in " + Thread.currentThread().getName());
                LockSupport.park();
                if (Thread.interrupted()) {
                    System.out.println(Thread.currentThread().getName() + "被中斷了");
                }
            }
            System.out.println(Thread.currentThread().getName() + "執行結束!");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(100);
        t2.start();
        t1.interrupt();
//        LockSupport.unpark(t1);
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}

運行結果:

in t1
t1被中斷了
t1執行結束!
in t2
t2執行結束!

當LockSupport.park()時有中斷通知,當有Thread.interrupted()的中斷標識時,會進入中斷處理.

相關文章
相關標籤/搜索