【JUC多线程与高并发】线程进阶,JDK并发包

线程进阶,JDK并发包

    • 重入锁
      • ReentranLock方法总结
    • Condition条件
    • 信号量(Semaphore)
    • ReadWriteLock读写锁
    • 倒计时器:CountDownLatch
    • 循环栅栏:CyclicBarrier
    • 线程阻塞工具类:LockSupport
  • 线程复用:线程池
    • 概念
    • Executor框架
    • 拒绝策略
    • ThreadFactory
    • 扩展线程池
  • JDK并发容器
    • 并发集合简介
    • 线程安全的HashMap
    • List的线程安全

博客地址(点击即可访问) github源码地址
深刻理解JMM(JAVA内存模型) https://github.com/zz1044063894/JMMprojcet
volatile详解 https://github.com/zz1044063894/volatile
线程基础,java并发程序基础 https://github.com/zz1044063894/thread-base
线程进阶,JDK并发包 https://github.com/zz1044063894/JDK-concurrency
多线程进阶,性能优化之锁优化 https://github.com/zz1044063894/lock-optimization
线程进阶,性能优化之无锁 https://github.com/zz1044063894/no-lock# 多线程的团队合作

同步控制是并发程序必不可少的。前面我们已经介绍了synchronized关键字,它决定了一个线程是否可以访问临界区资源。同事obj.wait()和obj.notify()方法起到了重要的作用。接下来我们介绍他们的替代品。

重入锁

重入锁完全可以代替synchronized关键字,在JDK5.0的再去版本中,重入锁的性能远远高于synchronized,但是从JDK6.0开始,JDK在synchronized做了大量的优化,使得两者的性能差距不大
在之前的博客中,我们做了一个10个线程进行10000次i++的操作,接下来我们使用重入锁做同样的操作

package com.jingchu.juc.relock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @description: 重入锁测试
 * @author: JingChu
 * @createtime :2020-07-21 10:07:39
 **/
public class MyRELock implements Runnable {
    private static int i = 0;
    public static ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        for (int k = 0; k < 10000; k++) {
            reentrantLock.lock();
            try{
                i++;
            }finally {
                reentrantLock.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 MyRELock());
            threads[j].start();
        }
        for (int j = 0; j < 10; j++) {
            threads[j].join();
        }

        System.out.println(i);
    }
}

上述代码使用重入锁保护临界资源i,确保多线程对i的安全性,从以上代码可以看出,与synchronized相比lock有着明显的操作过程。开发人员必须手动指定何时加锁,何时释放锁。也正是这个原因,重入锁比synchronized更加灵活。注意:在退出临界区时,记得释放锁,否则其他线程无法获得锁,无法访问临界区

至于重入锁这个名字的由来,是因为他支持在一个线程内反复加同一个锁。上面的加锁过程可以改写成如下代码
 reentrantLock.lock();
reentrantLock.lock();
try{
	 i++;
}finally {
	 reentrantLock.unlock();
	reentrantLock.unlock();
}

除了上述功能外,重入锁还提供了了其他功能

  • 中断响应
    对于synchronized来说,只有两种情况,要么获得获得锁继续执行,要么等待锁。而重入锁提供了另外一个种可能:中断。也就是在等待锁的过程中,可以取消锁。但是这个时候取消锁是会收到一个通知,告诉线程可以不用工作了。就比如你和朋友约好了一起吃饭,在约定地点等待了很久,突然收到了来电,说临时有事,无法赴约,你就可以做其他事情了,或者停止运行。
    下面的代码产生了死锁,但由于锁中断,我们可以轻易的解决这个死锁问题。
package com.jingchu.juc.relock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @description: 死锁的锁中断解决
 * @author: JingChu
 * @createtime :2020-07-21 10:28:25
 **/
public class LockInterrupt implements Runnable {
    public static ReentrantLock reentrantLock1 = new ReentrantLock();
    public static ReentrantLock reentrantLock2 = new ReentrantLock();
    int lock;

    /**
     * 控制加锁顺序,方便构成死锁代码
     *
     * @param lock
     */
    public LockInterrupt(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            if (lock == 1) {
                reentrantLock1.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                reentrantLock2.lockInterruptibly();
            }else {
                reentrantLock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                reentrantLock1.lockInterruptibly();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (reentrantLock1.isHeldByCurrentThread()) {
                reentrantLock1.unlock();
            }
            if (reentrantLock2.isHeldByCurrentThread()) {
                reentrantLock2.unlock();
            }
            System.out.println(Thread.currentThread().getId()+"退出线程");
        }
    }

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

结果:【JUC多线程与高并发】线程进阶,JDK并发包_第1张图片

可以看出中断后两个线程都退出了,但是真正完成工作的只有t1,而t2线程则放弃了起任务直接退出,释放资源。

