juc基础(二)

目录

一、集合的线程安全

1、List集合

2、hashset

3、hashmap

二、多线程锁

三、Callable&Future 接口  

1、Callable接口

2、Future 接口

3、FutureTask

四、JUC 三大辅助类

1、减少计数 CountDownLatch

2、 循环栅栏 CyclicBarrier

3、信号灯 Semaphore


一、集合的线程安全

1、List集合

List是线程不安全的,下面的例子运行时会报错

/**
 * 集合线程安全案例
 */
public class NotSafeDemo {
    /**
     * 多个线程同时对集合进行修改
     *
     * @param args
     */
    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            }, "线程" + i).start();
        }
    }
}
异常内容
java.util.ConcurrentModificationException
问题: 为什么会出现并发修改异常?
查看 ArrayList 的 add 方法源码发现并没有使用锁

三个解决方案

① vector

List<String> list = new Vector<>();

② Collections

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

③ CopyOnWriteArrayList

List<String> list = new CopyOnWriteArrayList<>();

2、hashset

        //演示Hashset
        Set<String> set = new HashSet<>();

        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();
        }

 解决方案

Set<String> set = new CopyOnWriteArraySet<>();

3、hashmap

        //演示HashMap
        Map<String,String> map = new HashMap<>();

        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();
        }

 

解决方案

Map<String,String> map = new ConcurrentHashMap<>();

 

小结(重点)
1.线程安全与线程不安全集合
集合类型中存在线程安全与线程不安全的两种,常见例如:
ArrayList ----- Vector
HashMap -----HashTable
但是以上都是通过 synchronized 关键字实现,效率较低
2.Collections 构建的线程安全集合 3.java.util.concurrent 并发包下
CopyOnWriteArrayList CopyOnWriteArraySet 类型,通过动态数组与线程安
全个方面保证线程安全

二、多线程锁

锁的八个问题演示

class Phone {

    public static synchronized void sendSMS() throws Exception {
        //停留4秒
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------sendSMS");
    }

    public synchronized void sendEmail() throws Exception {
        System.out.println("------sendEmail");
    }

    public void getHello() {
        System.out.println("------getHello");
    }
}

/**
 * @Description: 8锁
 *
1 标准访问,先打印短信还是邮件
------sendSMS
------sendEmail

2 停4秒在短信方法内,先打印短信还是邮件
------sendSMS
------sendEmail   --前两个锁的是对象

3 新增普通的hello方法,是先打短信还是hello
------getHello
------sendSMS

4 现在有两部手机,先打印短信还是邮件
------sendEmail
------sendSMS     --不同对象锁自己

5 两个静态同步方法,1部手机,先打印短信还是邮件
------sendSMS
------sendEmail

6 两个静态同步方法,2部手机,先打印短信还是邮件
------sendSMS
------sendEmail   -- 5和6锁的是当前对象的Class

7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
------sendEmail
------sendSMS

8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
------sendEmail
------sendSMS

 */

public class Lock_8 {
    public static void main(String[] args) throws Exception {

        Phone phone = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "AA").start();

        Thread.sleep(100);

        new Thread(() -> {
            try {
               // phone.sendEmail();
               // phone.getHello();
                phone2.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "BB").start();
    }
}

 结论:

(1)一个对象里面如果有多个 synchronized 方法,某一个时刻内,只要一个线程去调用其中的
一个 synchronized 方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些 synchronized 方法
锁的是当前对象 this ,被锁定后,其它的线程都不能进入到当前对象的其它的 synchronized 方法
(2)加个普通方法后发现和同步锁无关
(3)换成两个对象后,不是同一把锁了,情况立刻变化。
synchronized 实现同步的基础: Java 中的每一个对象都可以作为锁。
具体表现为以下 3 种形式。
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的 Class 对象。
对于同步方法块,锁是 Synchonized 括号里配置的对象
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,
可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁, 所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。
但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!

三、Callable&Future 接口  

1、Callable接口

Callable 接口的特点如下(重点)
  • 为了实现 Runnable,需要实现不返回任何内容的 run()方法,而对于Callable,需要实现在完成时返回结果的 call()方法。
  • call()方法可以引发异常,而 run()则不能。
  • 为实现 Callable 而必须重写 call 方法
  • 不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable
创建新类 MyThread 实现 runnable 接口
class MyThread implements Runnable{
    @Override
    public void run() {
    }
}

新类 MyThread2 实现 callable 接口
class MyThread2 implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        return 200;
    }
}

 2、Future 接口

