JDK并发包1-1

https://www.jianshu.com/p/ef342bc21f7e

多线程的控制并不是一个非常简单的事情,一般意义上呢,最基本的多线程控制,用synchronized关键字,和object.wait,

和object.notify,这些操作,那这个在之前的课程当中呢,也已经又给大家介绍,我们在这里会介绍一些更加高级的工具,

这些高级的工具呢,他首先在功能上要比synchronized要高级一些,其实在使用上面来说呢,它会封装一些更加常用的一些

场景,使大家使用更加快捷,同时写JDK开发包的呢,都是一些比较厉害的人物,所以相对来讲,他们的实现呢,会比大家自己

去实现一个类似的功能呢,性能上和效果上要好的多,首先第一个我们想要给大家介绍的呢,是ReentrantLock,是重入锁,它是

synchronized的一个替代品,或者说它是一个增强版,synchronized关键字,特点是使用简单,但是功能上是比较薄弱的,因为他

只能做到说,多个线程进行临界区访问的时候呢,不能进入临界区的线程进行一个等待,这个等待是一个死等,只有前面的线程离开

临界区以后呢,才能进去,但是ReentranLock给我们提供了更多的选择性,我们在JDK1.5之前呢,JAVA虚拟机对synchronized的优化

并不会充分,RenntrantLock的性能要好于synchronized关键字,但是在做了充分优化之后,现在的JDK的版本当中,其实两者性能是

不相上下的,所以如果只是一个简单功能的实现呢,没有必要刻意去追求比较高级的功能,ReentrantLock我们主要看哪些方面,他除了

实现普通的锁的功能之外,他还实现了比如可中断,可限时,公平锁,可重入,这些特点,什么是可重入呢,ReentrantLock对于同一个线程

