(二)、Java并发——JDK并发包

一、同步控制

1、synchronized:重入锁

        重入锁使用java.util.concurrent.locks.ReentranLock类实现。与synchronized相比,重入锁有着显示的操作过程。必须手动指定何时加锁,何时释放锁。也正因为这样,重入锁对逻辑控制的灵活性要远好于synchronized。当退出临界区时,必须记得释放锁,否则其他线程就没有机会再访问临界区了。
        实例:


public class ReentranLockThread implements Runnable{
    private static ReentrantLock reentrantLock=new ReentrantLock();
    static Integer i=0;

    @Override
    public void run() {
        try {
            reentrantLock.lock();
            for (int j = 0; j < 1000; j++) {
                i++;
            }
        }finally {
            reentrantLock.unlock();
        }

    }
}
    @Test
    public void ReentranLockThreadTest() throws InterruptedException {
        ReentranLockThread reentranLockThread=new ReentranLockThread();
        Thread t1=new Thread(reentranLockThread);
        Thread t2=new Thread(reentranLockThread);

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(ReentranLockThread.i);
    }

中断响应

        中断提供了一套机制。如果一个线程正在等待锁,那么他依然可以收到一个通知,被告知无须再等待,可以停止工作了。
        例子:执行下方代码后,两个线程会分别锁住l1和l2,然后再去抢l2和l1,他们就会占用了对方需要的锁资源,同时也在等对方释放锁资源,这样就造成了死锁。然后过了一秒后,线程t2主动释放锁资源,t1则成功拿到l2的锁资源继续执行代码。
        解释:interrupt():中断标志位,中断线程。
                   lockInterruptibly():获取某个锁,如果没有获取到,则进入等待,可以响应中断。
                   isHeldByCurrentThread():判断当前锁状态

@Data
public class IntLock implements Runnable{
    private static ReentrantLock l1=new ReentrantLock();
    private static ReentrantLock l2=new ReentrantLock();

    private Integer i=1;

    @Override
    public void run() {
        try{
            if(i==1){
                l1.lockInterruptibly();
                System.out.println("l1锁住");
                Thread.sleep(500);
                l2.lockInterruptibly();
                System.out.println("l2锁住");
            }else {
                l2.lockInterruptibly();
                System.out.println("l2锁住");
                Thread.sleep(500);
                l1.lockInterruptibly();
                System.out.println("l1锁住");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //判断锁状态
           if(l1.isHeldByCurrentThread()){
               l1.unlock();
               System.out.println("l1解锁");
           }
            if(l2.isHeldByCurrentThread()){
                l2.unlock();
                System.out.println("l2解锁");
            }
        }
    }
}
    @Test
    public void IntLockTest() throws InterruptedException {
        IntLock reentranLockThread1=new IntLock();
        IntLock reentranLockThread2=new IntLock();
        reentranLockThread1.setI(1);
        reentranLockThread2.setI(2);
        Thread t1=new Thread(reentranLockThread1);
        Thread t2=new Thread(reentranLockThread2);

        t1.start();
        t2.start();

        Thread.sleep(1000);
        t2.interrupt();
    }

锁申请等待限时

        避免死锁还有一种方式就是限时等待。给定一个等待时间,让线程自动放弃。可以使用tryLock()方法进行一次现时等待。
        tryLock()接收两个参数,一个是时长,一个是时间单位。此方法也可以不设置参数,那他就没有等待时间,他会立即返回是否得到锁资源的结果。
        例子:两个线程执行,去争抢锁资源,第一个拿到所资源会执行”获取到锁“那一部分代码,没有抢到锁资源会等待两秒,如果两秒后还没有获取到则直接放弃。

public class TryLock implements Runnable{
    private static ReentrantLock l1=new ReentrantLock();