  • 锁申请等待限时
    除了外部通知外,要避免死锁还有一个方法,就是限时等待。比如某宝,某东都有一个机制,等待你支付,订单支付还剩多少时间,如果在一定时间内你没有支付,那么程序就认为你是失败了。这个订单也就被取消了。商家那边会收到一个通知,告知这个订单不需要配货发货了。
package com.jingchu.juc.relock;

import org.omg.PortableServer.THREAD_POLICY_ID;

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

/**
 * @description: 锁限时等待
 * @author: JingChu
 * @createtime :2020-07-21 10:51:14
 **/
public class LockWaitTime implements Runnable{
    public static ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        try {
            if(reentrantLock.tryLock(3, TimeUnit.SECONDS)){
                Thread.sleep(4000);
            }else {
                System.out.println("获取锁失败");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            if(reentrantLock.isHeldByCurrentThread()){
                reentrantLock.unlock();
            }
            System.out.println(Thread.currentThread().getId()+"线程运行结束");
        }
    }

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

【JUC多线程与高并发】线程进阶,JDK并发包_第2张图片
在上面的例子中,由于占用锁的线程会持续4秒,获取锁的线程3秒内获取不到锁,因此会获取锁失败。

tryLock()方法可以不带参数,这时,线程会尝试获取锁,如果锁没有被其他线程占用,则申请锁成功,并返回true,如果锁被占用,不会继续等待,而是立即返回false,这种模式不会引起线程等待,因此不会产生死锁。
  • 公平锁
    大多数情况下,锁的申请都是非公平的。也就是说,线程1首先申请了锁A,接着线程2申请锁A。当锁A空闲时,系统会随机挑选一个线程让他占有锁。就好比早餐店不管先来后到,随便的把包子给其中一个人,这当然是不合理的。这样产生的锁是不公平的。ReentranLock提供了公平锁的构造方法。
package com.jingchu.juc.relock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @description: 公平竞争锁(先来后到)
 * @author: JingChu
 * @createtime :2020-07-21 11:06:05
 **/
public class LockFair implements Runnable {
    public static ReentrantLock reentrantLock = new ReentrantLock(true);
    @Override
    public void run() {
        while (true) {
            try {
                reentrantLock.lock();
                System.out.println(Thread.currentThread().getName() + "获取到锁");
            }finally {
                reentrantLock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        LockFair lockFair = new LockFair();
        Thread t1 = new Thread(lockFair,"A");
        Thread t2 = new Thread(lockFair,"B");
        t1.start();
        t2.start();
    }
}

运行结果:【JUC多线程与高并发】线程进阶,JDK并发包_第3张图片
必然是AB循环获得锁,如果没有公平锁的话,有可能连续的A或者连续的B。

ReentranLock方法总结

lock();//获得锁,如果锁被占用,则等待。
lockInterruptibly();//获得锁,有限响应中断
tryLock();//尝试获得锁,成功true,失败false,不等待。
tryLock(Long time,TimeUnit unit);//在给定时间内尝试获得锁
unlock();//释放锁

Condition条件

前面的博客我们已经讲过了obj.wait和obj.nofity,Condition对象和他们的作用大致相同。wait和notify和synchronized一起使用,condition和重入锁一起使用。下面直接上代码:

package com.jingchu.juc.condition;

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

/**
 * @description: Condition条件
 * @author: JingChu
 * @createtime :2020-07-21 11:22:38
 **/
public class MyCondition implements Runnable {
    public static ReentrantLock reentrantLock = new ReentrantLock();
    public static Condition condition = reentrantLock.newCondition();

    @Override
    public void run() {
        reentrantLock.lock();
        try {
            condition.await();
            System.out.println("线程正在运行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyCondition myCondition = new MyCondition();
        Thread t1 = new Thread(myCondition);
        t1.start();
        Thread.sleep(2000);
        reentrantLock.lock();

        condition.signal();
        reentrantLock.unlock();


    }
}

首先在run方法内,我们让线程进行等待,然后释放锁。在main方法中他出通知,并给予锁。程序才能继续运行。

Codition提供的基本方法如下:

await() throws InterruptedException;//使当前线程等待,同时释放当前锁,当其他线程调动signal()或者signalAll()方法时,线程会重新执行,类似wait()和notify()
awaitUninterruptibly();//基本于awati方法相同,但是不会咋等待中响应中断
sigal();//唤醒一个等待中的线程
sigalAll();//随机唤醒一个等待中的线程。

信号量(Semaphore)

信号量为多线程协作提供了更为强大的控制方法。广义上来说,信号量是对锁的扩展。锁都只运行访问一个资源,而信号量却可以指定过个线程,同时访问一个资源。

//JDK构造方法
Semaphore(int permits) ;//创建一个 Semaphore与给定数量的许可证和非公平公平设置。  
Semaphore(int permits, boolean fair) ;//创建一个 Semaphore与给定数量的许可证和给定的公平设置。  
//对象内置方法
acquire();//从该信号量获取许可证,阻止直到可用,或线程为 interrupted 。 
acquireUninterruptibly();//从这个信号灯获取许可证,阻止一个可用的。
availablePermits();//返回此信号量中当前可用的许可数。  
tryAcquire();//从这个信号量获得许可证,只有在调用时可以使用该许可证。 
tryAcquire(long timeout, TimeUnit unit);//如果在给定的等待时间内可用,并且当前线程尚未 到达 interrupted,则从该信号量获取许可。 
package com.jingchu.juc.semaphore;

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

/**
 * @description: 信号量测试
 * @author: JingChu
 * @createtime :2020-07-21 14:34:39
 **/
public class MySemaphore implements Runnable {
    final Semaphore semaphore = new Semaphore(5);

    @Override
    public void run() {
        try {
            semaphore.acquire();
            //模拟线程逻辑代码耗时
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "线程运行结束");
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        final MySemaphore mySemaphore = new MySemaphore();
        for (int i = 0; i < 20; i++){
            executorService.submit(mySemaphore);
        }
    }
}

ReadWriteLock读写锁

读写锁是JDK5中提供的读写分离锁。读写分离锁可以有效的帮助减少锁竞争,以提高性能。

^
非阻塞 阻塞
阻塞 阻塞

如果在系统中读操作的次数远远大于写操作,那么读写锁就可以发挥更大的作用,提供系统的性能。

package com.jingchu.juc.write.read.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @description: 读写锁测试
 * @author: JingChu
 * @createtime :2020-07-21 14:53:37
 **/
public class MyReadWriteLock {
    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 int value;

    public Object read(Lock lock) {
        lock.lock();
        try {
            Thread.sleep(1000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return value;
    }

    public void write(Lock lock, int index) {
        lock.lock();
        try {
            Thread.sleep(1000);
            value = index;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
        Runnable readRunnable = new Runnable() {
            @Override
            public void run() {
                myReadWriteLock.read(readLock);
                myReadWriteLock.read(lock);
            }
        };
        Runnable writeRunnable = new Runnable() {
            @Override
            public void run() {
                myReadWriteLock.write(writeLock, (int) (System.currentTimeMillis() % 1000));
                myReadWriteLock.write(lock, (int) (System.currentTimeMillis() % 1000));
            }
        };
        for (int i = 0; i < 9; i++) {
            new Thread(readRunnable).start();
        }

        new Thread(writeRunnable).start();

    }
}

上述代码写了两个线程类,分别约执行1秒钟,他们分别对应读和写。使用读写锁实际运行时间约为1秒钟,但是如果我们使用普调的锁代替读写锁,将约耗时10秒或者更长

倒计时器:CountDownLatch

CountDownLatch在多线程中的意思是倒计时器,就是说倒计时结束以后线程开始运行

package com.jingchu.juc.count.down;

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

/**
 * @description: 倒计时器测试
 * @author: JingChu
 * @createtime :2020-07-21 16:15:40
 **/
public class MyCountDown implements Runnable {
    static final CountDownLatch end = new CountDownLatch(5);
    static final MyCountDown myCountDown = new MyCountDown();

    @Override
    public void run() {
        try {
            Thread.sleep(100);
            System.out.println("检查子任务");
            end.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i <5 ; i++) {
            executorService.submit(myCountDown);
        }
        //等待检查
        end.await();
        //执行任务
        executorService.shutdown();
        System.out.println("开始最终任务");
    }
}

【JUC多线程与高并发】线程进阶,JDK并发包_第4张图片
当所有的子任务检查完毕,主线程才开始运行实例代码。

循环栅栏:CyclicBarrier

CyclicBarrier是另外一种多线程并发控制使用工具。和CountDownLatch非常相似,它也可以实现线程间的计数等待,但是他的功能比CountDownLatch更加强大,也更加复杂。
CyclicBarrier翻译为循环栅栅。栅栏(Barrier)在我们生活中是为了阻碍一些行为,比如羊圈的栅栏是为了阻碍随便初入。循环(Cyclic)就不用笔者多过解释了。在这里的意思就是这个计数器可以反复使用。比如我们要求4个员工给可乐装箱,每24瓶一箱,每次得到4个人以后就将计数器归零,然后等着凑齐下一批4个员工。

package com.jingchu.juc.cyclic.barrier;

import com.sun.org.apache.xpath.internal.operations.Bool;

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

/**
 * @description: 循环栅栏测试
 * @author: JingChu
 * @createtime :2020-07-21 16:35:33
 **/
public class MyCyclicBarrier {
    public static class ColaMan implements Runnable{
        private String colaMan;
        private final CyclicBarrier cyclicBarrier;

        public ColaMan(String colaMan, CyclicBarrier cyclicBarrier) {
            this.colaMan = colaMan;
            this.cyclicBarrier = cyclicBarrier;
        }

        @Override
        public void run() {
            try {
                //等待所有可乐凑齐
                cyclicBarrier.await();
                packing();
                //等待所有可乐装箱完毕
                cyclicBarrier.await();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        void packing(){
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(colaMan+":装箱完毕");
        }
    }
    public static class Boss implements Runnable{
        Boolean flag;
        int n;

        public Boss(Boolean flag, int n) {
            this.flag = flag;
            this.n = n;
        }

        @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 = 4;
        Thread[] allMan = new Thread[n];
        boolean flag = false;
        CyclicBarrier cyclic = new CyclicBarrier(n,new Boss(flag,n));
        System.out.println("召集员工");
        for (int i=0;i<n;i++){
            System.out.println("员工"+i+"报道");
            allMan[i] = new Thread(new ColaMan("员工号"+i,cyclic));
            allMan[i].start();
        }
    }
}

【JUC多线程与高并发】线程进阶,JDK并发包_第5张图片

线程阻塞工具类:LockSupport

用于创建锁和其他同步类的基本线程阻塞原语。 这个工具类可以再任何地方阻塞线程。
还记得上一章中的suspend()永久卡死线程的例子吗?,我们可以用线程阻塞类重写一下

package com.jingchu.juc.lock.support;

import java.util.concurrent.locks.LockSupport;

/**
 * @description: 线程阻塞工具类测试
 * @author: JingChu
 * @createtime :2020-07-21 16:59:16
 **/
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(name);
        }
        @Override public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("被中断了");
                }
                System.out.println("继续执行");
            }
        }
    }

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

【JUC多线程与高并发】线程进阶,JDK并发包_第6张图片
我们发现程序正常运行了。这是因为工具类类似信号量的机制,它为每个线程准备了一个许可,如果许可可用,那么park函数会立即返回,而且会讲许可变为不可用,不可用后就会阻塞。而unpark则使得一个许可变成可用,和信号量不同的是,许可不可以得加,你不能拥有超过一个许可,它最多只有1个
下面看一下这个工具类的主要方法:

public static void park(Object blocker); // 暂停当前线程
public static void parkNanos(Object blocker, long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(Object blocker, long deadline); // 暂停当前线程,直到某个时间
public static void park(); // 无期限暂停当前线程
public static void parkNanos(long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(long deadline); // 暂停当前线程,直到某个时间
public static void unpark(Thread thread); // 恢复当前线程
public static Object getBlocker(Thread t);

线程复用:线程池

概念

我们都知道,当线程运行结束以后一般都会自动销毁,这是一个很耗费资源的过程。为了避免系统频繁的创建和销毁线程,我们应该尽可能的让创建的线程可进行复用。这就类似于数据库连接池操作,为了避免每次查询都重新建立连接,查询结束后销毁连接,我们可以使用数据库连接池维护一些数据库连接,让他们长期保持在激活状态。线程池也是类似的理念,简单的来说,使用了线程池后,创建线程变成了从线程池中获取空闲线程,

Executor框架

我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。

1、Executor框架包括3大部分:

(1)任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;

(2)任务的执行。也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口   的ExecutorService接口。

(3)异步计算的结果。包括Future接口及实现了Future接口的FutureTask类。

文章https://www.cnblogs.com/liwangcai/p/11884690.html对Executor框架的讲解十分细致,我就不过多讲述了

拒绝策略

ThreadPoolExecutor的最后一个参数指定了拒绝策略,也就是当任务数量超过了系统实际承载的能力时的处理策略。
JDK内置了四种拒绝策略:

  • AbortPolicy策略:该策略直接抛出异常,阻止系统的正常工作
  • CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。这样做不会真的丢弃任务,但是任务提交的性能会急速下降。
  • DiscardOledestPolicy策略:该策略是丢弃最老的一个请求。
  • DiscardPolicy策略:该策略默默丢弃无法处理的任务。
    以上内置的策略都实现了RejectedExecutionHandler接口,如果上述策略无法满足实际应用,可以自己扩展接口

ThreadFactory

通过前面的介绍,我们知道线程池的主要作用就是为了线程的复用,那么线程从何而来呢?答案就是ThreadFactory。
自定义线程池可以帮助我们做很多事情,比如:我们可以追中线程池在核实创建了多少线程,也可以自定义线程的名称、组等信息,甚至可以设置线程为守护线程。总而言之,自定义线程池非常的自由。

package com.jingchu.juc.thread.factory;


import java.util.concurrent.*;

/**
 * @description: 自定义线程池的测试
 * @author: JingChu
 * @createtime :2020-07-21 17:30:03
 **/
public class MyThreadFactory {
    public static class MyTask implements Runnable {

        @Override
        public void run() {
            System.out.println("**********:" + Thread.currentThread().getName());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyTask task = new MyTask();
        ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MICROSECONDS,
                new SynchronousQueue<Runnable>(), new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("AAAA");
                System.out.println("c创建线程"+thread);
                return thread;
            }
        });
        for (int i = 0; i < 5; i++) {
            executorService.submit(task);
        }
        Thread.sleep(1000);
    }
}

扩展线程池

虽然JDK已经为我们提供了很方便的线程池,但是我们有些业务场景仍然需要对线程池进行扩展,比如我们想监控线程的运行时间,查看开始时间和结束时间等。
ThreadPoolExecutor是一格可以扩展的线程池,它提供了beforeExecture()和afterExecute()和terminated()三个接口对线程池进行控制。下面请看例子:

package com.jingchu.juc.extend.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @description: 扩展线程池测试
 * @author: JingChu
 * @createtime :2020-07-21 17:40:18
 **/
public class MyPool {
    public static class MyTask implements Runnable {

        public String name;

        public MyTask(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println("正在执行" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MICROSECONDS,
                new LinkedBlockingDeque<Runnable>()) {
            @Override
            protected void beforeExecute(Thread t, Runnable r) {
                System.out.println("准备执行" + ((MyTask) r).name);
            }

            @Override
            protected void terminated() {
                System.out.println("线程池退出");
            }

            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                System.out.println("结束执行" + ((MyTask) r).name);
            }
        };
        for (int i = 0; i < 5; i++) {
            MyTask task = new MyTask("AAAAA");
            executorService.execute(task);
            Thread.sleep(10);
        }
        executorService.shutdown();
    }
}

【JUC多线程与高并发】线程进阶,JDK并发包_第7张图片
可以看到,所有任务执行前、执行后,以及执行结束都可以轻易的捕获,这在调试程序的时候非常有帮助。

JDK并发容器

除了提供诸多线程方法以外,JDK还为开发人员提供了很多好用的容易类,可以大大减少开发公布工作量。众所周知,算法+数据结构组成了我们的程序、
本小节,笔者将为大家介绍这些数据结构。

并发集合简介

  • ConcurrentHashMap: 一个线程安全的HashMap类
  • CopyOnWriteArrayList:一个高性能,线程安全的ArrayList,在读写的时候比Vector性能更好
  • ConcurrentLinkedQueue:高效的并发对联,使用链表实现,可以认为是线程安全的LinkedList
  • BlockingQueue:一个接口,JDK内部通过链表、数组等方式实现,表示阻塞队列,适用于数据共享的通道
  • ConcurrentSkipListMap:跳表的实现。是一个Map,视同调表的数据结构进行快速查询
  • Vector:线程安选的ArrayList

线程安全的HashMap

之前我们说过HashMap在并发情况下带来的问题,程序中肯定不允许出现这样的问题,我们可以使用Collections.synchronziedMap()方法来包装我们的HashMap

public static Map map = Collections.synchronizedMap(new HashMap());

【JUC多线程与高并发】线程进阶,JDK并发包_第8张图片

List的线程安全

List同上

public static List<String> list= Collections.synchronizedList(new LinkedList<String>());

此时生成的List对象就是线程安全的了。

你可能感兴趣的:(JAVA,多线程,java,jdk,多线程)