JUC详解及案例-JDK8

JUC

什么是JUC

在java中,线程部分是一个重点,本篇说的JUC也是关于线程的,JUC就是java.util.concurrent工具包的简称。它是一个处理线程的工具包,JDK1.5开始出现的。

进程与线程

进程( Process )是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程( thread )是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

总结来说:

进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程一—资源分配的最小单位。

线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程—程序执行的最小单位。 I

线程状态

Thread.State枚举文件里有如下状态:

  • new:新建
  • RUNNABLE:准备就绪
  • BLOCKED:阻塞
  • WAITING:等待(不见不散)
  • TIMED_WAITING:过时不候
  • TERMINATED:终结

wait 与sleep

(1) sleep是Thread的静态方法,Iwait是Object的方法,任何对象实例都能调用。

(2) sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中),

(3)它们都可以被interrupted方法中断。

并发与并行

串行模式

串行表示所有任务都一按先后顺序进行。串行意味着必须先装完一车柴才能运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步骤,才能进行下一个步骤。

串行是一次只能取得一个任务,并执行这个任务

并行模式

并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。并行的效率从代码层次上强依赖于多进程/线程代码,从硬件角度上
则依赖于多核CPU。

并发

并发(€(concurren)€)指的是多个程序可以同时运行的现象,更细化的是多进程可
以同时运行或者多指令可以同时运行
。但这不是重点,在描述并发的时候也不
会去扣这种字眼是否精确,并发的重点在于它是一种现象,并发描述
的是多进程同时运行的现象
。但实际上,对于单核心CPU来说,同一时刻
只能运行一个线程。所以,这里的"同时运行表示的不是真的同一时刻有多个
线程运行的现象,这是并行的概念,而是提供一种功能让用户看来多个程序同
时运行起来了,但实际上这些程序中的进程不是一直霸占CP的,而是执行
会停一会。
**要解决大并发问题,通常是将大任务分解成多个小任务,**由于操作系统对进程的调度是随机的,所以切分成多个小任务后,可能会从任一小任务处执行。这可
能会出现一些现象:工

  • 可能出现一个小任务执行了多次,还没开始下个任务的情况。这时一般会采用
    队列或类似的数据结构来存放各个小任务的成果
  • 可能出现还没准备好第一步就执行第二步的可能。这时,一般采用多路复用或
    异步的方式,比如只有准备好产生了事件通知才执行某个任务。
  • 可以多进程/多线程的方式并行执行这些小任务。也可以单进程/单线程执行这
    些小任务,这时很可能要配合多路复用才能达到较高的效率

管程

JUC详解及案例-JDK8_第1张图片JUC详解及案例-JDK8_第2张图片
JUC详解及案例-JDK8_第3张图片

总结

并发:同一时刻多个线程访问同一个资源,多个线程对一个点,举例:春运抢票,电商秒杀
并行:多项工作一起执行,之后再汇总,举例:泡方便面,电水壶烧水,一边撕调料到桶里。

synchronized关键字

JUC详解及案例-JDK8_第4张图片JUC详解及案例-JDK8_第5张图片JUC详解及案例-JDK8_第6张图片JUC详解及案例-JDK8_第7张图片JUC详解及案例-JDK8_第8张图片

集合线程不安全和解决方案:

