前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线程,JVM锁整理

线程,JVM锁整理

作者头像
算法之名
发布2019-08-20 16:11:39
8680
发布2019-08-20 16:11:39
举报
文章被收录于专栏:算法之名算法之名

1、线程的等待和通知

首先wait()和notify(),notifyAll()方法一定是一般对象方法,他们并不属于线程对象方法,一定是跟synchronized(监视器锁)结伴出现的。wait()方法执行时会释放获取的监视器锁,线程进入休眠等待状态。而notify()执行时,会随机唤醒一个等待状态的线程,并重新获取监视器锁,然后再继续执行。notifyAll()方法是唤醒所有的相同对象的等待线程,再去竞争获取监视器锁。

代码语言:javascript
复制
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();
    }
}

执行结果

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

如果注释掉Thread.sleep(2000)代码块,则可能T2线程先执行,T1后执行,整个程序进入堵塞状态,无法唤醒!

2、等待线程结束

join()方法是执行一个wait()方法作用于当前线程,进行等待,如果当前线程是主线程则会使主线程等待。

代码语言:javascript
复制
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);
    }
}

执行结果

10000000

如果注释掉at.join(),主线程输出值为0,主线程执行打印时,线程at还未执行。

3、守护线程

守护线程的作用就是所有用户线程(包含主线程)都结束了,该线程也自然结束了。

代码语言:javascript
复制
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.

代码语言:javascript
复制
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();
    }
}

4、重入锁

重入锁指的是当一个线程申请获得一次加锁之后,当释放锁后再次获取该锁将无需再次申请,节省开销。

用加锁来实现多线程累加

代码语言:javascript
复制
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);
    }
}

当然还有两种方式可以达到同样的效果

代码语言:javascript
复制
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);
    }
}

原子类无锁

代码语言:javascript
复制
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

5、优先中断

优先中断并不是以获取锁为目的,而是以优先获取中断为目标

把一个死锁的例子逐步改成非死锁

代码语言:javascript
复制
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可以获取锁,程序执行完毕,抛出一个中断异常

代码语言:javascript
复制
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:线程退出

再修改成尝试获取锁

代码语言:javascript
复制
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()可以设等待时间,等待时间后再返回

代码语言:javascript
复制
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

如果等待时间小于睡眠时间,则拿不到锁

代码语言:javascript
复制
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

6、公平锁

让所有参与的线程都能够依次公平的获取锁,成本高,性能底下

代码语言:javascript
复制
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获得锁

从结果可以看出,两个线程之间总是交替获取锁。

取消公平锁

代码语言:javascript
复制
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获得锁

由结果可以看出,获取过一次锁的线程总是更容易获取下一次锁,是非公平的。

7、与重入锁结伴的等待与通知

await()方法,singal()方法与singalAll()方法类似于Object的wait(),notify(),notifyAll()方法。

代码语言:javascript
复制
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

8、信号量

信号量的理解就是如果有很多线程需要执行,而每次仅允许几个线程执行,只有其中有线程执行完毕才允许后面的线程进入执行,但总执行线程数不能多于限制数。

代码语言:javascript
复制
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线程执行完。

9、读写锁

当所有的写锁释放后,所有的读锁将并行执行,否则读锁和写锁都将进行一一锁定。

代码语言:javascript
复制
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之后没有锁竞争。

如果把读写锁换成可重入锁

代码语言:javascript
复制
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秒。

10、倒计时器

倒计时器的作用是让参与的线程挨个执行,其他线程等待,到计时器计时完毕,其他线程才可以继续执行。

代码语言:javascript
复制
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

代码语言:javascript
复制
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()的时候,任务线程才可以继续执行,从而不会出现单独线程抢占,让不同的线程都能够产生订单号。有兴趣的朋友可以查看之前的该博文。

11、循环栅栏

循环栅栏跟倒计时器最大的不同就是倒计时器当计数减到0的时候,开始允许其他线程执行,倒计时器不可再使用,而循环栅栏则无论多少线程执行,只要到了设置的限制数,就会执行绑定的线程方法,可以循环使用。如果到不了设置的限制数就会进行堵塞。

总共10个士兵,每5个士兵集合,进行一次报告共报告2次,全部集合后,每5个士兵执行任务进行一次报告共报告2次。

代码语言:javascript
复制
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个,任务完成!]

12、线程中断、阻塞

暴力停止线程stop()方法,该方法无视任何加锁情况是否执行完毕,直接把线程停止,会出现数据不一致的情况,在生产环境中禁止使用

代码语言:javascript
复制
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()的老问题.

代码语言:javascript
复制
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()代码段时,才会响应中断,并作出自己的处理,否则不会有任何响应.比如

代码语言:javascript
复制
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()方法在休眠时产生中断异常,捕获这个异常可以手动让线程产生中断.

代码语言:javascript
复制
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()方法之前执行,将永远被挂起,无法释放锁,程序被堵塞,这是极度危险的,不要在生产环境中使用这两个方法.

代码语言:javascript
复制
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无法继续执行,无法释放锁,导致程序出现及严重的错误.

修改方法,无论是否被挂起,等待,都不能影响锁的释放.

代码语言:javascript
复制
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()比,不需要先获得对象锁.同时支持中断通知处理.

代码语言:javascript
复制
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()一个许可,让挂起可以继续执行.不会进行堵塞.如果有锁则会释放锁.

代码语言:javascript
复制
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()的中断标识时,会进入中断处理.

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档