当前位置: 首页 > news >正文

JUC入门(二)

5、8锁现象:就是关于锁的八个问题

谁来充当锁?要锁的是什么东西?这个锁有什么用?

其实锁的作用就是:哪个线程先拿到锁,谁就有先执行同步方法的权力

那么谁能充当锁?任何对象都可以充当锁

要锁的是什么东西?其实把被synchronized修饰的代码,当成一个厕所(共享资源)会更好理解,线程执行这段代码,也就相当于要进去厕所,你要使用厕所(执行代码的权力),就应该先获得一把锁!

深刻理解我们的锁的例子

package com.yw.lock8;import java.util.concurrent.TimeUnit;
//8锁,就是关于锁的八个问题
//问题1:标准情况下,哪个线程先打印?   打电话
//答:两个方法均是用synchronized修饰,锁的对象是方法的调用者,也就是phone这个对象,这个对象的锁只有一把
// ,而A对象先拿到这把锁,所以先执行打电话的操作//问题2:此时对打电话操作延迟四秒,哪个线程先打印? 依旧是打电话
//答:因为依旧是A先拿到了锁住了new出来的phone对象这把锁,所以Hello这个方法只能等待call执行完释放锁//问题4:此时多new一个Phone对象,分别执行打call和hello,谁先执行?hello
//答:因为执行的对象不同,所以两个锁并不是一把锁,自然不会出现争抢锁的问题,hello之前只会休眠一秒,而
//call会休眠四秒,所以hello先输出
public class Demo1 {public static void main(String[] args) {Phone phone1 = new Phone();Phone phone2 = new Phone();new Thread(phone1::call,"A").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}new Thread(phone2::hello,"B").start();
//        new Thread(phone::hi,"C").start();}}class Phone{//synchronized锁的对象是方法的调用者//两个方法用的同一把锁,所以谁先拿到谁执行public synchronized void call(){try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("打电话");}public synchronized void hello(){System.out.println("Hello");}//问题3:增加了一个普通方法hi,我们添加一个线程来执行该方法,谁先执行? hi//答:hi方法没有被synchronized修饰,所以不需要锁,所以先执行,然后因为call方法先拿到锁,所以顺序为//hi->call->hello
//    public void hi(){
//        System.out.println("Hi~");
//    }
}
package com.yw.lock8;import java.util.concurrent.TimeUnit;
//问题5:在两个方法前均加入static,只有一个对象,现在会先输出什么?  打电话
//答:因为只有一个Phone对象,谁先拿到锁就执行谁//问题6:两个对象,分别执行两个静态方法,谁先输出? 依旧是打电话
//答:因为static锁的是Phone这个类,无论有几个对象,锁只有一个,所以因为call先拿到这把锁,依旧先输出打电话
public class Demo2{public static void main(String[] args) {Phone2 phone1 = new Phone2();Phone2 phone2 = new Phone2();new Thread(()->{phone1.call();},"A").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}new Thread(()->{phone2.hello();},"B").start();}}class Phone2{
//static 表示这是个静态方法,表示类一加载就有了,所以它锁的是类,它锁的是class,这里的class(类)只有一个,那就是Phonepublic static synchronized void call(){try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("打电话");}public static synchronized void hello(){System.out.println("Hello");}}
package com.yw.lock8;import java.util.concurrent.TimeUnit;
//问题7:一个对象,将hello的static取消,此时先输出什么? hello
//答:因为一个锁的是类Phone,一个锁的是对象,并不是一把锁,所以不会发生争抢,call延迟4s,hello延迟
//1s,所以先输出hello//问题8:两个个对象,将hello的static取消,此时先输出什么? hello
//答:依旧不是一个锁
public class Demo3{public static void main(String[] args) {Phone3 phone1 = new Phone3();Phone3 phone2 = new Phone3();new Thread(()->{phone1.call();},"A").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}new Thread(()->{phone2.hello();},"B").start();}}class Phone3{//static 表示这是个静态方法,表示类一加载就有了,所以它锁的是类,它锁的是class,这里的class(类)只有一个,那就是Phonepublic static synchronized void call(){try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("打电话");}public  synchronized void hello(){System.out.println("Hello");}}

6、集合类不安全

List

list不安全

比如当我们执行下面这段代码

package com.yw.unsale;import java.util.ArrayList;
import java.util.List;
import java.util.UUID;public class ListTest {public static void main(String[] args) {List<String> list = new ArrayList<>();for (int i = 1 ;i<=10000; i++){new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,5));System.out.println(list);},String.valueOf(i)).start();}}
}

会抛出我们以后会熟悉的异常

java.util.ConcurrentModificationException 并发修改异常

这个异常在所有集合处理并发是都有可能出现,在list集合中应该如何解决?

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

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

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

其中Vector是以前的老技术,其底层的add方法就是通过synchronized来修饰的

而现在的新技术CopyOnWriteArrayList是通过lock锁来实现的

这时我们要介绍

CopyOnWrite:写入时复制 简称cow

当多个线程或进程共享同一份数据时,只有在需要修改数据时,才真正创建数据的副本,并对副本进行修改

CopyOnWrite和Vector两者有什么区别?
1. 线程安全机制
  • Vector

    • 使用内置锁(synchronized)来保证线程安全。

    • 所有修改操作(如 addremoveset 等)和迭代操作都会加锁。

    • 优点是实现简单,缺点是锁的粒度较粗,容易导致性能瓶颈,尤其是在高并发场景下。

  • CopyOnWriteArrayList

    • 使用写入时复制(Copy-On-Write)机制来保证线程安全。

    • 修改操作(如 addset 等)会创建一个新的数组副本,并在副本上进行操作,而读操作直接在原始数组上进行。

    • 读操作不需要加锁,因此在读多写少的场景下性能更高。