    @Override
    public void run() {
        try{
            if(l1.tryLock(2, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName()+"获取到");
                Thread.sleep(5000);
                System.out.println(Thread.currentThread().getName()+"执行完毕");
            }else {
                System.out.println(Thread.currentThread().getName()+"没有获取锁资源");
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            if(l1.isHeldByCurrentThread()){
                l1.unlock();
                System.out.println(Thread.currentThread().getName()+"释放锁");
            }
        }

    }
}
    @Test
    public void TryLockTest() throws InterruptedException {
        TryLock reentranLockThread1=new TryLock();
        TryLock reentranLockThread2=new TryLock();
        Thread t1=new Thread(reentranLockThread1);
        Thread t2=new Thread(reentranLockThread2);

        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }

公平锁

        公平锁它会按照时间的先后顺序,保证先到先得。他不会产生饥饿。只要你排队,最终还是可以等到资源的。
        在创建ReentrantLock对象是带入参数true,则表示设置锁为公平锁。要实现公平锁必然要系统维护一个有序队列,因此公平锁的实现成本比较高,性能相对也非常低下,因此默认情况下,锁匙非公平的。
        例子:在创建ReentrantLock时分别设置true和不设置参数,然后使用两个线程分别进行打印输出。然后会发现设置了true的会依次按照创建线程的顺序来执行,而没有设置参数则会乱序打印。       

public class FairLock implements Runnable{
    private static ReentrantLock l1=new ReentrantLock(true);
    
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            l1.lock();
            System.out.println(Thread.currentThread().getName());
            l1.unlock();
        }

    }
}
    @Test
    public void FairLockTest() throws InterruptedException {
        FairLock reentranLockThread1=new FairLock();
        Thread t1=new Thread(reentranLockThread1);
        Thread t2=new Thread(reentranLockThread1);

        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }

总结

        方法总结:
                lock():获得锁,如果锁已经被占用,则等待。
                lockInterruptibly():获得锁,但优先响应中断。
                tryLock():尝试获得锁,如果成功返回true,失败返回false。不会等待,直接返回。
                tryLock(Long time,TimeUnit unit):在给的定时间获取锁,没有得到则返回false。
                unlock():释放锁。

2、Condition条件

        Condition与wait()和notify()方法类似。通过Lock接口的Condition newCondition()方法就可以生成一个与当前重入锁绑定的Condition实例。

        方法解释:
        await():使当前线程等待,同时释放当前锁,当其他线程中使用signal()或signalAll()方法时,线程会重新获取锁并继续执行。或者当前线程被中断时,也能跳出等待。
        awaitUninterruptibly()与await()方法相同,不过他不能在等待过程中响应中断。
        signal()方法用于唤醒一个在等待中的线程。相对的signalAll()会唤醒所有在等待中的线程。

        例子:ConditionThread在执行run方法时会进入等待,知道主线程这边去唤醒他。      
        注:在await()方法和signal()方法在执行时,都需要获取到相关的锁。可以注意到测试方法中的唤醒前后的加锁和释放锁。         

public class ConditionThread implements Runnable{
    public static ReentrantLock reentrantLock=new ReentrantLock();

    public static Condition condition= reentrantLock.newCondition();

    @Override
    public void run() {
        try{
            reentrantLock.lock();
            System.out.println("获取资源,开始等待");
            condition.await();
            System.out.println("继续执行");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            reentrantLock.unlock();
        }

    }
}
    @Test
    public void ConditionThreadTest() throws InterruptedException {
        ConditionThread reentranLockThread1=new ConditionThread();
        Thread t1=new Thread(reentranLockThread1);
        t1.start();
        Thread.sleep(1000);
        ConditionThread.reentrantLock.lock();
        ConditionThread.condition.signal();
        ConditionThread.reentrantLock.unlock();
    }

3、信号量

        内部锁synchronized或者重入锁ReentrantLock,一次都只允许一个线程访问一个资源,而信号量可以指定多个线程,同时访问某一个资源。
        主要构造函数:

   public Semaphore(int permts);

   public Semaphore(int permts,boolean faiir);     第二个参数表示是否公平  

       构造信号量对象时,必须要指定信号量的准入数,即同时能申请多少个许可。
        信号量主要方法:

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