来讲,否则会出现一个线程把自己给卡死
package com.learn.thread;

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo implements Runnable{
    public static ReentrantLock lock=new ReentrantLock();
    public static int i=0;
    @Override
    public void run() {
        for(int j=0;j<100000;j++) {
            lock.lock();
            try {
                i++;
            }finally {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockDemo demo=new ReentrantLockDemo();
        Thread t1=new Thread(demo);
        Thread t2=new Thread(demo);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

我们在多个线程要对i做++操作,大家可以知道,直接对这个变量做++操作呢,一定不是线程安全的,所以我们加锁,

这个做就是ReentrantLock,我们现在有两个线程,都会对他去做一个加加操作,我们每一次只允许一个对他做++,

所以我们每次在做加之前,我们会做一个lock操作,在这个锁上做一个lock操作,离开之后要做unlock操作,这个写法

也是使用ReentantLock的一个基本范式,必须这样写,为什么要把unlock写到finally里面呢,防止发生意外,导致你这个

锁没有释放掉,你这个锁没有释放掉后果是很严重的,会导致你这个添加的线程没法进来,万一你在执行过程当中,给抛了

个异常,异常你又没有处理,那这个时候后果就比较严重,你写finally里面呢,不管里面是否有异常发生,发生什么情况,

finally在我这个程序退出之前呢,总是会给执行一下的,在这个地方把锁给释放掉,相对于synchronized来讲呢,对synchronized

来说,他的这个锁的释放呢,是虚拟机完成的自动的动作,我们只要把synchronized的括号给括起来,就可以了,语法上检查是通过的,

那synchronized就能保证锁能够被释放掉,ReentrantLock是由程序决定是在什么时候释放锁,那从这点来讲,它提供了锁的一个灵活性,

在任何场景下来释放,但是同样道理,灵活性付出的代价呢,你要格外小心,不能忘记把锁给释放掉,如果你忘了释放这个地方,其他的

就永远进不来了,各自加了这么多次,它是没有任何不安全性发生,如果是不安全,那么会小于这个数字,这里是一个重入,如果说你不幸

很意外的,在线程中两次对这个锁加锁,如果两次对这个锁进行加锁之后呢,你许可的数量就变成2,如果出现这种情况,那么你必须对这个

锁释放两次,大家可能会觉得有点奇怪,从我们这个直观意义上呢,我一个线程取一次锁,这是很自然的事情,我不当心的情况,我又再

取得一次,这不是什么大的问题,当我不需要使用这个锁的时候呢,把它释放掉,那我也只需要执行一次,那也就是可以了,如果我们在

这种情况之下,沃我们只执行一次unlock会是什么结果,我们可以看到,这个程序我已经开启来了,开了很长时间了,但是没有停止,

因为有一个线程卡死在里边了,所以他是不会把这个i给打印出来的,这个时候是会有一个线程在等待的现象,是因为你前面的线程

只释放了一次unlock,导致其他线程就进不来了,对于重入锁来讲,lock了几次,你就必须要释放几次,如果我们lock了两次,我们就释放

两次,那就没有问题了,这是重入锁的一个特点,如果你一个线程拿了两个许可,你得释放两次,下面我们来看一个比较好的功能,叫做

可中断,重入锁它是可以被中断的,他不像synchronized关键字一样,对中断是没有响应的,对中断没有响应的一个后果呢,如果你发生了

死锁,或者长期等待的情况,不一定死锁产生了长期等待的情况,你前一个线程因为某一些原因没有完成某些操作,导致后面的线程要做一个

长期的等待,那么你在长期等待过程当中,我们想看到的一个现象,我们希望这个线程停下来,这个时候一个可行的办法呢,发送一个中断信号,

让这个线程停下来,重入锁就提供这个功能说,我在 加锁的同时,可以去响应你的中断,如果我发生了死锁,如果发生了一些意外发生的

情况,我在一个锁上卡了很久,那我还有一个办法把你这个线程给唤醒,不至于永久性的卡死下去
package com.learn.thread;

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockInterruptDemo implements Runnable{
    public static ReentrantLock lock1=new ReentrantLock();
    public static ReentrantLock lock2=new ReentrantLock();
    int lock;
    public ReentrantLockInterruptDemo(int lock) {
        this.lock=lock;
    }
    @Override
    public void run() {
        try {
            if(lock==1) {
                lock1.lockInterruptibly();
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock2.lockInterruptibly();
            }else {
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock1.lockInterruptibly();
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }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 {
        ReentrantLockInterruptDemo r1=new ReentrantLockInterruptDemo(1);
        ReentrantLockInterruptDemo r2=new ReentrantLockInterruptDemo(2);
        Thread t1=new Thread(r1);
        Thread t2=new Thread(r2);
        t1.start();
        t2.start();Thread.sleep(1000);
        DeadlockChecker.check();
    }
    
}
package com.learn.thread;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

/** 检查死锁  */
public class DeadlockChecker {
    private final static ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
    
    final static Runnable deadLockChecker = new Runnable() {
        @Override
        public void run() {
            while(true){
                long[] deadLockedThreadIds = mbean.findDeadlockedThreads();
                if(deadLockedThreadIds != null){
                    ThreadInfo[] threadInfos = mbean.getThreadInfo(deadLockedThreadIds);
                    for(Thread t : Thread.getAllStackTraces().keySet()){
                        for(ThreadInfo ti : threadInfos){
                            if(ti.getThreadId() == t.getId()){
                                t.interrupt();
                            }
                        }
                    }
                }
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
        }
    };
    
    public static void check() {
        Thread t = new Thread(deadLockChecker);
        t.setDaemon(true);
        t.start();
    }
}
lockInterruptibly表示一个可中断的加锁,只有这样去加锁,他才会响应中断,我们去申请一个锁,除非当前线程被

中断,中断之后他就会抛出一个异常,那我们来看一下这个程序,这个程序有两个锁,我想在这个程序当中去构造一个死锁

的现象,线程我们也开两个,这里是一个实例变量,不是一个静态变量,我两个实例可以赋值两个不同的值,那当我lock等于1的

时候呢,这样线程1锁了1,线程2先锁了2,同时线程1申请lock2,是很明显的一个死锁,对于这个死锁来讲,如果你使用lock方法呢,

就不太有办法去帮他解开了,但是如果你使用Interruptbly,那你就可以把两个线程当一个中断,这个程序依然可以顺利的结束,

死锁必然会产生一个无限期等待
package com.learn.thread;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TimedLock 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) {
        TimedLock ins=new TimedLock();
        Thread t1=new Thread(ins);
        Thread t2=new Thread(ins);
        t1.start();
        t2.start();
    }
    
}
释放占用的锁,使得别人可以获得锁,trylock有两个参数,准备在这个锁上等多久,比如这里就等5秒中,第二个是单位,

这里是5秒,如果你要改成5毫秒,那这里就改成毫秒,另外一个是公平锁,ReentrantLock内部是支持公平锁的,参数是fair,

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
	sync = fair ? new FairSync() : new NonfairSync();
}

什么叫公平锁呢,我这个锁可以保证线程先来先到,后来者后得,在一般意义上呢,我们这个锁是不公平的,我先来申请锁的线程,

未必会先拿到锁,我后面来的线程未必后拿到锁,可能我后面的锁运气好一点,我反而先拿到锁,有可能会拿到某些线程,拿不到锁,

产生饥饿现象,公平锁是不会有问题,对于公平锁来说,先到的线程他一定会先拿到锁,后到的线程会后拿到锁,公平锁虽然不会产生

饥饿,但是公平锁的性能呢,是要比非公平锁性能差很多,因为公平锁还要处理一个排队的问题,所以如果说没有特别需求,我们不一定

要去使用公平的状态,默认情况下这个锁是非公平的,那这里是ReentrantLock所提供的一些功能,下面我们来看一下和重入锁相关的

一个概念condition,ReentrantLock和Condition之间的关系呢,如同synchronized和object.wait,object.notify,

之间的关系,之前我们已经有了比较清楚地了解,你要去wait或者notify一个线程,

那你就要获得这个monitor监视器的所有权,如果你没有得到monitor的所有权,不能做notify,那么同样的道理,

我们condition是相当于说,在某一个锁上面,去做这个wait和notify,

你也要去获得这个锁,Condition什么意思呢,如果之前把wait和notify清楚的话,Condition意思是和他一样的,但是不同的是

它是和ReentrantLock一起使用,而这两个是和synchronized一起使用,一个是monitor,一个是ReentrantLock,那接口也是

非常非常类似,一个是wait和notify,一个是await,等待在Condition上,signal通知,通知等待在Condition上的线程,

也有signalAll,这个也notifyAll相同的意思,比较强大的是说,wait也是有一个等待时间

public final void wait(long timeout, int nanos) throws InterruptedException {
	if (timeout < 0) {
		throw new IllegalArgumentException("timeout value is negative");
	}

	if (nanos < 0 || nanos > 999999) {
		throw new IllegalArgumentException(
							"nanosecond timeout value out of range");
	}

	if (nanos > 0) {
		timeout++;
	}

	wait(timeout);
}

https://www.cnblogs.com/ten951/p/6212127.html
package com.learn.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReenterLockCondition implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    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 r1 = new ReenterLockCondition();
        Thread t1 = new Thread(r1);
        t1.start();
        Thread.sleep(2000);
        lock.lock();
        condition.signal();
        lock.unlock();
    }
}

使得当前线程被挂起,等待操作,信号量是什么呢,对于我们这个锁来讲,它是互斥的,排他的,它是exclusive的,

我进去了,没有人再能进去,它是绝对严格的保护,当我有一个线程进了这个区间之后呢,另外不可能再有线程能够

进入到这个区间里面去,信号量是一个许可为一的,信号量是我允许若干个线程,进入到这个区间里面来,临界区里面来,

但是超过我许可范围的线程呢,必须等待,他可以认为是一个广义上的锁,也可以认为是一个共享锁,他可以有多个线程共享

去使用这个临界区,比如我们这个信号量当中,假使有10个许可,每一个许可可以分配给10个线程,当然一个线程也可以那两三个

许可,你可以根据业务的需求,每个线程可以拿几个许可,比如10个线程,信号量允许多个线程进入临界区,许可数量唯一的时候

就相当于一把锁,我要处理多个请求,如果系统的负载有限,只能同时处理10个请求的任务,超过10个我们就没有能力处理,这个时候

我们就可以使用信号量去控制,当有10个线程进来的时候呢,那么我就给他做执行,超过10个我就让他做等待,这个是一个非常简单的

使用系统做这个功能控制,信号量是一个共享锁

/**
 * Acquires a permit from this semaphore, blocking until one is
 * available, or the thread is {@linkplain Thread#interrupt interrupted}.
 *
 * <p>Acquires a permit, if one is available and returns immediately,
 * reducing the number of available permits by one.
 *
 * <p>If no permit is available then the current thread becomes
 * disabled for thread scheduling purposes and lies dormant until
 * one of two things happens:
 * <ul>
 * <li>Some other thread invokes the {@link #release} method for this
 * semaphore and the current thread is next to be assigned a permit; or
 * <li>Some other thread {@linkplain Thread#interrupt interrupts}
 * the current thread.
 * </ul>
 *
 * <p>If the current thread:
 * <ul>
 * <li>has its interrupted status set on entry to this method; or
 * <li>is {@linkplain Thread#interrupt interrupted} while waiting
 * for a permit,
 * </ul>
 * then {@link InterruptedException} is thrown and the current thread's
 * interrupted status is cleared.
 *
 * @throws InterruptedException if the current thread is interrupted
 */
public void acquire() throws InterruptedException {
	sync.acquireSharedInterruptibly(1);
}

也可以让一个线程拿多个许可

/**
 * Acquires the given number of permits from this semaphore,
 * blocking until all are available,
 * or the thread is {@linkplain Thread#interrupt interrupted}.
 *
 * <p>Acquires the given number of permits, if they are available,
 * and returns immediately, reducing the number of available permits
 * by the given amount.
 *
 * <p>If insufficient permits are available then the current thread becomes
 * disabled for thread scheduling purposes and lies dormant until
 * one of two things happens:
 * <ul>
 * <li>Some other thread invokes one of the {@link #release() release}
 * methods for this semaphore, the current thread is next to be assigned
 * permits and the number of available permits satisfies this request; or
 * <li>Some other thread {@linkplain Thread#interrupt interrupts}
 * the current thread.
 * </ul>
 *
 * <p>If the current thread:
 * <ul>
 * <li>has its interrupted status set on entry to this method; or
 * <li>is {@linkplain Thread#interrupt interrupted} while waiting
 * for a permit,
 * </ul>
 * then {@link InterruptedException} is thrown and the current thread's
 * interrupted status is cleared.
 * Any permits that were to be assigned to this thread are instead
 * assigned to other threads trying to acquire permits, as if
 * permits had been made available by a call to {@link #release()}.
 *
 * @param permits the number of permits to acquire
 * @throws InterruptedException if the current thread is interrupted
 * @throws IllegalArgumentException if {@code permits} is negative
 */
public void acquire(int permits) throws InterruptedException {
	if (permits < 0) throw new IllegalArgumentException();
	sync.acquireSharedInterruptibly(permits);
}

申请给定量许可,阻塞直到所有的许可可用

https://www.cnblogs.com/ten951/p/6212132.html
package com.learn.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemapDemo implements Runnable {
    final Semaphore semp = new Semaphore(5);//允许五个许可

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        try {
            semp.acquire();
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getId() + ":done!");
            semp.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newFixedThreadPool(20);//容量为20的线程池
        final SemapDemo demo = new SemapDemo();
        for (int i = 0; i < 20; i++) {
            exec.submit(demo);
        }

    }
}
许可的释放最好是写到finally里面,万一你忘记释放了,你的许可就凭空蒸发掉了,永远就没有了,

现在我有20个线程,但是我许可数量是5个,信号量构造的时候你可以指定许可,因此前5个线程去拿的时候呢,

必然可以一下子拿到,进来就打印这个done,但是你后面的线程,进来的时候你未必可以马上拿到,因为我这个

线程要休眠两秒钟,这是一个比较长的时间,线程的提交是很快的,我一下子把20个线程提交上来了,但是我前5个在

里面待2秒,在2秒钟之内我只能做5个,另外的15个被等待,等待完了之后呢,后面5个进来之后呢,前5个一下子做完了,

过两秒钟在一起刷一下,这个程序没有结束,是因为这个线程池没有结束掉,所有的线程不是Daemon线程,所以不会结束,

事实上工作已经完成了,一个线程可以拿若干个许可,每个线程我给他拿两个许可,这样我们可以更加灵活的去共享我们的

资源,应该分配给谁,信号量也是堆资源的一种分配,下面我们来看一下ReadWriteLock,读写锁,为什么会有读写锁这种东西呢,

对于重入锁来讲,我们传统的synchronized来说,他的锁是部分线程功能的,有些时候读和写是两种不同的操作,因此我们可以想象

一下,如果在你读这个数据当中,你不分青红皂白都去加锁,那对这个性能是有很大杀伤力的,如果你都去读的,我们可以以一种

比较开放的姿态去看这个问题,都是读的线程你就不应该加锁,大家都应该能进去,但是如果你有写的线程发生,写的事件发生,

那在这种情况下,因为写有可能修改数据,修改数据会导致你读到的数据呢,会发生不一致,因此当有写发生的时候呢,我们才有

加锁这个操作,因此从功能上面讲,我们将这个锁进行功能上的划分,使得我们性能有很大的提高,并行度能够提高,毕竟有一点,

加锁之后,并行度是1,也就是一次只有一个线程能够进去,这完全不符合我们高并发的一个概念,高并发应该是一次有好多线程

在跑,我一次只能跑一个线程,那只是传统意义上的并发,ReadWriteLock才有一点点高并发的意思在里面,我允许你很多线程

一起做read,我们讲过一个无等待的阻塞,显然我们的ReentrantLock,我们的Synchronized,都是阻塞的并行,无等待的并发,

它会把这个线程挂起,ReadWriteLock如果没有write线程发生,所有的read线程都是无等待的并发,JDK5提供的一个具有读写分离

的一个锁,我们看读写锁的情况

我写到一半,你会读到不一致的情况,所以读写之间还是要做一些互斥的操作,下面我们来看一下CountDownLatch,

从名字上可以看到,他就是一个倒数计时器,10,9,8,7,6,...0,是一个倒数,这个可以用到一个什么地方呢,比如一个

非常典型的场景呢,就是在发射火箭的时候,在发射火箭之前,可能会进行一个各项的检查,是不是符合发射的条件,

那么每一项检查呢,都可以看做有一个单独的线程去执行的,那么在这个检查的过程呢,每一个线程都有自己的检查任务,

如果我们一共有10个检查项,假设我们有10个检查项的话,每当一个线程完成自己的检查任务之后呢,他就做一个countdown,

自己任务就完成了,到达了他的一个执行目标,当所有的线程都完成任务了呢,我们的计时器就会清零,一共有10个线程去做这个

事情,他每一个做完之后呢,他就会countdown,减到0之后呢,最终等待在countdown上的主线程,比如以火箭发射为例呢,那么

主线程就是火箭发射本身,火箭发射发现所有的任务都完成了,这个时候await就会返回,不会再去等待,就返回了,就可以执行

后面的一些事情,还有一个示意图就是这样子,主线程会在这个临界线上做一个等待,等待发射

其他检查任务可能就分别执行,过程可能也需要花费一些时间,并不会马上就做完,因此主线程就在这里做等待,

所有的检查任务全部都到临界点,全部都执行完毕之后,主线程才会到这个点上全部执行,所以countdownLatch可以

简单的看成是一个栅栏,整个线程按照时间的执行上面呢,画了一条线,使得所有的线程都要到了那个点为止,我们的

主线程,他才能够继续往下走
package com.learn.thread;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchDemo implements Runnable {
    static final CountDownLatch end = new CountDownLatch(10);
    static final CountDownLatchDemo demo = new CountDownLatchDemo();

    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see Thread#run()
     */
    @Override
    public void run() {
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println("check complete");
            end.countDown();//完成 可以减1
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            exec.submit(demo);
        }
        end.await();//主线程等待所有10个线程完成任务 才能继续执行
        System.out.println("Fire!");
        exec.shutdown();
    }
}