当 call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用 Future 对象。将 Future 视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦 Callable 返回)。Future 基本上是主线程可以跟踪进度以及其他线程的结果的
一种方式。要实现此接口,必须重写 5 种方法,这里列出了重要的方法,如下:
  • public boolean cancel(boolean mayInterrupt):用于停止任务。
==如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterrupt 为 true时才会中断任务。==
  • public Object get()抛出 InterruptedException,ExecutionException: 用于获取任务的结果。
==如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。 ==
  • public boolean isDone():如果任务完成,则返回 true,否则返回 false
可以看到 Callable 和 Future 做两件事-Callable 与 Runnable 类似,因为它封装了要在另一个线程上运行的任务,而 Future 用于存储从另一个线程获得的结果。实际上,future 也可以与 Runnable 一起使用。
要创建线程,需要 Runnable。为了获得结果,需要 future。

3、FutureTask

Java 库具有具体的 FutureTask 类型,该类型实现 Runnable 和 Future,并方便地将两种功能组合在一起。 可以通过为其构造函数提供 Callable 来创建 FutureTask。然后,将 FutureTask 对象提供给 Thread 的构造函数以创建 Thread 对象。因此,间接地使用 Callable 创建线程。
核心原理:(重点)
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成
当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态
一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法
一旦计算完成,就不能再重新开始或取消计算
get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常
get 只计算一次,因此 get 方法放到最后

demo

//比较两个接口
//实现Runnable接口
class MyThread1 implements Runnable {
    @Override
    public void run() {

    }
}

//实现Callable接口
class MyThread2 implements Callable {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+" come in callable");
        return 200;
    }
}

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //Runnable接口创建线程
        new Thread(new MyThread1(),"AA").start();

        //Callable接口,报错
       // new Thread(new MyThread2(),"BB").start();

        // Thread没有传递参数为callable的方法,那么如何使用callable?
        // 找一个中间人,既认识Runnable又认识Callable的就可以了————FutureTask
        //FutureTask除了实现了Future接口以外,FutureTask还实现了Runnable接口
        FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());

        //lam表达式 callable接口是函数式接口,可以简化
        FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
            System.out.println(Thread.currentThread().getName()+" come in callable");
            return 1024;
        });

        //创建一个线程
        new Thread(futureTask2,"lucy").start();
        new Thread(futureTask1,"mary").start();

//        while(!futureTask2.isDone()) {
//            System.out.println("wait.....");
//        }
        //调用FutureTask的get方法
        System.out.println(futureTask2.get());

        System.out.println(futureTask1.get());

        System.out.println(Thread.currentThread().getName()+" come over");
        //FutureTask原理  未来任务
        /**
         * 1、老师上课,口渴了,去买票不合适,讲课线程继续。
         *   单开启线程找班上班长帮我买水,把水买回来,需要时候直接get
         *
         * 2、4个同学, 1同学 1+2...5   ,  2同学 10+11+12....50, 3同学 60+61+62,  4同学 100+200
         *      第2个同学计算量比较大,
         *     FutureTask单开启线程给2同学计算,先汇总 1 3 4 ,最后等2同学计算位完成,统一汇总
         *
         * 3、考试,做会做的题目,最后看不会做的题目
         *
         * 汇总一次
         *
         */

    }
}
小结
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成, 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态• 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去 获取结果
仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
只计算一次

四、JUC 三大辅助类

JUC 中提供了三种常用的辅助类,通过这些辅助类可以很好的解决线程数量过
多时 Lock 锁的频繁操作。这三种辅助类为:
  • CountDownLatch: 减少计数
  • CyclicBarrier: 循环栅栏
  • Semaphore: 信号灯

1、减少计数 CountDownLatch

CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这 些线程会阻塞
其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行
场景: 6 个同学陆续离开教室后值班同学才可以关门。

//演示 CountDownLatch
public class CountDownLatchDemo {
    //6个同学陆续离开教室之后,班长锁门
    public static void main(String[] args) throws InterruptedException {

        //创建CountDownLatch对象,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        //6个同学陆续离开教室之后
        for (int i = 1; i <=6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" 号同学离开了教室");

                //计数  -1
                countDownLatch.countDown();

            },String.valueOf(i)).start();
        }

        //等待
        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+" 班长锁门走人了");
    }
}

2、 循环栅栏 CyclicBarrier

CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。可以将 CyclicBarrier 理解为加 1 操作
场景: 集齐 7 颗龙珠就可以召唤神龙
/*
 * CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。
 * 它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,
 * 屏障才会开门,所有被屏障拦截的线程才会继续干活。
 * CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,
 * 每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
 */
//集齐7颗龙珠就可以召唤神龙
public class CyclicBarrierDemo {

    //创建固定值
    private static final int NUMBER = 7;

    public static void main(String[] args) {
        //创建CyclicBarrier
        CyclicBarrier cyclicBarrier =
                new CyclicBarrier(NUMBER,()->{
                    System.out.println("*****集齐7颗龙珠就可以召唤神龙");
                });

        //集齐七颗龙珠过程
        for (int i = 1; i <=7; i++) {
            new Thread(()->{
                try {
                    System.out.println(Thread.currentThread().getName()+" 星龙被收集到了");
                    //等待
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

3、信号灯 Semaphore

Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方法获得许可证,release 方法释放许可
场景: 抢车位, 6 部汽车 3 个停车位
//6辆汽车,停3个车位
public class SemaphoreDemo {
    public static void main(String[] args) {
        //创建Semaphore,设置许可数量
        Semaphore semaphore = new Semaphore(3);

        //模拟6辆汽车
        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();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/85614.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android开发基础知识总结(四)简单控件(下)

一.按钮触控 最常见的按钮button类继承自Textview类。 需要注意的是&#xff0c;在Button中显示的单词默认全部大写 ~ public void onClick(View v){s1et1.getText().toString();//有一些小bug&#xff0c;好像变量必须声明在Onclick方法内部才有效&#xff1f;&#xff1f;&am…

SpringMVC拦截器学习笔记

SpringMVC拦截器 拦截器知识 拦截器(Interceptor)用于对URL请求进行前置/后置过滤 Interceptor与Filter用途相似但实现方式不同 Interceptor底层就是基于Spring AOP面向切面编程实现 拦截器开发流程 Maven添加依赖包servlet-api <dependency><groupId>javax.se…

Hadoop小结(上)

最近在学大模型的分布式训练和存储&#xff0c;自己的分布式相关基础比较薄弱&#xff0c;基于深度学习的一切架构皆来源于传统&#xff0c;我总结了之前大数据的分布式解决方案即Hadoop&#xff1a; Why Hadoop Hadoop 的作用非常简单&#xff0c;就是在多计算机集群环境中营…

【经验】VScode 远程连接 Ubuntu 出错,Could not establish connection

用VScode常常会碰到以下情况&#xff0c;Could not establish connection。 先介绍一下VScode远程连接和终端SSH连接的区别&#xff1a;终端直接用SSH连接时&#xff0c;只需要开启SSH服务&#xff0c;并消耗少量的内存即可&#xff1b;VScode连接时&#xff0c;会自动在服务器…

二、Kafka快速入门

目录 2.1 安装部署1、【单机部署】2、【集群部署】 2.2 Kafka命令行操作1、查看topic相关命令参数2、查看当前kafka服务器中的所有Topic3、创建 first topic4、查看 first 主题的详情5、修改分区数&#xff08;注意&#xff1a;分区数只能增加&#xff0c;不能减少&#xff09;…

Java后端开发面试题——微服务篇总结

Spring Cloud 5大组件有哪些&#xff1f; 随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件 注册中心/配置中心 Nacos 负载均衡 Ribbon 服务调用 Feign 服务保护 sentinel 服务网关 Gateway Ribbon负载均衡策略有哪些 ? RoundRobinRule&…

opencv-手势识别

# HandTrackingModule.py import cv2 import mediapipe as mpclass HandDetector:"""使用mediapipe库查找手。导出地标像素格式。添加了额外的功能。如查找方式&#xff0c;许多手指向上或两个手指之间的距离。而且提供找到的手的边界框信息。"""…

强训第38天

选择 D 0作为本地宿主机&#xff0c;127作为内部回送&#xff0c;不予分配 A B C C 存储在浏览器 D A B B D 网络延迟是指从报文开始进入网络到它离开网络之间的时间 编程 红与黑 红与黑__牛客网 #include <iostream> #include <stdexcept> #include <string…

在vue3+ts+vite中使用svg图片

目录 前言 步骤 1.安装svg-sprite-loader,这里使用的是6.0.11版本 2.项目的svg图片存放在src/icons下&#xff0c;我们在这里创建两个文件index.ts和index.vue&#xff08;在哪创建和文件名字并没有任何要求&#xff09; 3.在index.ts中加入下列代码(如果报错找不到fs模块请…

飞天使-k8s基础组件分析-控制器

文章目录 控制器含义解释pod的标签与注释ReplicaControllerReplicaSetDeploymentsDaemonSetJobCronjob参考文档 控制器含义解释 空调遥控器知道吧ReplicationController: ReplicationController确保在任何时候都运行指定数量的pod副本。换句话说&#xff0c;一个ReplicationCo…

无涯教程-Perl - wantarray函数

描述 如果当前正在执行的函数的context正在寻找列表值,则此函数返回true。在标量context中返回false。 语法 以下是此函数的简单语法- wantarray返回值 如果没有context,则此函数返回undef&#xff1b;如果lvalue需要标量,则该函数返回0。 例 以下是显示其基本用法的示例…

3种获取OpenStreetMap数据的方法【OSM】

OpenStreetMap 是每个人都可以编辑的世界地图。 这意味着你可以纠正错误、添加新地点&#xff0c;甚至自己为地图做出贡献&#xff01; 这是一个社区驱动的项目&#xff0c;拥有数百万注册用户。 这是一个社区驱动的项目&#xff0c;旨在在开放许可下向每个人提供所有地理数据。…

Centos 7 安装系列(8):openGauss 3.0.0

安装依赖包&#xff1a; yum -y install libaio-devel flex bison ncurses-devel glibc-devel patch redhat-lsb-core readline-devel openssl-devel sqlite-devel libnsl 安装插件&#xff1a; yum install -y bzip2 net-tools为什么要安装这两个&#xff1f; 安装bzip2 是…

SSRF 服务器端请求伪造

文章目录 SSRF(curl)网址访问通过file协议访问本地文件dict协议扫描内网主机开放端口 SSRF(file_get_content)网站访问http协议请求内网资源通过file协议访问本地文件 SSRF(Server-Side Request Forgery:服务器端请求伪造) 其形成的原因大都是由于服务端提供了从其他服务器应用…

三、MySQL 数据库安装集

一、CentOS—YUM 1. MySQL—卸载 # 1、查看存在的MySQL。 rpm -qa | grep -i mysql rpm -qa | grep mysql# 2、删除存在的MySQL。 rpm -e –-nodeps 包名# 3、查找存在的MySQL目录。 find / -name mysql# 4、删除存在的MySQL目录。 rm -rf 目录# 5、删除存在的MySQL配置文件。…

TCP/IP---网络层

一、网络层的主要功能 1、提供了通讯过程中&#xff0c;必须要使用的另一个地址&#xff1a;逻辑IP地址【ipv4、ipv6】 2、连接不同媒介类型【内网--外网&#xff08;intra -- inter&#xff09;】 3、根据运行的不同的路由协议&#xff0c;选择不同的最佳路径 4、在选择的最好…

【数据结构与算法】迪杰斯特拉算法

迪杰斯特拉算法 介绍 迪杰斯特拉&#xff08;Dijkstra&#xff09;算法是典型最短路径算法&#xff0c;用于计算一个节点到其他节点的最短路径。它的主要特点是以中心向外层层扩展&#xff08;广度优先搜索思想&#xff09;&#xff0c;直到扩展到终点为止。 算法过程 设置…

微信小程序 车牌号输入组件

概述 一个小组件&#xff0c;用于方便用户输入车牌号码 详细 概述 有时候我们开发过程中会遇到需要用户输入车牌号的情况&#xff0c;让客户通过自带键盘输入&#xff0c;体验不好且容易出错&#xff0c;例如车牌号是不能输入O和I的&#xff0c;因此需要有一个自定义的键盘…

2023/8/16 华为云OCR识别驾驶证、行驶证

目录 一、 注册华为云账号开通识别驾驶证、行驶证服务 二、编写配置文件 2.1、配置秘钥 2.2、 编写配置工具类 三、接口测试 3.1、测试接口 3.2、结果 四、实际工作中遇到的问题 4.1、前端传值问题 4.2、后端获取数据问题 4.3、使用openfeign调用接口报错 4.3、前端显示问题…

SQL 语句继续学习之记录二

三&#xff0c; 聚合与排序 对表进行聚合查询&#xff0c;即使用聚合函数对表中的列进行合计值或者平均值等合计操作。 通常&#xff0c;聚合函数会对null以外的对象进行合计。但是只有count 函数例外&#xff0c;使用count(*) 可以查出包含null在内的全部数据行数。 使用dis…