void acquireUninterruptibly():与acquire类似,但是他不响应中断。

boolean tryAcquire():尝试获得一个许可,成功true,失败false,立即返回结果。

boolean tryAcquire(long timeout, TimeUnit unit):与tryAcquire类似,不过是有最大等待时间

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

        例子:利用线程池创建初始为20的线程池,通过for循环进行执行子线程代码,实现类创建一个最大线程5的信号量对象。

public class SemaphoreThread implements Runnable{
    final Semaphore semaphore=new Semaphore(5);

    @Override
    public void run(){
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName()+"开始执行");
            Thread.sleep(1000);
            semaphore.release();
            Thread.currentThread().interrupt();
            System.out.println(Thread.currentThread().getName()+"执行完毕");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
@Test
    public void SemaphoreThreadTest() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        SemaphoreThread reentranLockThread1=new SemaphoreThread();
        for (int i = 0; i < 20; i++) {
            executorService.execute(reentranLockThread1);
        }
        //关闭线程池
        executorService.shutdown();
        //子线程全部执行完则退出程序
        while (!executorService.isTerminated()){
        }
    }

4、读写锁

        ReadWriteLock是JDK5中提供的读写分离锁。读写分离锁可以有效的减少锁竞争,提升系统性能。读写锁允许多个线程同时读,但是写写操作和读写操作之间依然需要相互等待和持有锁。也就是只有读与读之间才不阻塞。
       
例子:ReadWritLockDemo:创建读写方法,并且枷锁。
                   ChangeThread:子线程进行调用,调用读写方法,根据传入参数判断。
                   测试类:在交叉进行读写操作,看输出结果,
                    结果:读操作没有进行阻塞,读与读之间会交替进行,但是写会是一个完整且单独的
                               执行。

public class ReadWritLockDemo {
    public static ReentrantReadWriteLock reentrantReadWriteLock= new ReentrantReadWriteLock();
    public static Lock readLock=reentrantReadWriteLock.readLock();
    public static Lock writeLock=reentrantReadWriteLock.writeLock();

    public static Integer num=0;

    public static void read(){
        try {
            readLock.lock();
            System.out.println(Thread.currentThread().getName()+"读");
            Thread.sleep(1000);
            System.out.println(num);
            System.out.println(Thread.currentThread().getName()+"读完成");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            readLock.unlock();
        }
    }

    public static void write(){
        try {
            writeLock.lock();
            System.out.println(Thread.currentThread().getName()+"写");
            Thread.sleep(1000);
            num++;
            System.out.println(Thread.currentThread().getName()+"写完成");
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            writeLock.unlock();
        }
    }

}
@AllArgsConstructor
public class ChangeThread implements Runnable{
    private Boolean isRead=true;

    @Override
    public void run() {
        if(isRead){
            ReadWritLockDemo.read();
        }else{
            ReadWritLockDemo.write();
        }
    }
}
    @Test
    public void ReadWritLockDemoTest() throws InterruptedException {
        ChangeThread read=new ChangeThread(true);
        ChangeThread write=new ChangeThread(false);

        for (int i = 0; i < 20; i++) {
            if(i%3==0){
                Thread thread = new Thread(write);
                thread.start();
            }else{
                Thread thread = new Thread(read);
                thread.start();
            }
        }

        Thread.sleep(20000);
    }

5、倒计时器

        CountDownLatch是一个多线程控制工具类。通常用来控制线程等待,他可以让某个一个线程等待直到倒计时结束,在开始执行。
        使用场景:可以让子线程执行到多少条,主线程才继续往下走。
                          多个线程同时执行一个任务。
        他的构造函数接受一个整数为参数,即这个计数个数(线程个数)。

public CountDownLatch(int count);

        方法解释

countDown:没调用一次计数器值减一

getCount:获取当前计数值

await:等待计数器清空,等待其他线程执行完毕

        例子:_01_05_CountDownLatch:设置计数线程为5。
                   测试类:运行5个线程,触发主线程继续执行。