我们有10个线程,有10个检查任务,是需要在主线程开始之前,如果没有跑完主线程会一直等待,在模拟一个任务或者检查,

检查完毕之后会打印一个检查完毕,然后countdown,表示自己完成了,做几个countdown之后,程序启动之后我们开启10个

线程,每个线程都会去做这个run,等到10个都跑完之后,这个countdown减掉之后呢,我们这个await才会返回,返回之后

就发射火箭,因为每个线程都睡了随机的时间,在这个线程完成之前,是不会做这个发射操作的,所有10个线程都完成了,

那就做这个发射操作,所以这个应用场景,其实在我们的实际业务当中是非常普遍的,要等待他的准备业务完成之后才能操作,

那么你这个准备业务怎么去通知主线程,我们就可以用countdown去通知,我都可以包装在countdownlatch上面,下面是CyclicBarrier,

循环栅栏,和CountDownLatch是非常相像的,Cyclic是循环,CountDownLatch只是一次计数,Cyclic他可以反复的使用,他可以

一批一批的去执行,比如我要做10个线程,我第二批10个线程到了之后呢,我主线程再工作一次,第三个10个线程到了之后呢,

我主线程还可以再工作一次,他就是循环的一个姿态,主要接口也非常的相像,等待所有的参与者都到达了之后,我才能够继续