package com.lyj.demo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @program: java-test-demo
 * @Date: 2021/8/11 7:34
 * @Author: 凌兮
 * @Description:
 */
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class JUCTest {

    /**
     * List非线程安全的,底层add方法没有synchronized
     * JUC:就是java.util.concurrent包
     */
    public static void main(String [] args) {
        // 方式1:list arrayList是非线程安全集合
        // 报错:Exception in thread "1" Exception in thread "2"
        // Exception in thread "0" java.util.ConcurrentModificationException
//        List list = new ArrayList<>();
        //方式2:使用vector线程安全的集合,底层add方法有synchronized,该方式比较古老,jdk1.0时就有了。
//        List list = new Vector<>();
        // 方式3:使用Collections.synchornizedList方式创建,入参传入一个集合,
        // 线程安全的,底层也是用的synchronized,该方式比较古老,jdk1.2就有了
//        List list = Collections.synchronizedList(new ArrayList<>());
        // 方式4:使用JUC 里的CopyOnWriteArrayList,采用的是写时复制技术,底层采用lock方式
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
                /** ============hashSet线程不安全测试======== */
        // hashset非线程安全,报错:Exception in thread "15" Exception
        // in thread "19" java.util.ConcurrentModificationException
//        Set set = new HashSet<>();
        // 解决方式1:CopyOnWriteArraySet,线程安全的,底层用的是lock
        Set<String> set = new CopyOnWriteArraySet<>();
//        for (int i = 0; i < 30; i++) {
//            new Thread(() -> {
//                set.add(UUID.randomUUID().toString().substring(0, 8));
//                System.out.println(set);
//            }, String.valueOf(i)).start();
//        }
        /** ============hashMap线程不安全测试======== */
        // hashmap线程不安全的,报错:Exception in thread "3" java.util.ConcurrentModificationException
//        Map map = new HashMap<>();
        // 解决方式1:ConcurrentHashMap 线程安全的
        Map<String, String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            String key = String.valueOf(i);
            new Thread(() -> {
                map.put(key, UUID.randomUUID().toString().substring(0, 8));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }

    }
}

JUC详解及案例-JDK8_第9张图片

公平锁和非公平锁

公平锁:
优点:效率高
缺点:会持续抢占获得锁,造成其他线程饿死。
非公平锁:
优点:每个线程都能公平的获得锁
缺点:效率低

死锁

JUC详解及案例-JDK8_第10张图片JUC详解及案例-JDK8_第11张图片在这里插入图片描述
在idea里的terminal终端输入

# 查看当前运行的进程
jps -l
# 查看当前端口号进程的堆栈信息
jstack 端口号

JUC详解及案例-JDK8_第12张图片

可重入锁

可重入锁:可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,
并且不发生死锁,这样的锁就叫做可重入锁,在一个synchronized 修饰的方法
或者代码块的内部,调用本类的其他synchronized修饰的方法或代码块时,是永远
可以得到锁的。

Callable接口

JUC详解及案例-JDK8_第13张图片在这里插入图片描述JUC详解及案例-JDK8_第14张图片

JUC详解及案例-JDK8_第15张图片JUC详解及案例-JDK8_第16张图片

JUC三个辅助类-减少计数(CountDownLatch)-循环栅栏(cyclicBarrier)-信号灯(Semaphore)

JUC详解及案例-JDK8_第17张图片

   /**
     * countDown测试 :
     * 5个学生都必须先离开后,才能锁门。相当于多个线程都执行完后,
     * countdown值减为0才执行await之后的代码。
     * @throws InterruptedException
     */
    @Test
    public void countDownLatchTest() throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 1; i <= 5; i++) {
             new Thread(() -> {
                 System.out.println(Thread.currentThread().getName() + "我离开教室了");
                 countDownLatch.countDown();
             }, String.valueOf(i)).start();
         }
         countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "班长锁门了,所有人都离开教室了");
    }

JUC详解及案例-JDK8_第18张图片

 /**
     * 循环栅栏cyclicBarrier测试
     * 案例:集齐7个龙珠召唤神龙
     */
    @Test
    public void cyclicBarrierTest() {
        // 创建循环栅栏
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            // 只有parties达到7,才会执行该线程,召唤神龙
            System.out.println("集齐了7个龙珠召唤神龙");
        });
        // 集齐龙珠过程,如果i的最大值改为6,则cyclicBarrier会一直等待,不会召唤神龙
        for (int i = 1; i <= 7; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "龙珠");
                try {
                    // 等待,parties加1,未达到7个线程时,cyclicBarrier一直会等待。
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }


    }

JUC详解及案例-JDK8_第19张图片

/**
     * Semaphore信号量测试:
     * 6辆汽车,3个停车位
     */
    @Test
    public void semaphoreTest() {

        // 创建信号量,具有3个许可证
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                try {
                    // 抢占许可证
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "获得了许可证并停车了");
                    // 设置随机停车时间
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                    System.out.println(Thread.currentThread().getName() + "归还了许可证并离开车库");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 释放许可证,并离开
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }

JUC详解及案例-JDK8_第20张图片

ReentrantReadWriteLock读写锁

表锁:不会发生死锁,因为它是锁的整张表
行锁:会发生死锁,多个线程互相等待,因为它锁的是行记录
读锁:又叫共享锁,会发生死锁
写锁:又叫独占锁,会发生死锁
读锁发生死锁原因:
JUC详解及案例-JDK8_第21张图片
写锁发生死锁原因:
JUC详解及案例-JDK8_第22张图片
线程1在写的时候可以操作方框2的数据,但线程2在写的时候可以操作方框1的数据,这个时候互相等待,出现死锁。

未使用读写锁,会出现读写并发问题

 static class MapCache {

        // 创建缓存器 由于是经常进行读写操作,所以设置为volatile
        public static volatile Map<String, Object> cache = new HashMap<>();

        /**
         * 写操作
         * @param key
         * @param value
         */
        public static void put(String key, Object value) {
            System.out.println(Thread.currentThread().getName() + "正在进行写操作" + key);
            // 暂停一会
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cache.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入完毕" + key);
        }

        /**
         * 读操作
         * @param key
         * @return
         * @throws InterruptedException
         */
        public static Object get(String key) {
            System.out.println(Thread.currentThread().getName() + "正在进行读操作" + key);
            // 暂停一会
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cache.get(key);
            System.out.println(Thread.currentThread().getName() + "读取完毕" + key);
            return cache.get(key);
        }
    }

    @Test
    public void readWriteLockTest() {
        // 多线程写
        for (int i = 1; i <= 5; i++) {
            final int key = i;
            new Thread(() -> {
                MapCache.put(key + "", key + "");
            }, String.valueOf(i)).start();
        }
        // 多线程读
        for (int i = 1; i <= 5; i++) {
            final int key = i;
            new Thread(() -> {
                MapCache.get(key + "");
            }, String.valueOf(i)).start();
        }
    }

JUC详解及案例-JDK8_第23张图片
使用读写锁之后:

    static class MapCache {

        // 创建缓存器 由于是经常进行读写操作,所以设置为volatile
        public static volatile Map<String, Object> cache = new HashMap<>();
        // 创建读写锁
        public static ReadWriteLock lock = new ReentrantReadWriteLock();
        // 读锁
        public static Lock readLock = lock.readLock();
        // 写锁
        public static Lock writeLock = lock.writeLock();
        /**
         * 写操作
         * @param key
         * @param value
         */
        public static void put(String key, Object value) {
            writeLock.lock();
            // 暂停一会
            try {
                System.out.println(Thread.currentThread().getName() + "正在进行写操作" + key);
                TimeUnit.MILLISECONDS.sleep(300);
                cache.put(key, value);
                System.out.println(Thread.currentThread().getName() + "写入完毕" + key);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                writeLock.unlock();
            }
        }

        /**
         * 读操作
         * @param key
         * @return
         * @throws InterruptedException
         */
        public static Object get(String key) {
            readLock.lock();
            Object result = null;
            // 暂停一会
            try {
                System.out.println(Thread.currentThread().getName() + "正在进行读操作" + key);
                TimeUnit.MILLISECONDS.sleep(300);
                result = cache.get(key);
                System.out.println(Thread.currentThread().getName() + "读取完毕" + key);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
            }
            return result;
        }
    }

// 测试
 public static void main(String[] args) {
        // 多线程写
        for (int i = 1; i <= 5; i++) {
            final int key = i;
            new Thread(() -> {
                JUCTest.MapCache.put(key + "", key + "");
            }, String.valueOf(i)).start();
        }
        // 多线程读
        for (int i = 1; i <= 5; i++) {
            final int key = i;
            new Thread(() -> {
                JUCTest.MapCache.get(key + "");
            }, String.valueOf(i)).start();
        }
    }