public class _01_05_CountDownLatch extends Thread{
    static CountDownLatch countDownLatch=new CountDownLatch(5);

    @Override
    public void run(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName()+"准备好了");
        countDownLatch.countDown();
    }

}
    @Test
    public void _01_05_CountDownLatchTest() throws InterruptedException {

        for (int i = 0; i < 4; i++) {
            _01_05_CountDownLatch downLatch=new _01_05_CountDownLatch();
            downLatch.start();
        }
        _01_05_CountDownLatch.countDownLatch.await();

        System.out.println("全部执行完毕");
    }

6、循环栅栏

        CyclicBarrier是另一种多线程并发控制实用工具。他可以实现线程间的计数等待。相比于CountDownLatch他可以进行循环等待,一批线程等齐执行了,就继续进行下一批次。
        他接受两个参数,第一个:计数总数;第二个:一次计数完成后,系统会执行的动作。
        例子:一批子线程执行完后,打印一批执行完的信息,在进入下一批。
                   CyclicBarrierDemo:传入一个CyclicBarrier用作等待,run方法为子线程执行的主要方
                                                     法。
                   CyclicBarrierLast:此run方法为一批次执行完后执行的方法。
                    测试类:创建一个线程池和CyclicBarrier,循环调用并start CyclicBarrierDemo,查看
                                  打印台输出

public class CyclicBarrierDemo implements Runnable{
    public final CyclicBarrier cyclicBarrier;
    public int miao;

    public CyclicBarrierDemo(CyclicBarrier cyclicBarrier,int miao) {
        this.cyclicBarrier = cyclicBarrier;
        this.miao=miao;
    }

    @Override
    public void run() {
        try {
            System.out.println("正在执行");
            Thread.sleep(miao*500);
            System.out.println("已执行完毕,等待其他线程执行完毕");
            cyclicBarrier.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (BrokenBarrierException e) {
            throw new RuntimeException(e);
        }
    }
}
public class CyclicBarrierLast implements Runnable{
    @Override
    public void run() {
        System.out.println("一批执行完毕");
    }
}
    @Test
    public void CyclicBarrierTest() throws InterruptedException, BrokenBarrierException {
        CyclicBarrier cyclicBarrier=new CyclicBarrier(5,new CyclicBarrierLast());
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(5,10,20,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.submit(new CyclicBarrierDemo(cyclicBarrier,i));
        }

        threadPoolExecutor.shutdown();
        while (!threadPoolExecutor.isTerminated()){

        }
        System.out.println("全部执行完毕");
    }
}

7、线程阻塞工具类

        LockSupport是一个线程阻塞工具类,它可以在线程内任意位置让线程阻塞。LockSupport类使用类似信号量的机制。他为每一个线程准备了一个许可,如果许可可用,那么park()函数会立即返会,并消费这个许可,如果许可不可用,就会阻塞。unpark()则使一个许可变为可用。
        wait和notify这个两个方法必须获取到同一个对象的锁才能执行。而park和inpark这两个不需要获取到同一个的锁。而且可以先解锁,在后锁(不用担心线程之间执行的顺序)。
        例子1:使用wait和notify。子线程在锁住一个对象后,再调用wait方法,然后主线程在调用notify方法进行释放,最后子线程在开始执行。期间必须保持这种执行顺序,不然可能会导致先解锁,然后才加锁。

public class WaitThread extends Thread{
    static Object object=new Object();

    @Override
    public void run(){
        synchronized (object){
            System.out.println("子线程开始执行,并等待解锁");
            try {
                object.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("执行完毕");
        }

    }

    public void selfNotify(){
        synchronized (object){
            System.out.println("解锁");
            object.notify();
            System.out.println("解锁完毕");
        }
    }
}
    @Test
    public void te() throws InterruptedException {
        WaitThread waitThread=new WaitThread();
        waitThread.start();
        Thread.sleep(1000);
        waitThread.selfNotify();
    }

