• 多线程高并发整套学习笔记(含源码)


    一,基础概念
        什么是线程
            进程里最小的执行单元,程序里不通的执行路径
        线程实现
            继承thread
            实现runnable
            实现callable
            用过线程池创建 
            ExecutorServices=Executors.newCachedThreadPool();
            s.execute(()->{System.out.println(Thread.currentThread().getName());});
        常用方法
            yield 指让出一下CPU
            sleep 指休眠固定时间
            join   只让固定线程插队执行
        线程状态
            new 
            runnable包含两个状态ready running 
            timedWaiting 
                t.sleep(500);
                o.wait(500);
                t.join(500);
                LockSupport.parkNanos(500);
                LockSupport.parkUntil(200);
            waiting
                o.wait();
                o.notify();
                o.notifyAll();
                t.join(t1);
                LockSupport.park();
                LockSupport.unpark(t1);
            blocked 
        线程同步
            synchronized,保证数据一致性
            方法
                锁一个方法
                public class T{
                privateint count=10;
                public synchronized void m(){
                    count--;
                    System.out.println(Thread.currentThread().getName()+"count="+count);
                    }
                }
            对象加锁
                synchronized(this)锁当前对象
                public class T{
                private int count=10;
                private Object o=new Object();
                public void m(){
                synchronized(o){
                    count--;
                    System.out.println(Thread.currentThread().getName()+"count="+count);
                        }
                    }
                }
        synchronized锁升级
            偏向锁,对象为32位,前两位记录线程id,有线程进来的话进行判断,有过线程id相同,就继续执行
            自旋锁,如果线程id不同就等待,自旋10圈
            重量级锁,自旋10圈之后直接请求操作系统
            执行时间短,线程数少用自旋锁
            执行时间长,线程数多,用系统锁
        synchronized同步方法与非同步方法
            可以相互调用
        synchronized锁重入
            都是可重入的,sleep,yield,join
        异常与锁
            异常发生后释放锁,不影响其他线程
        volatile
            保证线程间可见,MESI缓存一致性协议
            防止指令重排序
        AtomicXXX
            本身是线程安全的,针对加锁专门设计的
        wait notify(面试高频)
            等待,唤醒
            可以替代为LockSupport的park和unpark
    二,JUC同步工具
        cas自旋原理
            AtomicInteger底层是调用的UnSafe调用的是CompareAndSetI方法
        ReentrantLock可重入锁
            和syncronized一样的功能,只是在需要加锁的代码前写lock.lock()在finally里面释放锁lock.unlock()
            public class T01_ReentrantLock2 {
                Lock lock= new ReentrantLock();
                void m1(){
                    try {
                        lock.lock();//synchronized (this)
                        for (int i = 0; i < 10; i++) {
                            SleepUtil.sleepSecond(1);
                            System.out.println(i);
                        }
                    }finally {
                        lock.unlock();
                    }
                }
                void m2(){
                    try {
                        lock.lock();
                        System.out.println("m2...");
                    }finally {
                        lock.unlock();
                    }
                }
                public static void main(String[] args) {
                    T01_ReentrantLock2 r1= new T01_ReentrantLock2();
                    new Thread(r1::m1).start();
                    SleepUtil.sleepSecond(1);
                    new Thread(r1::m2).start();
                }
            }
            ReentrantLock有tryLock()方法进行尝试锁定,用返回值判断是否锁定,不管锁定与否,方法继续执行
            public class T01_ReentrantLock3 {
                Lock lock= new ReentrantLock();
                void m1(){
                    try {
                        //加锁
                        lock.lock();//synchronized (this)
                        for (int i = 0; i < 10; i++) {
                            SleepUtil.sleepSecond(1);
                            System.out.println(i);
                        }
                    }finally {
                        //finally解锁
                        lock.unlock();
                    }
                }
                /**
                 * ReentrantLock比较强大,可以使用tryLock进行尝试锁定,不管锁定与否,方法继续执行,
                 * 可以根据ReentrantLock返回值来判断是否锁定
                 * 也可以指定truLock的时间
                 */
                void m2(){
                            boolean locked =false;
                    try {
                        //尝试获取锁
                        locked = this.lock.tryLock(5, TimeUnit.SECONDS);
                        System.out.println("m2..."+locked);
                //lock.lockInterruptibly();//可以对interrupt方法做出响应
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        //finally解锁
                        if (locked)lock.unlock();
                    }
                }
                public static void main(String[] args) {
                    T01_ReentrantLock3 r1= new T01_ReentrantLock3();
                    new Thread(r1::m1).start();
                    SleepUtil.sleepSecond(1);
                    new Thread(r1::m2).start();
                  //t2.interrupt();//打断线程2的等待
                }
            }
        condition条件等待与通知
        一个condition等待与通知,与wait(),notify类似
            private Lock lock=new ReentrantLock();
            private Condition condition=lock.newCondition();
            condition.await();
            condition.signalAll();
        多个condition等待与通知和生产者消费者类似
            /**
             * 写一个固定容量同步容器,拥有put()get()方法,以及getCount方法,
             * 能够支持两个生产者线程以及10个消费者线程阻塞调用
             * @param <T>
             */
            public class MyContainer2<T> {
                final private LinkedList<T> lists= new LinkedList<>(); //初始化容器
                final private int MAX=10;//最大容量
                private int count=0;//表示当前容量
                private Lock lock= new ReentrantLock();
                private Condition producer=lock.newCondition();
                private Condition consumer=lock.newCondition();
            
                /**
                 * 生产者
                 * @param t
                 */
                 public void put(T t){
                        try {
                            lock.lock();
                            while (lists.size()==MAX){//用while的目的是要保证list的size不能为负数
                                producer.await();//生产者等待
                            }
                            lists.add(t);
                            ++count;
                            consumer.signalAll();//通知消费者线程进行消费
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }finally {
                            lock.unlock();
                        }
                 }
                /**
                 * 消费者
                 * @return
                 */
                 public T get(){
                    T t= null;
                        try {
                            lock.lock();
                            while (lists.size()==0) {//用while的目的是要保证list的size不能为负数
                                consumer.await();
                            }
                            t=lists.removeFirst();//消费第一个
                            count--;
                            producer.signalAll();//通知生产者线程进行生产
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }finally {
                            lock.unlock();
                        }
                    return t;
                }
                public static void main(String[] args) {
                    MyContainer2<String> c= new MyContainer2();
                    //启动消费者线程
                    for (int i = 0; i < 10; i++) {
                        new Thread(()->{
                            for (int j = 0; j < 5; j++) {
                                System.out.println(c.get());
                            }
                        },"c"+i).start();
                    }
                    SleepUtil.sleepSecond(2);
                    //启动生产者线程
                    for (int i = 0; i < 2; i++) {
                        new Thread(()->{
                            for (int j = 0; j < 25; j++) {
                                c.put(Thread.currentThread().getName()+" "+j);
                            }
                        },"p"+i).start();
                    }
                }
            }
        CountDownLatch门闩
            latch.countDown();主要用于线程间倒数
            public class T05_CountDownLatch {
                volatile List list = new ArrayList();
            //    volatile List list = Collections.synchronizedList(new ArrayList<>());
                public void add (Object o){
                    list.add(o);
                }
                public int size(){
                    return list.size();
                }
                public static void main(String[] args) {
                    T05_CountDownLatch c= new T05_CountDownLatch();
                    CountDownLatch latch= new CountDownLatch(1);
                    new Thread(()->{
                            System.out.println("t2 启动");
                                if (c.size()!=5){
                                    try {
                                        latch.await();
                                    } catch (InterruptedException e) {
                                        e.printStackTrace();
                                    }
                                }
                                System.out.println("t2 结束");
                    },"t2").start();
                    SleepUtil.sleepSecond(1);
                    new Thread(()->{
                            for (int i = 0; i < 10; i++) {
                                c.add(new Object());
                                System.out.println("add "+i);
                                if (c.size()==5){
                                    latch.countDown();
                                    //释放锁,让t2执行
                                    try {
                                        latch.await();
                                    } catch (InterruptedException e) {
                                        e.printStackTrace();
                                    }
                                }
                                SleepUtil.sleepMillis(100);
                            }
                    },"t1").start();
            
                }
            }
        CyclicBarrier分批执行,每一批执行一次
            /**
             * 一个栅栏,循环使用,什么时候人满了,栅栏打开,放大家过关
             * CyclicBarrier循环栅栏,一直监听,到达指定容量,就放行
             * barrier.await();监听是否到达设置的容量20,够20人就发一次车,不到就一直等着
             */
            public class T07_TestCyclicBarrier {
                public static void main(String[] args) {
                    CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人发车"));
                    for (int i = 0; i < 105; i++) {
                        new Thread(()->{
                            try {
                                barrier.await();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            } catch (BrokenBarrierException e) {
                                e.printStackTrace();
                            }
                        }).start();
                    }
                }
            }
    三,同步容器
        各个容器的特性
            list可以按索引位置取数据
            set去重
            map键值对存取数据
            让容器变得安全(加锁工具类)
    Collections.synchronizedList(list);
            Collections.synchronizedSet(set);
            Collections.synchronizedMap(map);
            Queue为高并发设计
            
    四,线程池
        线程池7个参数介绍
        ThreadPoolExecutor tpe=new ThreadPoolExecutor(
                            2,//核心线程数
                  4,//最大线程数
                            60,//生存时间
                            TimeUnit.SECONDS,//生存时间的单位
                            new ArrayBlockingQueue<Runnable>(4),//任务队列,最多可以装4个任务
                            Executors.defaultThreadFactory(),//线程工厂,可以指定线程名称
                            new ThreadPoolExecutor.CallerRunsPolicy()
                /**拒绝策略,四种:
                *abort,抛异常
                *discard,扔掉,不抛异常
                *discardOldest,扔掉排队时间最久的
                *callerRuns调用者处理服务
                */
        
        );
        for (int i = 0; i < 8; i++) {
                    tpe.execute(new Task(i));
                }
        //缓存线程池,没有核心线程数,最大线程数可以有好多,60秒没有调用就回收线程,使用默认工厂和拒绝策略,来一个任务我给你启动一个线程,
        ExecutorService service = Executors.newCachedThreadPool();
        Future<String> submit = service.submit(call);
        //单线程线程池,保证扔进去的任务按顺序执行
        ExecutorService s = Executors.newSingleThreadExecutor();
        s.execute(()->{System.out.println(j+Thread.currentThread().getName());})
        //固定大小线程池,可以并行计算,把计算任务分成多个小段执行
        ExecutorService service = Executors.newFixedThreadPool(4);
        MyTask t1 = new MyTask(1, 80000); //MyTask实现了Callable
        Future<List<Integer>> f1 = service.submit(t1);
        //定时任务线程池
        scheduleAtFixedRate间隔多长时间在一定的频率上来执行这个任务
        第一个参数是delay
        第二个参数是间隔多长时间period
        第三个参数是时间单位timeunit
        ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
        service.scheduleAtFixedRate(()->{System.out.println(Thread.currentThread().getName());},0,500, TimeUnit.MILLISECONDS);
        MyTask t1 = new MyTask(1, 80000);
        Future<List<Integer>> f1 = service.submit(t1);
         
        ExecutorService service = Executors.newWorkStealingPool();
        service.execute(new R(1000));
        ForkJoinPool
        public class T12_ForkJoinPool {
            static int[] nums = new int[1000000];
            static final int MAX_NUM = 50000;
            static Random r = new Random();
            static {
                for(int i=0; i<nums.length; i++) {
                    nums[i] = r.nextInt(100);
                }
                System.out.println("---" + Arrays.stream(nums).sum()); //stream api
            }
            static class AddTask extends RecursiveAction {
                int start, end;
        
                public AddTask(int start, int end) {
                    this.start = start;
                    this.end = end;
                }
                @Override
                protected void compute() {
                    if (end-start<=MAX_NUM){
                        long sum=0L;
                        for (int i = start ; i <end ; i++) {
                            sum+=nums[i];
                        }
                        System.out.println("from "+start+" to: "+end+" = "+sum);
                    }else {
                        int middle = start+(end-start)/2;
        
                        AddTask addTask1= new AddTask(start, middle);
                        AddTask addTask2= new AddTask(middle, end);
                        addTask1.fork();
                        addTask2.fork();
                    }
                }
            }
            static class AddTaskRet extends RecursiveTask<Long> {
                private static final long serialVersionUID = 1L;
                int start, end;
        
                public AddTaskRet(int start, int end) {
                    this.start = start;
                    this.end = end;
                }
                @Override
                protected Long compute() {
                    if (end-start<=MAX_NUM){
                        long sum=0L;
                        for (int i = start ; i <end ; i++) {
                            sum+=nums[i];
                        }
                        return sum;
                    }
                        int middle = start+(end-start)/2;
                        AddTaskRet addTask1= new AddTaskRet(start, middle);
                        AddTaskRet addTask2= new AddTaskRet(middle, end);
                        addTask1.fork();
                        addTask2.fork();
                    return addTask1.join()+addTask2.join();
                }
            }
            public static void main(String[] args) {
                T12_ForkJoinPool temp = new T12_ForkJoinPool();
                ForkJoinPool fjp = new ForkJoinPool();
                AddTaskRet task= new AddTaskRet(0, nums.length);
                fjp.execute(task);
                Long result = task.join();
                System.out.println(result);
        
            }
        }
        线程池execute和submit用法
        1.execute提交的是Runnable类型的任务,而submit提交的是Callable或者Runnable类型的任务
        2.execute的提交没有返回值,而submit的提交会返回一个Future类型的对象
        3.execute提交的时候,如果有异常,就会直接抛出异常,而submit在遇到异常的时候,通常不会立马抛出异常,而是会将异常暂时存储起来,等待你调用Future.get()方法的时候,才会抛出异常
        
        
    五,JMH(Java Microbenchmark Harness)
        创建JMH测试
            新建maven项目,引入依赖
                <dependencies>
                    <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
                    <dependency>
                        <groupId>org.openjdk.jmh</groupId>
                        <artifactId>jmh-core</artifactId>
                        <version>1.21</version>
                    </dependency>
            
                    <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->
                    <dependency>
                        <groupId>org.openjdk.jmh</groupId>
                        <artifactId>jmh-generator-annprocess</artifactId>
                        <version>1.21</version>
                        <scope>test</scope>
                    </dependency>
                </dependencies>
            idea安装JMH插件 JMH plugin v1.0.3
            由于用到了注解,打开运行程序注解配置:compiler -> Annotation Processors -> Enable Annotation Processing
            定义需要测试类PS (ParallelStream)
            public class PS {
                static List<Integer> nums = new ArrayList<>();
                static {
                    Random r = new Random();
                    for (int i = 0; i < 10000; i++) nums.add(1000000 + r.nextInt(1000000));
                }
                static void foreach() {
                    nums.forEach(v->isPrime(v));
                }
                static void parallel() {
                    nums.parallelStream().forEach(PS::isPrime);
                }
                    static boolean isPrime(int num) {
                    for(int i=2; i<=num/2; i++) {
                        if(num % i == 0) return false;
                    }
                    return true;
                }
            }
            写单元测试(在test下,与ps类同路径)
            public class PSTest {
             @Benchmark
             public void testForEach() {
                 PS.foreach();
             }
            }
            运行测试类,如果遇到下面的错误: Exception while trying to acquire the JMH lock (C:WINDOWS/jmh.lock): C:WINDOWSjmh.lock (拒绝访问。), exiting. Use -Djmh.ignoreLock=true to forcefully continue.
            这个错误是因为JMH运行需要访问系统的TMP目录,解决办法是:
            打开RunConfiguration -> Environment Variables -> include system environment viables
            阅读测试报告
            1. Warmup 预热,由于JVM中对于特定代码会存在优化(本地化),预热对于测试结果很重要
            2. Mesurement 总共执行多少次测试
            3. Timeout
            4. Threads 线程数,由fork指定
            5. Benchmark mode 基准测试的模式
            6. Benchmark 测试哪一段代码
        更多例子:http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/ 六,Disruptor Disruptor的特点 对比ConcurrentLinkedQueue : 链表实现 JDK中没有ConcurrentArrayQueue Disruptor是数组实现的 无锁,高并发,使用环形Buffer,直接覆盖(不用清除)旧的数据,降低GC频率 实现了基于事件的生产者消费者模式(观察者模式) RingBuffer 环形队列 RingBuffer的序号,指向下一个可用的元素 采用数组实现,没有首尾指针 对比ConcurrentLinkedQueue,用数组实现的速度更快 假如长度为8,当添加到第12个元素的时候在哪个序号上呢?用12
    %8决定 当Buffer被填满的时候到底是覆盖还是等待,由Producer决定 长度设为2的n次幂,利于二进制计算,例如:12%8 = 12 & (8 - 1) pos = num & (size -1) Disruptor开发步骤 1. 定义Event - 队列中需要处理的元素 2. 定义Event工厂,用于填充队列 这里牵扯到效率问题:disruptor初始化的时候,会调用Event工厂,对ringBuffer进行内存的提前分配 GC产频率会降低 3. 定义EventHandler(消费者),处理容器中的元素 源码地址: https://github.com/koukay/JUC.git https://github.com/koukay/TestJMH.git
  • 相关阅读:
    zabbix 添加 微信、邮件 媒介详解
    zabbix使用之常用功能使用心得
    Nginx1.8源码包编译安装
    httpd启动显示Could not reliably determine the server's fully qualified domain name, using 127.0.0.1. Set the 'ServerName'
    apache-httpd2.4编译安装
    apache-httpd2.2编译安装
    MYSQL5.7源码包编译安装
    MYSQL常见安装错误集:[ERROR] --initialize specified but the data directory has files in it. Abort
    MYSQL启用数据库错误:ERROR 2002 (HY000)
    编译mysql时CMake Error at cmake/readline.cmake:85 (MESSAGE)
  • 原文地址:https://www.cnblogs.com/hikoukay/p/12819225.html
Copyright © 2020-2023  润新知