JUC介绍及底层源码分析(详解)

线程基础知识复习

1,线程基本概念

   一个线程只能属于一个进程,但一个进程可以有多个线程或者说至少有一个线程,通常也就是我们说的主线程了,资源分配给进程 同一进程的所有线程共享该进程的所有资源,线程在执行过程中需要协作同步,不同进程的线程间,要利用消息通信的办法实现同步,处理机分给线程,即真正的处理及上运行的是线程,线程是指进程内的一个执行单元,也是进程内的可调度实体。

2,生命周期

   线程调用start方法进入就绪状态也就是可执行状态,他等待cpu给线程分配时间片获得系统资源之后cpu执行他时候线程就进入运行状态 他就会从就绪和运行状态来回转换同时也有可能进入暂停状态 如果在运行执行sleep休眠wait等待外界因素导致线程阻塞比如等待用户输入用户信息等待 进入暂停与就绪状态不一样是持有系统资源的只是没有做任何操作而已当休眠结束之后或notify唤醒线程或用户输完信息 线程就会从暂停状态回到就绪状态当线程执行状态就会进入死忙状态垃圾回收站回收而我们掌握是运行状态和暂停状态来回切换

volatile关键字

volatile 是java虚拟机提供的轻量级的同步机制 有三大特性:保证可见性 不保证原子性 禁止指令重排

验证volatile关键字保证内存可见性:

/**
 * 测试 volatile关键字 之 可见性
 */
public class VolatileDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t come in");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addNum();
            System.out.println(Thread.currentThread().getName()+"\t 当前数字为 "+ myData.num);
        },"AAA").start();
        while (myData.num == 0){

        }
        System.out.println(Thread.currentThread().getName()+"\t"+myData.num);
    }
}
//资源类
class MyData{
    int num = 0;
    public void addNum(){
        this.num = 88;
    }
}

这是没有添加volatile关键字,打印效果如下:  

子线程读取不到主线程修改后的num值,陷入死循环程序无法结束。

接下来添加volatile关键字再试试:

 打印结果如下:子线程可以读取的新值并结束子线程

CAS  (自旋锁)

Cas 本质的原理:

预期值===v(共享变量中值) ,才会修改我们 v

示例:

    public static void main(String[] args) {
        /**
         *  V=共享变量 =0
         *  线程1---- E=每个线程中都会缓存副本V的值----旧的预期值=E=0 N=1
         *  线程2----E=每个线程中都会缓存副本V的值----旧的预期值=E=0 N=2, false原因是用旧的值去匹配
         */
        AtomicInteger atomicInteger = new AtomicInteger(0);
        boolean b1 = atomicInteger.compareAndSet(0, 1);
        boolean b2 = atomicInteger.compareAndSet(0, 1);
        System.out.println(b1 + "结果:" + atomicInteger.get());
        System.out.println(b2 + "结果:" + atomicInteger.get());
    }

测试结果

优点没有获取到锁的线程,会一直在用户态,不会阻塞,没有锁的线程会一直通过循环控 制重试

缺点:通死循环控制,消耗 cpu 资源比较高,需要控制循次数,避免 cpu 飙高问题;

CompletableFuture

异步编排 CompletableFuture jdk8的一个新特性,同一层级异步执行,父子层级等待。等所有的异步任务全部执行完成,查到结果以后,把整个数据聚合返回,如果用以前的技术来解决异步之间的编排关系。想想都复杂
    //未缓存优化前 - 400/s
    public SkuDetailTo getSkuDetailFromRpc(Long skuId) {
        SkuDetailTo detailTo = new SkuDetailTo();
        //以前   一次远程超级调用【网络交互比较浪费时间】  查出所有数据直接给我们返回
        // 一次远程用 2s
        //CountDownLatch downLatch = new CountDownLatch(6);
        // 6次远程调用 6s + 0.5*6 = 9s
        //同步调用
        //远程调用其实不用等待,各查各的。 异步的方式
        //CompletableFuture.runAsync()// CompletableFuture<Void>  启动一个下面不用它返回结果的异步任务
        //CompletableFuture.supplyAsync()//CompletableFuture<U>  启动一个下面用它返回结果的异步任务
        //异步实际上是: 空间换时间;  new Thread()
        //最一行: 串行: 6s
        //最一行: 并行: 等待一个最长时间,全部任务都能完成。
        //如果异步:  new Thread().start();
        //  不能直接用 new Thread().start();
        //          一个请求进来,直接无脑开6个线程,高并发下直接OOM。
        //          一个一炸可能导致整个集群雪崩。
        // 不能无脑开线程,很容易资源耗尽,池技术(线程池、连接池、xxx池)【资源复用问题】
        //   线程池+阻塞队列:解决资源复用与等待问题。

        //1、查基本信息   1s
        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            Result<SkuInfo> result = skuDetailFeignClient.getSkuInfo(skuId);
            SkuInfo skuInfo = result.getData();
            detailTo.setSkuInfo(skuInfo);
            return skuInfo;
        }, executor);


        //2、查商品图片信息  1s
        CompletableFuture<Void> imageFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null) {
                Result<List<SkuImage>> skuImages = skuDetailFeignClient.getSkuImages(skuId);
                skuInfo.setSkuImageList(skuImages.getData());
            }

        }, executor);


        //3、查商品实时价格 2s
        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            Result<BigDecimal> price = skuDetailFeignClient.getSku1010Price(skuId);
            detailTo.setPrice(price.getData());
        }, executor);


        //4、查销售属性名值
        CompletableFuture<Void> saleAttrFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null) {
                Long spuId = skuInfo.getSpuId();
                Result<List<SpuSaleAttr>> saleattrvalues = skuDetailFeignClient.getSkuSaleattrvalues(skuId, spuId);
                detailTo.setSpuSaleAttrList(saleattrvalues.getData());
            }

        }, executor);


        //5、查sku组合
        CompletableFuture<Void> skuVlaueFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null) {
                Result<String> sKuValueJson = skuDetailFeignClient.getSKuValueJson(skuInfo.getSpuId());
                detailTo.setValuesSkuJson(sKuValueJson.getData());
            }
        }, executor);


        //6、查分类
        CompletableFuture<Void> categoryFuture = skuInfoFuture.thenAcceptAsync(skuInfo -> {
            if (skuInfo != null) {
                Result<CategoryViewTo> categoryView = skuDetailFeignClient.getCategoryView(skuInfo.getCategory3Id());
                detailTo.setCategoryView(categoryView.getData());
            }
        }, executor);

        CompletableFuture
                .allOf(imageFuture, priceFuture, saleAttrFuture, skuVlaueFuture, categoryFuture)
                .join();

        return detailTo;
    }