        例子2:与上方例子差不多,但是把wait和notify改为park和unpark。差别,不用再同一个对象上加锁,没有异常处理。

public class LockSupportThread extends Thread{
    static Object object=new Object();
    @Override
    public void run(){
        synchronized (object){
            System.out.println("子线程开始执行,并等待解锁");
            LockSupport.park();
            System.out.println("执行完毕");
        }

    }
}
    @Test
    public void te2() throws InterruptedException {
        LockSupportThread lockSupportThread=new LockSupportThread();
        lockSupportThread.start();
        System.out.println("主线程解锁");
        LockSupport.unpark(lockSupportThread);
        System.out.println("主线程解锁完毕");
    }

二、线程池

1、什么是线程池

        为了避免系统频繁的创建和销毁线程,可以让创建的线程进行复用。可以节约创建和销毁对象的时间。
        线程Executor主要工厂方法:
                newFixedThreadPool():该方法返回一个固定线程数量的线程池。该线程池中的线程始
                                                        终不变。当有一个新的任务提交时,线程中若有空闲线程,则
                                                        立即执行。若没有,则新的任务会被暂存在一个任务队列中,
                                                        待有线程空闲时,便处理在任务队列中的任务。
                newSingleThreadExecutor():只有一个线程的线程池。多余的线程任务进入队列排队,
                                                        后续会顺序执行。
                newCachedThreadPool():返回一个可根据实际情况调整的线程数量的线程池。线程数
                                                        量不确定,但若有空闲线程可以复用,则会优先使用可复用的
                                                        线程。若所有线程均有任务,又有新的任务,则会创建新的线
                                                        程处理任务。所有线程在当年前任务执行完毕后,将返回线程
                                                        池进行复用。
                newSingleThreadScheduledExecutor():返回一个ScheduleExecutorService对象,线程
                                                        池大小为1。这个对象添加了指定时间,可以用于周期性、延
                                                        时等任务。
                newScheduledThreadPool():与上方想同,不过可以指定线程数量。

2、内部实现

        对于上述工厂方法中的前三个方法,他们的内部都是使用了ThreadPoolExecutor实现的。都是通过new ThreadPoolExecutor()这个方法设置不同的参数实现。
        ThreadPoolExecutor这个方法的参数解释(按照顺序): 

                corePoolSize:指定线程池中的线程数量。(核心线程数)
                maximumPoolSize:指定了线程池中的最大线程数量。
                keepAliveTime:当线程池数量超过corePoolSize时,多余的空闲线程的存活时
                                        间。多余的线程,在多长时间后会被销毁。
                unit:keepAliveTime的单位。
                workQueue:任务队列,被提交但尚未被执行的任务。
                threadFactory:线程工厂,用于线程创建。
                handler:拒绝策略。当任务太多来不及处理,如何拒绝任务。     

        参数workQueue指被提交但未执行的任务队列,是一个BlockingQueue接口的对象,仅用于存放Runnable对象。可以使用下列几个队列:

直接提交的队列
        SynchronousQueue没有容量,每一个任务的新建都要等待一个相应的结束任务,同时每一个结束任务都对应一个新建任务。使用这个队列提交的任务不会被真实的保存,而总是将新任务提交给线程执行,如果没有空闲的线程,则尝试创建新的线程,如果线程数量已经到达最大值,则执行拒绝策略。因此使用这个队列,通常会设置很大的maximumPoolSize,否则容易执行拒绝策略。

有界的任务队列
        ArrayBlockingQueue有一个参数,表示该队列的最大容量。这个队列的执行顺序:实际线程数小于corePoolSize,创建新线程,否则加入等待队列,等待队列满了则创建小于maximumPoolSize的线程数量,如果大于maximumPoolSize的任务执行拒绝策略。

无界的任务队列
       
LinkedBlockingQueue。与有界相似,线程数在大于corePoolSize后,新的任务会一直放到队列里面。直到系统内存耗尽。

优先任务队列
        PriorityBlockingQueue。带有执行优先级的队列。可以控制任务的执行先后顺序。是一个特殊的无界队列。无论是无界还是有界都是按照先进先出的顺序执行的