JUC详解及案例-JDK8_第24张图片
JUC详解及案例-JDK8_第25张图片JUC详解及案例-JDK8_第26张图片JUC详解及案例-JDK8_第27张图片

   /**
     * 写锁降级测试:
     * 读锁:只有读完之后,才能进行写操作
     * 写锁:在写的过程中也可以进行读操作,写锁可以降级为读锁,提高数据的可见性,提高效率
     * 读锁不能升级为写锁
     */
    @Test
    public void writeLockFallBackTest() {
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        Lock writeLock = readWriteLock.writeLock();
        Lock readLock = readWriteLock.readLock();
        /** 写锁降级测试成功---注释下面的测试模块*/
        // 写操作
        writeLock.lock();
        System.out.println("写操作已上写锁");
      
        // 释放写锁
        writeLock.unlock();
        // 释放读锁
        readLock.unlock();
        /** 读锁升级为写锁测试 ===不能升级--注释上面的测试模块*/
//        // 读操作
//        readLock.lock();
//        System.out.println("读操作已上读锁");
//        // 写操作
//        writeLock.lock();
//        System.out.println("写操作已上写锁");
//        // 释放读锁
//        readLock.unlock();
//        // 释放写锁
//        writeLock.unlock();
        
    }

阻塞队列

JUC详解及案例-JDK8_第28张图片JUC详解及案例-JDK8_第29张图片JUC详解及案例-JDK8_第30张图片JUC详解及案例-JDK8_第31张图片在这里插入图片描述JUC详解及案例-JDK8_第32张图片JUC详解及案例-JDK8_第33张图片JUC详解及案例-JDK8_第34张图片JUC详解及案例-JDK8_第35张图片JUC详解及案例-JDK8_第36张图片JUC详解及案例-JDK8_第37张图片在这里插入图片描述JUC详解及案例-JDK8_第38张图片JUC详解及案例-JDK8_第39张图片JUC详解及案例-JDK8_第40张图片JUC详解及案例-JDK8_第41张图片JUC详解及案例-JDK8_第42张图片JUC详解及案例-JDK8_第43张图片JUC详解及案例-JDK8_第44张图片JUC详解及案例-JDK8_第45张图片JUC详解及案例-JDK8_第46张图片

BlockingQueue核心方法

JUC详解及案例-JDK8_第47张图片

四种场景的测试demo

    /**
     * 阻塞队列测试1--抛出异常
     */
    @Test
    public void blockingQueueTest() {

        // 创建数组类型的阻塞队列
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));
        // 数组类型的阻塞队列,检查元素时,默认是第一个元素
        System.out.println(queue.element());
        // 再次添加会报错,queue full
//        System.out.println(queue.add("d"));

        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        // 再次移除会报错,Nosuchemenet
//        System.out.println(queue.remove());

    }

    /**
     * 阻塞队列测试2--特殊值
     */
    @Test
    public void blockingQueueTest2() {

        // 创建数组类型的阻塞队列
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);

        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));
        // 数组类型的阻塞队列,检查元素时,默认是第一个元素
        System.out.println(queue.peek());
        // 再次添加返回false, 添加成功返回true
        System.out.println(queue.offer("d"));

        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        // 再次移除会返回null
        System.out.println(queue.poll());
    }

    /**
     * 阻塞队列测试3--阻塞
     */
    @Test
    public void blockingQueueTest3() throws InterruptedException {

        // 创建数组类型的阻塞队列
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        queue.put("a");
        queue.put("b");
        queue.put("c");
        // 再放程序会阻塞,直到队列空出一个位置。
//        queue.put("d");
        System.out.println("检测程序是否执行完");
        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
        // 再取程序会阻塞,直到队列里有值,获得值
        System.out.println(queue.take());
        System.out.println("检测程序是否执行完");
    }

    /**
     * 阻塞队列测试4--超时
     */
    @Test
    public void blockingQueueTest4() throws InterruptedException {

        // 创建数组类型的阻塞队列
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        System.out.println(queue.offer("a", 10, TimeUnit.MILLISECONDS));
        System.out.println(queue.offer("b", 10, TimeUnit.MILLISECONDS));
        System.out.println(queue.offer("c", 10, TimeUnit.MILLISECONDS));
        // 再次添加,如果添加不进去,并且超时时间到了,会放弃添加
        System.out.println(queue.offer("d", 10, TimeUnit.MILLISECONDS));

        System.out.println("检测程序是否执行完");
        System.out.println(queue.poll(10, TimeUnit.MILLISECONDS));
        System.out.println(queue.poll(10, TimeUnit.MILLISECONDS));
        System.out.println(queue.poll(10, TimeUnit.MILLISECONDS));
        // 再次移除,如果没有移除的值,并且超时时间到了,会放弃移除,并返回null
        System.out.println(queue.poll(10, TimeUnit.MILLISECONDS));
        System.out.println("检测程序是否执行完");
    }