2. 性能对比
  • 读操作

    • Vector:每次读操作都需要加锁,即使只是读取数据,也会因为锁的开销而降低性能。

    • CopyOnWriteArrayList:读操作完全不需要加锁,直接在原始数组上进行,性能非常高。

  • 写操作

    • Vector:写操作需要加锁,并且每次修改都会触发数组扩容(如果达到容量上限),这可能导致较大的性能开销。

    • CopyOnWriteArrayList:写操作需要创建数组副本,虽然第一次写入时会有较大的开销,但后续的读操作不会受到影响。如果写操作较少,这种开销是可以接受的。

3. 迭代安全性
  • Vector

    • 在迭代过程中,如果对集合进行修改(如添加或删除元素),会抛出 ConcurrentModificationException

    • 虽然 Vector 是线程安全的,但在并发环境下,迭代操作仍然需要额外的同步机制来避免异常。

  • CopyOnWriteArrayList

    • 在迭代过程中,即使其他线程对集合进行了修改,也不会影响当前迭代操作。

    • 因为迭代操作是在原始数组上进行的,而修改操作是在新的数组副本上进行的,所以不会抛出 ConcurrentModificationException

4. 内存占用
  • Vector

    • 内存占用相对较小,因为它只维护一个数组。

    • 但每次数组扩容时,会创建一个新的数组,并将旧数组的内容复制到新数组中,这可能会导致短暂的内存占用增加。

  • CopyOnWriteArrayList

    • 内存占用可能会更大,因为每次写操作都会创建一个新的数组副本。

    • 但在写操作较少的场景下,这种内存开销是可以接受的,并且可以避免频繁的锁竞争。

5. 使用场景
  • Vector

    • 适用于读写操作较为均衡,且对性能要求不高的场景。

    • 由于其锁机制较为简单,适合在单线程或多线程环境下使用,但并发性能较差。

  • CopyOnWriteArrayList

    • 适用于读多写少的场景,例如:

      • 日志记录:多个线程写入日志,但读取日志的操作较少。

      • 配置管理:配置信息被频繁读取,但修改操作较少。

      • 缓存数据:缓存数据被频繁读取,但更新操作较少。

总结

CopyOnWriteArrayList 在读多写少的场景下比 Vector 更具优势,主要体现在以下几点:

  1. 读操作性能高CopyOnWriteArrayList 的读操作不需要加锁,性能更高。

  2. 迭代安全性CopyOnWriteArrayList 在迭代过程中不会抛出 ConcurrentModificationException,更适合并发环境。

  3. 锁的粒度更细CopyOnWriteArrayList 的写操作不会影响读操作,减少了锁的竞争。

然而,CopyOnWriteArrayList 也有缺点,例如写操作时会创建数组副本,可能会导致较大的内存开销和首次写入的性能开销。因此,在选择时需要根据具体的使用场景来决定。

Set

set不安全

解决方法:

  1. Set<String> set= Collections.synchronizedSet(new HashSet<>())

  2. Set<String> set= new CopyOnWriteHashSet<>();

补充:HastSet的底层是什么? HashMap

那么HashMap的底层又是什么?

add set 的本质就是map key是无法重复的!

Map

map 不安全

解决方法:

  1. Map<String> map= Collections.synchronizedMap(new HashMap<>())

  2. Map<String> map= new CopyOnWriteHashMap<>();

http://www.xdnf.cn/news/512605.html

相关文章:

  • [创业之路-362]:企业战略管理案例分析-3-战略制定-华为使命、愿景、价值观的演变过程
  • 开源项目实战学习之YOLO11:12.5 ultralytics-models-sam.py通用图像分割模型源码分析
  • Django学习
  • **HTTP/HTTPS基础** - URL结构(协议、域名、端口、路径、参数、锚点) - 请求方法(GET、POST) - 请求头/响应头 - 状态码含义
  • IS-IS 中间系统到中间系统
  • ASCII码表
  • 离散文本表示
  • Java IO框架
  • YOLO12改进-模块-引入Channel Reduction Attention (CRA)模块 降低模型复杂度,提升复杂场景下的目标定位与分类精度
  • 云原生安全:IaaS安全全解析(从基础到实践)
  • Linux 安装 Unreal Engine
  • 4.1.8文件共享
  • MCP实战:在扣子空间用扣子工作流MCP,一句话生成儿童故事rap视频
  • java中的Servlet3.x详解
  • 07、基础入门-SpringBoot-自动配置特性
  • wsl2中Ubuntu22.04配置静态IP地址
  • 荔枝成熟度分割数据集labelme格式2263张3类别
  • 基于PageHelper的分页查询
  • MyBatis-Plus 的 updateById 方法不更新 null 值属性的问题
  • MySQL--day2--基本的select语句
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Expanding Cards (展开式卡片)
  • AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年5月18日第81弹
  • symfonos: 1靶场
  • 一个stm32工程从底层上都需要由哪些文件构成
  • 【ROS2】RViz2源码分析(九):RosClientAbstraction和RosNodeAbstraction的关系
  • Android 性能优化入门(二)—— 内存优化
  • MATLAB安装常见问题解决方案
  • C++23 放宽范围适配器以允许仅移动类型(P2494R2)
  • Java求职者面试:从Spring Boot到微服务的技术点解析
  • 什么是机器学习?