        回顾一下前三个工厂线程创建:

newFixedThreadPool()

        他的corePoolSize和maximumPoolSize大小一样,并且使用LinkedBlockingQueue任务队列的线程池。因为对于固定大小的线程池,不存在现场数量的动态变化。他使用无界队列存放无法立即执行的任务,当任务在短时间内大量添加,可能导致内存耗尽

newSingleThreadExecutor()

        与newFixedThreadPool一样,不过他把线程数量设置为了1。

newCachedThreadPool()

        corePoolSize为0,maximumPoolSize为Integer的最大值。在没有任务时,线程池内部没有任何线程。当任务提交时,有空闲线程直接使用,没有会进入SynchronousQueue队列,这个队列是没有容量的,他会直接提交,迫使创建一个新的线程执行,线程执行完会在60秒后销毁

3、拒绝策略

        ThreadPoolExecutor的最后一个参数指定了拒绝策略。JDK提供了四种拒绝策略。

AbortPolicy

        直接抛出异常,阻止系统正常工作。
        

CallerRunsPolicy

        只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。这会导致任务提交的线程性能降低。

DiscardOledestPolicy

        丢弃最老的一个请求,并尝试再次提交当前任务。

DiscardPolicy

        丢弃无法处理的任务,不予任何处理。

        上述四种方式都实现了RejectedExecutionHandler接口,可以去实现这个接口进行自定义的拒绝策略。
        例子:RejectedExecutionHandlerDeamo:实现上述接口,复写方法。编写拒绝逻辑
                   RejectedExecutionHandlerThread:正常线程执行的代码。

public class RejectedExecutionHandlerDeamo implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println(r.toString()+"被拒绝了");
    }
}
public class RejectedExecutionHandlerThread extends Thread{
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"线程执行");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName()+"执行完毕");
    }
}
    @Test
    public void _030203Test(){
        RejectedExecutionHandlerThread rejectedExecutionHandlerThread=new RejectedExecutionHandlerThread();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(), new RejectedExecutionHandlerDeamo());
        for (int i = 0; i < 20; i++) {
            threadPoolExecutor.execute(rejectedExecutionHandlerThread);
        }

        threadPoolExecutor.shutdown();

        while (!threadPoolExecutor.isTerminated()){

        }
    }

4、自定义线程创建

        线程池的主要作用是线程复用。最开始的线程都是ThreadFactory创建的。线程池会调用ThreadFactory中的new Thread(Runnable r)类来创建。
        我们可以通过实现这个接口并复写其方法,就可以实现一些自定义的线程执行条件。复写方法只会在创建线程时执行,第一次使用这个线程时调用
        例子:ThreadFactoryDeamo:实现工厂并复写其方法,自定义了一些执行代码。
                   ThreadDeamo:子线程执行的代码。

public class ThreadFactoryDeamo implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread=new Thread(r);
        System.out.println(thread.getName()+"正在执行");
        return thread;
    }
}
public class ThreadDeamo extends Thread {
    @Override
    public void run(){
        System.out.println("子线程正在执行");
    }
}
    @Test
    public void run(){
        ThreadDeamo threadDeamo=new ThreadDeamo();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), new ThreadFactoryDeamo());
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.execute(threadDeamo);
        }

        threadPoolExecutor.shutdown();
        while (!threadPoolExecutor.isTerminated()){

        }

    }

5、扩展线程池

        如果需要对一些线程池做一些扩展,比如每个任务的开始时间和结束时间。ThreadPoolExecutor是可以做扩展的线程池。他提供了beforeExecute()、afterExecute()和terminated()三个接口对线程池进行控制。
        在ThreadPoolExecutor.Worker.runTask()方法内部提供了这样的实现。                  