集合线程不安全案例

    List<String> list = new ArrayList<>();
    for (int i = 0; i < 10000; i++) {
        new Thread(() -> {
            list.add(UUID.randomUUID().toString().substring(0, 3));
            System.out.println(list);
        }, String.valueOf(i)).start();
    }
}

测试结果

原因:没有synchronized线程不安全

解决方案:

1,new Vector<>();

2,List<String> list = Collections.synchronizedList(new ArrayList<>());

3,new CopyOnWriteArrayList<>();

CopyOnWriteArrayList采用了一种读写分离的并发策略CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器

HashSet

Set<String> set = new HashSet<>();//线程不安全

Collections.synchronizedSet(new HashSet<>());//线程安全
new CopyOnWriteArraySet<>();//线程安全

HashMap

1, Map<String,String> map = new HashMap<>();//线程不安全

2, Map<Object, Object> objectObjectMap = Collections.synchronizedMap(new HashMap<>());
3, new ConcurrentHashMap<>();//线程安全

线程间通信

生产者与消费者案例:判断 干活 通知

public class SetTest {
    public static void main(String[] args) {
        Pet pet = new Pet();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                pet.incd();
            },"A").start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                pet.del();
            },"b").start();
        }
    }
}
class Pet{
    int sum=0;
    public synchronized void incd(){
        //判断
        while (sum!=0){
            try {
                Thread.sleep(10);
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        //干活  System.out.println(Thread.currentThread().getName()+"----"+(++sum));
    }
        //通知
        this.notifyAll();

    public synchronized void del(){
        while (sum==0){
            try {
                Thread.sleep(10);
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notifyAll();      

   System.out.println(Thread.currentThread().getName()+"----"+(--sum));
    }
}

死锁案例 

static Object lockA = new Object();
static Object lockB = new Object();
public static void main(String[] args) {
    new Thread(() -> {
        synchronized (lockA) {
            System.out.println(Thread.currentThread().getName() + "\t" + "自己持有A锁,想获取B锁");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName() + "\t" + "成功获取B锁");
            }
        }
    }, "A").start();

    new Thread(() -> {
        synchronized (lockB) {
            System.out.println(Thread.currentThread().getName() + "\t" + "自己持有B锁,想获取A锁");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "\t" + "成功获取A锁");
            }
        }
    }, "B").start();
}

减少计数CountDownlatch

 CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。

 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞), 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。

    public static void main(String[] args) throws InterruptedException {
        int a = 6;
        CountDownLatch countDownLatch = new CountDownLatch(a);
        for (int i = 0; i <= a; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "离开教师");
                countDownLatch.countDown();//走一个减一个
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();  //减到0才会走下面的程序
        System.out.println(Thread.currentThread().getName() + "\t" + "关门走人");

    }

测试结果

循环栅栏CyclicBarrier

 字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBarrier的await()方法。

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
            System.out.println("集合到了"); //到第6个才会输出 和CountDownLatch相反
        });
        for (int i = 1; i < 6; i++) {
            int a = i;
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + "得到龙珠" + a);
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }

测试结果

信号灯Semaphore

 在信号量上我们定义两种操作:

 * acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),

 *  要么一直等下去,直到有线程释放信号量,或超时。

 * release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。

 * 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(2); //模拟2停车位 相当于2个锁子,每次只能进两个线程
        for (int i = 1; i <=4; i++) { //四辆汽车,
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢车成功");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }).start();
        }
    }

测试结果

Callable接口

和Runnable区别

