美文网首页
Java并发编程-JDK并发包

Java并发编程-JDK并发包

作者: agile4j | 来源:发表于2018-09-07 18:46 被阅读29次

    参考资料:《Java高并发程序设计》


    1.同步控制

    1.扩展了synchronized功能的:重入锁

    1.简介

    • 使用示例:
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Test {
        // 声明锁
        private static ReentrantLock lock = new ReentrantLock();
        private static int i = 0;
    
        private static final Runnable runnable = () -> {
            for (int j = 0; j < 100000; j++) {
                // 加锁
                lock.lock();
                try {
                    i++;
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        };
    
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(runnable);
            Thread t2 = new Thread(runnable);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
    }
    
    • 重入锁可以完全替代synchronized关键字。
    • 与synchronized相比,重入锁有着明显的操作过程。开发人员必须手动指定何时加锁,何时释放锁。也正因为如此,重入锁对逻辑控制的灵活性要远好于synchronized。
    • 需要注意的是,在退出临界区时,必须记得释放锁,否则,其他线程就没有机会再访问临界区了。
    • 重入锁之所以有重入两字,是因为这种锁是可以被同一个线程反复进入的。示例中的核心代码可写成下面的形式:
    lock.lock();
    lock.lock();
    try {
        i++;
    } finally {
        lock.unlock();
        lock.unlock();
    }
    
    • 可重入的目的是为了避免同一个线程在第2次获取锁的时候和自己产生死锁。
    • 需要注意的是:如果一个线程多次获得锁,那么在释放锁的时候,也必须释放相同的次数。
      1. 如果释放锁的次数多,那么会得到一个IllegalMonitorStateException异常。
      2. 如果加锁的次数多,那么相当于线程还持有这个锁,其他线程仍无法进入临界区。

    2.高级功能

    1.中断

    • 对于synchronized来说,如果一个线程在等待锁,那么结果只有两种情况:
      1. 获得这把锁继续执行
      2. 保持等待
    • 而重入锁提供了另外一种可能:线程可以被中断。即在等待锁的过程中,程序可以根据需要取消对锁的请求。
    • 下面的代码产生了一个死锁,但得益于锁中断,可以轻易地解决这个死锁:
    import java.util.concurrent.locks.ReentrantLock;
    import lombok.AllArgsConstructor;
    
    public class Test {
        private static final ReentrantLock lock1 = new ReentrantLock();
        private static final ReentrantLock lock2 = new ReentrantLock();
    
        @AllArgsConstructor
        private static class IntLock implements Runnable {
            private int lock;
    
            @Override
            public void run() {
                try {
                    if (lock == 1) {
                        lock1.lockInterruptibly();
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            System.out.println("interrupted in sleep");
                        }
                        lock2.lockInterruptibly();
                    } else {
                        lock2.lockInterruptibly();
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            System.out.println("interrupted in sleep");
                        }
                        lock1.lockInterruptibly();
                    }
                } catch (InterruptedException e) {
                    System.out.println("interrupted in business");
                } finally {
                    if (lock1.isHeldByCurrentThread()) {
                        lock1.unlock();
                    }
                    if (lock2.isHeldByCurrentThread()) {
                        lock2.unlock();
                    }
                    System.out.println(Thread.currentThread().getId() + ":线程退出");
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(new IntLock(1));
            Thread t2 = new Thread(new IntLock(2));
            t1.start();
            t2.start();
            Thread.sleep(1000);
            t2.interrupt();
        }
    }
    
    // interrupted in business
    // 12:线程退出
    // 11:线程退出
    
    • 上述代码中对锁的请求,统一使用lockInterruptibly()方法。这是一个可以对中断进行响应的锁申请动作,即在等待锁的过程中,可以响应线程中断。

    2.锁申请等待限时

    • 可以使用tryLock()方法进行一次限时的锁申请:
    public class Test {
        public static class TimeLock implements Runnable {
    
            public static ReentrantLock lock = new ReentrantLock();
    
            @Override
            public void run() {
                try {
                    if (lock.tryLock(5, TimeUnit.SECONDS)) {
                        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 lock = new TimeLock();
            Thread t1 = new Thread(lock);
            Thread t2 = new Thread(lock);
            t1.start();
            t2.start();
        }
    }
    
    // get lock failed
    
    • 上述代码中的tryLock()方法接收两个参数,一个表示等待时长,另一个表示计时单位。如果申请锁成功,则返回true,否则返回false。
    • tryLock()方法也可以不带参数直接运行。这种情况下,如果锁未被其他线程占用,则申请锁成功,返回true,否则不会做任何等待,立即返回false。下面演示了这种用法:
    public class Test {
        public static class TryLock implements Runnable {
    
            public static ReentrantLock lock1 = new ReentrantLock();
            public static ReentrantLock lock2 = new ReentrantLock();
            int lock;
    
            public TryLock(int lock) {
                this.lock = lock;
            }
    
            public void tryDoubleLock(ReentrantLock firstLock,
                                      ReentrantLock secondLock,
                                      String param, Consumer<String> consumer) {
                while (true) {
                    if (firstLock.tryLock()) {
                        try {
                            if (secondLock.tryLock()) {
                                try {
                                    consumer.accept(param);
                                    return;
                                } finally {
                                    secondLock.unlock();
                                }
                            }
                        } finally {
                            firstLock.unlock();
                        }
                    }
                }
            }
    
            @Override
            public void run() {
                String param = ":My Job done";
                Consumer<String> consumer = str ->
                        System.out.println(Thread.currentThread().getId() + str);
                if (lock == 1) {
                    tryDoubleLock(lock1, lock2, param, consumer);
                } else {
                    tryDoubleLock(lock2, lock1, param, consumer);
                }
            }
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new TryLock(1));
            Thread t2 = new Thread(new TryLock(2));
            t1.start();
            t2.start();
        }
    }
    
    // 11:My Job done
    // 10:My Job done
    
    • 先让t1获得lock1,再让t2获得lock2,接着让t1申请lock2,t2申请lock1。在一般情况下,这会导致t1和t2相互等待,从而引起死锁。但因为使用了tryLock(),线程不会傻傻等待,而是不断重试,直到某个线程同时获得lock1和lock2两把锁。执行上述代码,可以发现两个线程可以很快双双正常执行完毕。

    3.公平锁

    • 大多数情况下,锁的申请都是非公平的。举个例子:线程t1首先请求了锁A,接着线程t2也请求了锁A,那么锁A可用时,是t1获得锁还是t2获得锁是不确定的。系统会从这个锁的等待队列中随机挑选一个,因此不能保证公平性,有可能会产生饥饿
    • synchronized关键字进行锁控制所产生的锁就是非公平的
    • 重入锁允许通过构造函数对锁的公平性进行设置。fair为true表示公平。
    public ReentrantLock(boolean fair)
    
    • 公平锁的实现需要维护一个有序队列,因此锁的实现成本高,性能也非常底下。因此默认情况下锁是非公平的。如果不是特殊的需求,也不需要公平锁。下面的代码演示了公平锁的使用:
    public class Test {
        public static class FairLock implements Runnable {
    
            public static ReentrantLock fairLock = new ReentrantLock(true);
    
            @Override
            public void run() {
                while (true) {
                    try {
                        fairLock.lock();
                        System.out.println(Thread.currentThread().getName() + "获得锁");
                    } finally {
                        fairLock.unlock();
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            FairLock r1 = new FairLock();
            Thread t1 = new Thread(r1, "Thread_t1");
            Thread t2 = new Thread(r1, "Thread_t2");
            t1.start();
            t2.start();
        }
    }
    
    // 程序运行一段时间(稳定后)的部分输出:
    // Thread_t1获得锁
    // Thread_t2获得锁
    // Thread_t1获得锁
    // Thread_t2获得锁
    // Thread_t1获得锁
    // Thread_t2获得锁
    // Thread_t1获得锁
    // Thread_t2获得锁
    
    • 而如果把fairLock初始化时的参数改为false,那么根据系统的调度,一个线程会倾向于再次获取已经持有的锁,这种分配方式是高效的,但无公平性可言。

    3.总结

    • 对ReentrantLock的几个重要方法整理如下:
    1. lock(): 获得锁,如果锁已经被占用,则等待。
    2. lockInterruptibly(): 获得锁,但优先响应中断。
    3. tryLock(): 尝试获得锁,如果成功返回true,失败返回false。该方法不等待,立即返回。
    4. tryLock(long time, TimeUnit unit): 在给定的时间内尝试获得锁。
    5. unlock(): 释放锁。
    • 从重入锁的实现来看,主要包含三个要素:
    1. 原子状态。原子状态使用CAS操作(CompareAndSwap)来存储当前锁的状态,判断锁是否已经被别的线程持有。
    2. 等待队列。所有没有请求到锁的线程,会进入等待队列进行等待。待有线程释放锁后,系统就能从等待队列中唤醒一个线程,继续工作。
    3. 阻塞原语park()和unpark()。这两个方法用来挂起和恢复线程。没有得到锁的线程将会被挂起。有关park()和unpark()可参考线程阻塞工具类LockSupport。

    2.重入锁的好搭档:Condition条件

    1.简介

    • Condition的作用和Object.wait()、Object.notify()的作用是大致相同的。但是Condition和重入锁相关联,而后者是和synchronized关键字合作使用的。
    • 通过Lock接口(重入锁就实现了该接口)的newCondition()方法可以生成一个与当前重入锁绑定的Condition实例。利用Condition实例就可以让线程在合适的时间等待,或在特定的时刻得到通知继续执行。

    2.方法

    • await()
      调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。调用该方法外,当前线程会释放当前已经获得的锁(这一点与Object.wait方法一致),并且等待其它线程调用该条件对象的signal()或者signalAll()方法(这一点与Object.notify()或Object.notifyAll()很像)。或者在等待期间,当前线程被中断,则wait()方法会抛出InterruptedException并清除当前线程的中断状态。

    • await(long time, TimeUnit unit)
      适用条件和行为与await()基本一致,唯一不同点在于,指定时间之内没有收到signal()或signalALL()信号或者线程中断时该方法会返回false;其它情况返回true。

    • awaitNanos(long nanosTimeout)
      调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。nanosTimeout指定该方法等待信号的的最大时间(单位为纳秒)。若指定时间内收到signal()或signalALL()则返回nanosTimeout减去已经等待的时间;若指定时间内有其它线程中断该线程,则抛出InterruptedException并清除当前线程的中断状态;若指定时间内未收到通知,则返回0或负数。

    • awaitUntil(Date deadline)
      适用条件与行为与awaitNanos(long nanosTimeout)完全一样,唯一不同点在于它不是等待指定时间,而是等待由参数指定的某一时刻。

    • awaitUninterruptibly()
      调用该方法的前提是,当前线程已经成功获得与该条件对象绑定的重入锁,否则调用该方法时会抛出IllegalMonitorStateException。调用该方法后,结束等待的唯一方法是其它线程调用该条件对象的signal()或signalALL()方法。等待过程中如果当前线程被中断,该方法仍然会继续等待,同时保留该线程的中断状态。

    • signal()
      用于唤醒一个等待中的线程。类似于Object.notify()

    • signalAll()
      会唤醒所有在等待中的线程。类似于Object.notifyAll()

    • 代码演示:

    public class Test {
    
        public static class ReenterLockCondition implements Runnable {
    
            public static ReentrantLock lock = new ReentrantLock();
            public static Condition condition = lock.newCondition();
    
            @Override
            public void run() {
                try {
                    lock.lock(); // 申请并获得锁
                    condition.await();  // 释放锁
                                        // 收到signal信号后重新申请锁、获得锁
                    System.out.println("thread is going on");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock(); // 释放锁
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            new Thread(new ReenterLockCondition()).start();
            Thread.sleep(2000);
            ReenterLockCondition.lock.lock(); // 获得锁
            ReenterLockCondition.condition.signal(); // signal通知
            ReenterLockCondition.lock.unlock(); // 释放锁,谦让给被唤醒的线程
        }
    }
    
    // thread is going on
    

    3.允许多个线程同时访问:信号量(Semaphore)

    1.简介

    • 从广义上说,信号量是对锁的扩展。无论是内部锁synchronized还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源,而信号量却可以指定多个线程,同时访问某一个资源。
    • 信号量主要提供了以下构造函数。在构造信号量对象时,必须指定信号量的准入数,即同时能申请多少个许可。当每个线程每次只申请一个许可时,这就相当于指定了同时有多少个线程可以访问某一个资源。
    public Semaphore(int permits) // 参数为信号量的准入数
    public Semaphore(int permits, boolean fair) // 第二个参数可以指定是否公平
    

    2.方法

    • acquire()
      尝试获得一个准入的许可。若无法获得,则线程会等待,直到有线程释放一个许可或者当前线程被中断。

    • acquireUninterruptibly()
      和acquire()方法类似,但是不响应中断。

    • tryAcquire()
      尝试获得一个许可,如果成功返回true,失败返回false,不会等待,立即返回。

    • release()
      用于在线程访问资源结束后,释放一个许可,以使其他等待许可的线程可以进行资源访问。

    • 代码演示:

    public class Test {
    
        public static class SemaphoreDemo implements Runnable {
    
            final Semaphore semaphore = new Semaphore(5);
    
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    Thread.sleep(2000);
                    System.out.println(System.currentTimeMillis() + "-" +
                            Thread.currentThread().getId() + ":done!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            ExecutorService exec = Executors.newFixedThreadPool(20);
            final SemaphoreDemo demo = new SemaphoreDemo();
            for (int i = 0; i < 20; i++) {
                exec.submit(demo);
            }
        }
    }
    
    // 从输出可以观察出系统以5个线程一组为单位,依次进行输出
    

    4.读写分离锁:ReadWriteLock

    • 读写分离锁(简称“读写锁”)可以有效地帮助 减少锁竞争,以提升系统性能。
    • 读写锁允许多个线程同时读,但考虑到数据完整性,写写操作和读写操作间依然是需要相互等待和持有锁的。读写锁的访问约束如下表:
    -
    非阻塞 阻塞
    阻塞 阻塞
    • 在系统中,如果读操作次数远大于写操作,那么读写锁的功效就会非常明显。例如下面的代码:
    public class Test {
    
        private static Lock lock = new ReentrantLock();
        private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        private static Lock readLock = readWriteLock.readLock();
        private static Lock writeLock = readWriteLock.writeLock();
        private static CountDownLatch latch = new CountDownLatch(20);
        private int value;
    
        public Object handleRead(Lock lock) throws InterruptedException {
            try {
                lock.lock(); // 模拟读操作
                Thread.sleep(1000); // 读操作的耗时越多,读写锁的优势就越明显
                return value;
            } finally {
                latch.countDown();
                lock.unlock();
            }
        }
    
        public void handleWrite(Lock lock, int index) throws InterruptedException {
            try {
                lock.lock(); // 模拟写操作
                Thread.sleep(1000);
                value = index;
            } finally {
                latch.countDown();
                lock.unlock();
            }
        }
    
        public static void main(String[] args) throws Exception {
            final Test demo = new Test();
            Runnable readRunnable = () -> {
                try {
                    demo.handleRead(readLock);
                    //demo.handleRead(lock); // 使用重入锁而不是读写锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
            Runnable writeRunnable = () -> {
                try {
                    demo.handleWrite(writeLock, new Random().nextInt());
                    //demo.handleRead(lock); // 使用重入锁而不是读写锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
    
            long startTime = System.currentTimeMillis();
            for (int i = 0; i <= 18; i++) {
                new Thread(readRunnable).start();
            }
            for (int i = 19; i < 20; i++) {
                new Thread(writeRunnable).start();
            }
            latch.await();
            System.out.println("used time: " + (System.currentTimeMillis() - startTime));
        }
    }
    
    // 使用读写锁
    // used time: 2022
    // 使用重入锁
    // used time: 20009
    
    • 通过运行结果可以看出,使用读写锁程序大约2秒多就能结束(写线程直接是实际串行的)。而使用重入锁,所有的读和写线程之间都必须相互等待,整个程序的执行时间将长达20余秒。

    5.倒计数器:CountDownLatch

    • 倒计数器通常用来控制线程等待,它可以让某一个线程等待直到倒计数结束,再执行。

    • 上一节介绍读写锁统计用时时,就是使用的倒计数器。此外倒计数器的一种典型场景就是火箭发射。在火箭发射前,为确保万无一失,往往会进行各项设备仪器的检查。只有等所有的检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使点火线程等待所有检查线程全部完工后,再执行。示意图如下:

      未命名文件(1).png-12.2kB未命名文件(1).png-12.2kB
    • 倒计时器的几个关键方法

    // 构造函数接收一个整数作为参数,即当前这个倒计时器的计数个数
    public CountDownLatch(int count)
    
    // 计数减一
    public void countDown()
    
    // 使线程等待,直到倒计数完成
    public void await()
    

    6.循环栅栏:CyclicBarrier

    • CyclicBarrier与CountDownLatch类似,也可实现线程间的 计数等待 ,但功能更加强大:这个计数器可以 反复使用
    • 循环栅栏的一个使用场景:比如司令下达命令,要求10个士兵一起去完成一项任务。这时,就会要求10个士兵先集合报道,全部报道完毕后(第一次计数完成),再一起去执行任务,当10个士兵把自己的任务都完成了(第二次计数完成),那么司令再对外宣布:任务完成!
    • CyclicBarrier有两个主要的方法:
    // parties:计数总数,也就是参与的线程总数
    // barrierAction:当计数器完成一次计数后,系统要执行的动作
    public CyclicBarrier(int parties, Runnable barrierAction)
    
    // 等待一次计数完成
    // 注意抛出的两种异常(见下一节的代码)
    public int await() throws InterruptedException, BrokenBarrierException
    
    • 上面的的场景用代码实现如下:
    public class Test {
    
        private static final int SOLDIER_NUM = 10;
        private volatile static boolean allAssembledFlag = false;
        private static final Runnable barrierRun = () -> {
            if (allAssembledFlag) {
                System.out.println("司令:[士兵" + SOLDIER_NUM + "个,任务完成!]");
            } else {
                System.out.println("司令:[士兵" + SOLDIER_NUM + "个,集合完毕!]");
            }
        };
    
        public static class Soldier implements Runnable {
            private final CyclicBarrier cyclic;
            private String soldierName;
    
            Soldier(CyclicBarrier cyclic, String soldierName) {
                this.cyclic = cyclic;
                this.soldierName = soldierName;
            }
    
            void doWork() {
                try {
                    Thread.sleep(Math.abs(new Random().nextInt() % 10000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(soldierName + ":任务完成");
            }
    
            @Override
            public void run() {
                try {
                    // 等待所有士兵到齐
                    cyclic.await();
                    allAssembledFlag = true;
                    doWork();
                    // 等待所有士兵完成工作
                    cyclic.await();
                } catch (InterruptedException e) {
                    System.out.println("线程等待时被中断——需要执行响应外部紧急事件的逻辑");
                } catch (BrokenBarrierException e) {
                    System.out.println("当前的CyclicBarrier已经被损坏," +
                            "系统可能已经无法等待所有线程到齐——需要执行使等待线程就地解散的逻辑");
                    // 例如当前循环栅栏的计数总数为10,若有一个线程被中断,
                    // 那我们就会得到1个InterruptedException和9个BrokenBarrierException
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            Thread[] allSoldier = new Thread[SOLDIER_NUM];
            CyclicBarrier cyclic = new CyclicBarrier(SOLDIER_NUM, barrierRun);
            // 设置屏障点,主要是为了执行这个方法
            System.out.println("集合队伍!");
            for (int i = 0; i < SOLDIER_NUM; ++i) {
                System.out.println("士兵 " + i + " 报道!");
                allSoldier[i] = new Thread(new Soldier(cyclic, "士兵 " + i));
                allSoldier[i].start();
            }
        }
    
    }
    
    // 集合队伍!
    // 士兵 0 报道!
    // 士兵 1 报道!
    // 士兵 2 报道!
    // 士兵 3 报道!
    // 士兵 4 报道!
    // 士兵 5 报道!
    // 士兵 6 报道!
    // 士兵 7 报道!
    // 士兵 8 报道!
    // 士兵 9 报道!
    // 司令:[士兵10个,集合完毕!]
    // 士兵 3:任务完成
    // 士兵 5:任务完成
    // 士兵 9:任务完成
    // 士兵 7:任务完成
    // 士兵 4:任务完成
    // 士兵 1:任务完成
    // 士兵 8:任务完成
    // 士兵 0:任务完成
    // 士兵 6:任务完成
    // 士兵 2:任务完成
    // 司令:[士兵10个,任务完成!]
    

    7.线程阻塞工具类:LockSupport

    • LockSupport是一个线程阻塞工具,可以在线程内任意位置让线程阻塞。
    1. 和Thread.suspend()相比,它弥补了由于resume()在前发生,导致线程无法继续执行的情况。
    2. 和Object.wait()相比,它不需要先获得某个对象的锁,也不会抛出InterruptedException异常。
    • LockSupport的 静态方法park() 可以阻塞当前线程,类似的还有parkNanos()、parkUntil()等方法,它们实现了一个限时的等待。
    • 之前提到的有关 suspend()永久卡死线程 的例子,可用LockSupport重写如下:
    public class Test {
    
        private static final Object u = new Object();
    
        public static class ChangeObjectThread extends Thread {
            public ChangeObjectThread(String name) {
                super.setName(name);
            }
    
            @Override
            public void run() {
                synchronized (u) {
                    try {
                        System.out.println("in " + getName());
                        Thread.sleep(1000);
                        LockSupport.park();
                        System.out.println("out " + getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            ChangeObjectThread t1 = new ChangeObjectThread("t1");
            ChangeObjectThread t2 = new ChangeObjectThread("t2");
            t1.start();
            t2.start();
            LockSupport.unpark(t1);
            LockSupport.unpark(t2);
            t1.join();
            t2.join();
        }
    
    }
    
    // in t1
    // out t1
    // in t2
    // out t2
    
    • 这里只是将原来的suspend()和resume()方法用park()和unpark()方法做了替换。并且为了确保unpark()在park()之前被调用,还让线程多sleep了1秒。但执行这段代码可以发现,它永远可以正常的结束,不会被永久性的挂起。

    • LockSupport不会因unpark()先于park()执行而被永久性挂起的原因,是因为它使用了类似 信号量 的机制。它为每一个线程准备了一个 许可,如果许可 可用,那么park()函数会立即返回,并且 消费掉 这个许可(也就是将许可变为 不可用),如果许可不可用,就会阻塞。而unpark()则可以使一个许可变为可用(但和信号量不同的是,许可不能累加,你不可能拥有超过一个许可,它永远只有一个)。

    • 此外,处于park()挂起状态的线程不会像suspend()那样给出一个令人费解的Runnable的状态。它会非常明确地给出一个WAITING状态,而且还会标注是park()引起的:

      1.png-312.7kB1.png-312.7kB
    • 这种标注使得分析问题非常方便。此外,如果使用park(Object)函数,还可以为当前线程设置一个阻塞对象。这个阻塞对象会出现在线程Dump中,这样分析问题就更方便了。例如将上述代码中的park()改为park(this),那么在线程Dump中,可以看到如下信息:

      1.png-379.1kB1.png-379.1kB
    • 除了有定时阻塞的功能外,LockSupport.park()还支持中断响应。但和其他接收中断的函数不同,park()函数不会抛出InterruptedException异常。park()函数只会默默的返回,但我们可以从Thread.interrupted()等方法获得中断标记。例如:

    public class Test {
    
        private static final Object u = new Object();
    
        public static class ChangeObjectThread extends Thread {
            public ChangeObjectThread(String name) {
                super.setName(name);
            }
    
            @Override
            public void run() {
                synchronized (u) {
                    System.out.println("in " + getName());
                    LockSupport.park();
                    if (Thread.interrupted()) {
                        System.out.println(getName() + " 被中断了");
                    }
                    System.out.println("out " + getName());
                }
            }
        }
    
        public static void main(String[] args) throws Exception {
            ChangeObjectThread t1 = new ChangeObjectThread("t1");
            ChangeObjectThread t2 = new ChangeObjectThread("t2");
            t1.start();
            Thread.sleep(100);
            t2.start();
            t1.interrupt();
            LockSupport.unpark(t2);
            t1.join();
            t2.join();
        }
    
    }
    
    // in t1
    // t1 被中断了
    // out t1
    // in t2
    // out t2
    

    end

    相关文章

      网友评论

          本文标题:Java并发编程-JDK并发包

          本文链接:https://www.haomeiwen.com/subject/hbfhgftx.html