线程池

JUC详解及案例-JDK8_第48张图片在这里插入图片描述JUC详解及案例-JDK8_第49张图片JUC详解及案例-JDK8_第50张图片JUC详解及案例-JDK8_第51张图片JUC详解及案例-JDK8_第52张图片JUC详解及案例-JDK8_第53张图片

/**
     * 线程池测试
     */
    @Test
    public void threadPoolTest() {
        // 创建指定数量的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(5, new ThreadFactory() {
            // 原子计数器
            AtomicInteger num = new AtomicInteger(1);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread("fixedThread_" + num.getAndIncrement());
            }
        });
        // 创建一池仅有一个线程的线程池
        ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
        // 创建一池自动可扩容的线程池
        ExecutorService threadPool3 = Executors.newCachedThreadPool();

        try {
            for (int i = 0; i <= 10; i++) {
                // 线程池切换名字即可测试
                threadPool3.execute(() -> {
                   System.out.println(Thread.currentThread().getName());
               });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 将线程归还
            threadPool3.shutdown();
        }
    }

线程池参数

JUC详解及案例-JDK8_第54张图片

线程池工作流程

JUC详解及案例-JDK8_第55张图片JUC详解及案例-JDK8_第56张图片在这里插入图片描述

JUC详解及案例-JDK8_第57张图片JUC详解及案例-JDK8_第58张图片

Fork/Join框架

JUC详解及案例-JDK8_第59张图片JUC详解及案例-JDK8_第60张图片JUC详解及案例-JDK8_第61张图片JUC详解及案例-JDK8_第62张图片JUC详解及案例-JDK8_第63张图片在这里插入图片描述


    /**
     * 采用ForkJoin池,计算1+2+3+...+100和
     */
    static class MyTask extends RecursiveTask<Integer>{
        // 拆分的差值不能大于10
        private static final Integer VALUE = 10;
        // 拆分开始值
        private int begin;
        // 拆分结束值
        private int end;
        // 拆分结束值
        private int result;

        public MyTask(int begin, int end) {
            this.begin = begin;
            this.end = end;
        }


        @Override
        protected Integer compute() {
            // 差值小于10分支
            if ((end - begin) <= 10) {
                for (int i = begin; i <= end; i++) {
                    result += i;
                }
            } else {
                // 差值大于10, 进行任务拆分
                int middle = (begin + end) / 2;
                // 左任务
                MyTask myTask01 = new MyTask(begin, middle);
                // 右任务
                MyTask myTask02 = new MyTask(middle + 1, end);
                // 调用方法拆分
                myTask01.fork();
                // 调用方法拆分
                myTask02.fork();
                // 合并结果
                result = myTask01.join() + myTask02.join();
            }
            return result;
        }
    }

    /**
     * forkJoin测试
     */
    @Test
    public void forkJoinTest() throws ExecutionException, InterruptedException {
        // 创建任务对象
        MyTask myTask = new MyTask(0, 100);
        // 创建合并分支池
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        // 将任务扔进池里
        ForkJoinTask<Integer> joinTask = forkJoinPool.submit(myTask);
        // 获取最终合并值
        System.out.println(joinTask.get());
        // 关闭合并池
        forkJoinPool.shutdown();
    }

你可能感兴趣的:(javaSE,juc)