往下执行

当有一个士兵到了你不能叫做集合完成,当有一个士兵到达了之后,要等待其他的士兵全部到达,才叫集合完毕,

所以这个士兵会等待所有的士兵到达,到达完毕之后呢,下达任务,所有士兵都会分别去执行自己的任务,当有一个

士兵执行完成呢,我们并不能表明我们总体任务完成,我要等待所有的任务都执行完成了,我们才能说这个任务完成了,

其实他们是可以复用同一个Cyclic的,这里10个等待完毕,这里10个任务完成,都用同一个实例

https://www.cnblogs.com/ten951/p/6212160.html
package com.learn.thread;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static class Soldier implements Runnable {
        private String soldier;
        private final CyclicBarrier cyclic;

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

        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see Thread#run()
         */
        @Override
        public void run() {
            try {
                //等待所有士兵到齐
                cyclic.await();
                doWork();
                //等待所有士兵完成工作
                cyclic.await();
            } catch (InterruptedException e) {//在等待过程中,线程被中断
                e.printStackTrace();
            } catch (BrokenBarrierException e) {//表示当前CyclicBarrier已经损坏.系统无法等到所有线程到齐了.
                e.printStackTrace();
            }
        }

        void doWork() {
            try {
                Thread.sleep(Math.abs(new Random().nextInt() % 10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soldier + ":任务完成");
        }

    }

    public static class BarrierRun implements Runnable {
        boolean flag;
        int N;

        public BarrierRun(boolean flag, int N) {
            this.flag = flag;
            this.N = N;
        }

        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see Thread#run()
         */
        @Override
        public void run() {
            if (flag) {
                System.out.println("司令:[士兵" + N + "个,任务完成!]");
            } else {
                System.out.println("司令:[士兵" + N + "个,集合完毕!]");
                flag = true;
            }
        }
    }

    public static void main(String[] args) {
        final int N = 10;
        Thread[] allSoldier = new Thread[N];
        boolean flag = false;
        CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
        //设置屏障点,主要为了执行这个方法
        System.out.println("集合队伍! ");
        for (int i = 0; i < N; i++) {
            System.out.println("士兵" + i + "报道! ");
            allSoldier[i] = new Thread(new Soldier(cyclic, "士兵" + i));
            allSoldier[i].start();
        }
    }
}
一旦调用lockSupport就会被挂起了,unpark可以继续往下执行,他和suspend一样,都是挂起,但是有个本质的不同,

Support是可以毫无顾忌的去使用,但是suspend不是,是不建议使用的,LockSupport他有一个什么好处呢,他的思想

有点类似于信号量的思想,我内部会产生一个许可的东西,那我park的时候呢,unpark就是去申请这个许可,因此他有一个

特点是说,如果不幸的发生unpark,发现发生在park之前,那我这个park并不会把这个线程给阻塞,这个和suspend是不一样的,

如果resume发生在suspend之前,现在还是会被挂起,但是如果unpark发生在park之前,那我park是挂不住的

package com.learn.thread;

import java.util.concurrent.locks.LockSupport;

public class LockSupportDemo {
    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");

    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super.setName(name);
        }

        public void run() {
            synchronized (u) {
                System.out.println("in" + getName());
                LockSupport.park();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(100);
        t2.start();
        LockSupport.unpark(t1);
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }
}
就算我unpark发生在park之前,相当于许可被拿掉了,不会吧线程给阻塞住,unpark是使得一个许可可用,park

会使得线程挂起,什么时候可以让这个线程继续往下走呢,第一种是调用unpark,第二种是中断,大部分wait函数,

他在中断之后呢,都会抛出一个中断异常,但是你要注意的是,park不会,他不会抛出中断异常,这个操作是没有

异常抛出的,但是他会响应中断,如果中断发生,这个park会立即返回,另外线程中断了当前线程,如果说这个地方

被中断了,但是从Park本身是不可以判断是否被中断的,park有这么一个特点,能够响应中断,但是不抛出异常,JDK

内部是使用的非常广泛的,它是一个比较底层的原理操作,把这个线程给挂起,内部也是使用了unsafe这个类,

/**
 * Disables the current thread for thread scheduling purposes unless the
 * permit is available.
 *
 * <p>If the permit is available then it is consumed and the call
 * returns immediately; otherwise the current thread becomes disabled
 * for thread scheduling purposes and lies dormant until one of three
 * things happens:
 *
 * <ul>
 *
 * <li>Some other thread invokes {@link #unpark unpark} with the
 * current thread as the target; or
 *
 * <li>Some other thread {@linkplain Thread#interrupt interrupts}
 * the current thread; or
 *
 * <li>The call spuriously (that is, for no reason) returns.
 * </ul>
 *
 * <p>This method does <em>not</em> report which of these caused the
 * method to return. Callers should re-check the conditions which caused
 * the thread to park in the first place. Callers may also determine,
 * for example, the interrupt status of the thread upon return.
 */
public static void park() {
	UNSAFE.park(false, 0L);
}

有些地方会直接调用unsafe的park,看一下重入锁ReentrantLock基本的实现思路

重入锁它是一个应用级的东西,不是一个系统级的东西,LockSupport它是一个系统级的东西,它是调用了一些native

的API,重入锁本身它是一个应用级实现,有一些他调用了LockSupport,直接的实现是JAVA的实现,他的实现有三个内容是

比较重要的,第一个是CAS状态,无锁的操作我们在前面也有介绍,CAS状态做什么事情呢,判断这个锁到底有没有被人占用,

比如0没有被占用,1表示被占用了,锁的本质内部是一个CAS操作,用它来修改某一个变量,这个变量能不能修改成功,第二个

是等待队列,如果我没有拿到这个锁,那我就进入一个等待的队列,多个线程要排队,内部必须要维护一个等待队列,把所有等待

在这个锁上的线程呢,都给保存起来,等待在队列上的线程呢,类似LockSupport当中的park操作,只要我进入了等待队列的线程呢,

我都要park把它挂起,我什么时候把它unpark呢,让他继续执行呢,当我前面的线程把锁unlock的时候,我就从等待队列当中挑一个出来,

来做unpark操作,这个就是ReentrantLock实现的一个根本,

/**
 * Acquires the lock.
 *
 * <p>Acquires the lock if it is not held by another thread and returns
 * immediately, setting the lock hold count to one.
 *
 * <p>If the current thread already holds the lock then the hold
 * count is incremented by one and the method returns immediately.
 *
 * <p>If the lock is held by another thread then the
 * current thread becomes disabled for thread scheduling
 * purposes and lies dormant until the lock has been acquired,
 * at which time the lock hold count is set to one.
 */
public void lock() {
	sync.lock();
}

这里有两个实现,一个公平的,一个是非公平的,我们来看一下非公平的实现

/**
 * Performs {@link Lock#lock}. The main reason for subclassing
 * is to allow fast path for nonfair version.
 */
abstract void lock();

/**
 * Sync object for non-fair locks
 */
static final class NonfairSync extends Sync {
	private static final long serialVersionUID = 7316153563782823691L;

	/**
	 * Performs lock.  Try immediate barge, backing up to normal
	 * acquire on failure.
	 */
	final void lock() {
		if (compareAndSetState(0, 1))
			setExclusiveOwnerThread(Thread.currentThread());
		else
			acquire(1);
	}

	protected final boolean tryAcquire(int acquires) {
		return nonfairTryAcquire(acquires);
	}
}

首先是compareAndSetState比较设置CAS操作,acquire这个是锁申请,

/**
 * Acquires in exclusive mode, ignoring interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
	if (!tryAcquire(arg) &&
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

加到等待队列当中去

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
	Node node = new Node(Thread.currentThread(), mode);
	// Try the fast path of enq; backup to full enq on failure
	Node pred = tail;
	if (pred != null) {
		node.prev = pred;
		if (compareAndSetTail(pred, node)) {
			pred.next = node;
			return node;
		}
	}
	enq(node);
	return node;
}

等待队列的节点是Node,Node是对线程的包装,节点可以把等待的线程信息都拿出来

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值