1,Callable接口使用call方法,Runnable接口使用run方法;

2,Callable接口有返回值和可以抛出异常;而Runnable接口没有返回值和不能抛出异常。

 @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "call方法的之前");
        try {
            TimeUnit.SECONDS.sleep(1);
        } finally {
        }
        return "call方法的返回值";
    }
}
class MyTh {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> stringFutureTask = new FutureTask<String>(new CallbleDemo());
        new Thread(stringFutureTask, "这个时Thread方法").start();
        System.out.println(Thread.currentThread().getName() + "--------------");
        //main  call方法的返回值
        System.out.println(Thread.currentThread().getName() + "\t" + stringFutureTask.get());
    }
}

线程池

 线程池它的主要特点为:线程复用;控制最大并发数;管理线程。

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。

第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

Executors.newFixedThreadPool(3);// 一池3线程(救急线程=核心线程 不合适  阻塞队列的大小为int 最大值 有OOM风险)

Executors.newSingleThreadExecutor()  一池一线程

Executors.newCachedThreadPool()  救急线程默认我int 最大值 有OOM风险

自定义线程池

/**
 * 配置线程池
 */
//1、AppThreadPoolProperties 里面的所有属性和指定配置绑定
//2、AppThreadPoolProperties 组件自动放到容器中
//开启自动化属性绑定配置
@EnableConfigurationProperties(AppThreadPoolProperties.class)
@Configuration
public class AppThreadPoolAutoConfiguration {

    @Autowired
    AppThreadPoolProperties threadPoolProperties;

    @Value("${spring.application.name}")
    String applicationName;

    @Bean
    public ThreadPoolExecutor coreExecutor(){
        // -Xmx100m 100mb:  内存合理规划使用
        //压力测试:  1亿/1万/1个   500mb
//        new ArrayBlockingQueue(10): 底层队列是一个数组
//        new LinkedBlockingDeque(10): 底层是一个链表
        //数组与链表? -- 检索、插入
        //数组是连续空间,链表不连续(利用碎片化空间)
        /**
         * int corePoolSize,  核心线程池: cpu核心数   4
         * int maximumPoolSize, 最大线程数:          8
         * long keepAliveTime,  线程存活时间
         * TimeUnit unit,      时间单位
         * BlockingQueue<Runnable> workQueue, 阻塞队列:大小需要合理
         * ThreadFactory threadFactory,  线程工厂。 自定义创建线程的方法
         * RejectedExecutionHandler handler
         *
         * //  2000/s:队列大小根据接口吞吐量标准调整
         */
        //配置可抽取
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                threadPoolProperties.getCore(), //2
                threadPoolProperties.getMax(), //4
                threadPoolProperties.getKeepAliveTime(), //300L
                TimeUnit.SECONDS,
                new LinkedBlockingQueue(threadPoolProperties.getQueueSize()), //队列的大小由项目最终能占的最大内存决定
                Executors.defaultThreadFactory(), // 线程工厂一般使用默认就可以了
                new ThreadPoolExecutor.AbortPolicy()
        );
        //每个线程的线程名都是默认的。
        return executor;
    }
}

线程七大参数

corePoolSize:线程池中常驻核心线程池(正在运行线程)

maximumPoolSize:(最大线程数)线程池中能够容纳同时执行最大线程数,改值必须大于corepoolsize,否则就会报非法参数异常

keepAliveTime:空闲线程存活时间指当线程池中没有任务时,会销毁一些线程,销毁的线程数 = maximumPoolSize - corePoolSize

unit:(时间单位)指空闲线程存活时间单位,与 keepAliveTime 配合使用。

workQueue(阻塞队列)指线程池存放任务的队列,用来存储线程池的所有待执行的任务

threadFactory:生成线程池中工作线程的线程工厂,一般使用默认即可

handler:拒绝策略,表示当任务队列满并且工作线程=>线程池的最大线程数时,对即将到来的线程的拒绝策略

拒绝策略:

   1,AbortPolicy() 默认 当任务>最大线程数的时候,直接拒绝 并抛出异常 (工作时用)

   2,discardPolicy()当任务大于最大线程数的时候,直接拒绝  但不抛出异常

   3,discardOldestPolicy() 丢弃最老任务

   4.callRunsPolicy()  调用主线程执行任务

线程池的运行原理?

第一步:线程池刚创建的时候,里面没有任何线程,等到有任务过来的时候才会创建线程。也可以调用 prestartAllCoreThreads() 或者 prestartCoreThread() 方法预创corePoolSize个线程(新建时就有线程了)

第二步:调用execute()提交一个任务时,如果当前的工作线程数<corePoolSize,直接创建新的线程执行这个任务

第三步:如果当时工作线程数量>=corePoolSize,会将任务放入任务队列中缓存

第四步:如果队列已满,并且线程池中工作线程的数量<maximumPoolSize,还是会创建线程执行这个任务

第五步:如果队列已满,并且线程池中的线程已达到maximumPoolSize,这个时候会执行拒绝策略,JAVA线程池默认的策略是AbortPolicy,即抛出RejectedExecutionException异常

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有心不在迟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值