beforeExecute(wt, task);
Throwable thrown = null;
try {
   task.run();
} catch (RuntimeException x) {
   thrown = x; throw x;
} catch (Error x) {
   thrown = x; throw x;
} catch (Throwable x) {
   thrown = x; throw new Error(x);
} finally {
   afterExecute(task, thrown);
}

        Worker是ThreadPoolExecutor的内部类,他实现了Runnable接口。ThreadPoolExecutor线程池中的工作线程也正是Worker实例。Worker.runTask()方法会被线程池以多线程异步调用,即Worker.runTask()会同时被多个线程访问。因此内部的beforeExecute、afterExecute也会同时被多个线程访问。
        例子:KuoZhanDeamo:继承ThreadPoolExecutor并复写beforeExecute、afterExecute。
                   ThreadDeamo:普通的Thread继承类。

public class KuoZhanDeamo extends ThreadPoolExecutor{
    public KuoZhanDeamo(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.println("线程"+t.getName()+"正在执行"+r.hashCode());
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        System.out.println("执行完毕"+r.hashCode());
    }
}
    @Test
    public void run(){
        ExecutorService executorService=new KuoZhanDeamo(5,5,5, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
        for (int i = 0; i < 10; i++) {
            ThreadDeamo threadDeamo=new ThreadDeamo();
            executorService.execute(threadDeamo);
        }
        executorService.shutdown();
        while (!executorService.isTerminated()){}
    }

6、优化线程池线程数量

        线程池的大小对系统的性能有一定的影响。过大或过小的线程数量都无法发挥最优的系统性能,但线程池的大小的确定也不需要做的非常精确,因为只要避免太大和太小两个极端就行。
        线程的计算公式:

        a=CPU数量

        b=目标CPU使用率,0<=b<=1  

        c=等待时间与计算时间的比率

        最优大小=a*b*(1+c)

7、在线程池中寻找堆栈

        例子:在线程继承类中,实现一个获取两个数的商的函数。在测试类中使用线程池进行调用测试,并且线程池提交执行线程使用submit方法。

@AllArgsConstructor
public class TestThread extends Thread{
    private Integer a;
    private Integer b;

    @Override
    public void run(){
        System.out.println(a/b);
    }
}
    @Test
    public void run(){
        ExecutorService executorService=new ThreadPoolExecutor(5,5,1, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));

        for (int i = 0; i < 5; i++) {
            executorService.execute(new TestThread(100,i));
        }
        executorService.shutdown();
        while (!executorService.isTerminated()){}

    }

结果:

总结:打印输出的结果应有5条记录,但实际只有4条,这是因为第一条的被除数为0,执行时报错了,就没有打印输出。同时控制台没有进行错误信息输出

解决方式:1、把submit改为execute。
                  2、submit执行时,把返回值接收到,并使用Future的get方法获取异常信息。
                  3、扩展ThreadPoolExecutor线程池,让它在调度任务之前,保存提交任务线程的堆栈
                        信息。

8、Fork/Join框架

        fork函数用来创建子线程,使系统进程可以多一个执行分支。join表示等待,在使用fork函数后系统多了一个执行分支,所以需要等待这个执行分支执行完毕,才能得到最终结果。
        ForkJoinPool中的ForkJoinTask任务就是支持fork分解以及join等待的任务。ForkJoinTask有两个重要的子类,RecursiveAction和RecursiveTask,他们表示没有返回值的任务和可携带返回值的任务。
        例子:计算1-20000的累加。
                ForkThread:继承RecursiveTask,复写compute方法,此方法完成计算工作。

@AllArgsConstructor
public class ForkThread extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    
    @Override
    protected Long compute() {
        long l = end - start;
        Long sum=0l;
        //小于10000不分线程累加
        if(l<10000){
            for (Long i = start; i <= end; i++) {
                sum+=i;
            }
        }else{
        //大于10000则分100个小线程进行计算
            //子线程list
            ArrayList<ForkThread> forkThreads=new ArrayList<>();
            long l1 = (l / 100)+1;
            Long first=start;
            for (int i = 0; i < 100; i++) {
                Long last=first+l1;
                if(last>end){
                    last=end;
                }
                //创建子线程对象
                ForkThread forkThread=new ForkThread(first,last);
                first+=l1+1;
                forkThreads.add(forkThread);
                forkThread.fork();
            }
            for (ForkThread forkThread : forkThreads) {
                sum+=forkThread.join();
            }

        }
        return sum;
    }
}
    @Test
    public void run() throws ExecutionException, InterruptedException {

        ForkJoinPool forkJoinPool=new ForkJoinPool();
        ForkThread forkThread=new ForkThread(0l,100002l);
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkThread);
        Long aLong = submit.get();
        System.out.println(aLong);

    }

三、JDK的并发容器

1、并发集合简介

        ConcurrentHashMap:高效的并发HashMap。一种线程安全的HashMap。
        CopyOnWriteArrayList:在读多写少的场合,这个List的性能非常好,远远好于Vector。
        ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看作一个线程安全的
                                LinkedList。
        BlockingQueue:这是一个接口,JDK内部通过链表、数组等方式实现了这个接口。表示阻塞
                                队列,非常适合用于作为数据共享的通道。
        ConcurrentSkipListMap:跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。

2、线程安全的HashMap

        ConcurrentHashMap组成。在1.7由Segment数组和HashEntry组成,数组加链表;1.8由CAS和Synchronize组成,存放数据的HashEntry改为了Node节点,他的val和next都使用了volatile修饰保证了可见性。
        8以后采用了红黑树保证了查询的效率,取消了ReentrantLock改为了Synchronize,因为8以后对Synchronize做了很大的优化。
        安全的原理。使用了分段锁技术,将数据一段一段存储,然后每一段数据配一把锁,当一个线程占用了锁访问了其中一段数据,其他段的数据可以被其他线程访问。

3、ConcurrentLinkedQueue

        他是基于链表的队列,在高并发的环境下性能最好的队列。
        他在添加元素是没有任何锁的操作,线程完全是由CAS操作和队列算法保证的。整个核心就是使用一个没有出口的for循环,直到添加成功才结束。

4、CopyOnWriteArrayList

        他在读与读、读与写之间不会加锁。他在进行修改时,并不会修改原有list,而是对原始数据进行一次复制,将修改内容写入副本中。写完之后,再将修改后的数据替换原来的数据。
        读操作:读取的代码没有任何的同步操作和锁操作,理由是内部数组array不会发生修改,只会被另外一个array替换。
        写操作:写操作使用锁,这个所仅限于写-写操作。他会先从原有数据复制一个新的数组,然后更改这个新的数组,最后把这个新的数组赋值给旧的数组。

5、BlockingQueue

        BlockingQueue是一个接口,主要实现的类有ArrayBlockingQueue和LingkedBlockingQueue。他们分别基于数组和链表实现。所以ArrayBlockingQueue适合做有界队列,而LingkedBlockingQueue适合做无界队列。
        BlockingQueue之所以适合作为数据共享的通道,其关键还在于Blocking上。当服务线程处理完成队列中的消息后,他如何知道下一条消息何时来呢?
        以ArrayBlockingQueue为例,这个类中的获取消息和插入消息的方法中有Condition对象,在消息为空时,会掉用Condition的await方法,如果这时有新的消息进来,就会调用Condition的signal方法进行线程唤醒。

6、跳表(SkipList)

        跳表是一种可以用来快速查找的数据结构,类似于平衡树。他们都可以对元素进行快速的查找。区别:对于跳表的插入和删除只需要对整个数据结构的局部进行操作即可。所以在高并发的情况下只需要锁部分数据即可。
        跳表的算法是随机的。他的本质就是维护了多个链表,并且链表是分层的。

      最底层的链表维护了所有的数据,上一层的链表都是下一层的子集,一个元素插入那些层是完全随机的。
        跳表内的所有链表的元素都是有序的。查找时可以从顶层链表开始,一旦发现被查找元素大于当前链表中的取值,就会转入下一层链表继续查找。比如找5的顺序,3(顶层)---3(二层)---5(低层)。
        跳表是一种使用空间换时间的算法。

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值