目录

一、锁(一):对象的锁属性[作用/实现/修饰/危害]

1.作用

2.实现

2.1线程单代码块竞争锁

2.2锁对象判持有层信息

3.修饰

4.危害

二、线程卡住

死锁

解决方法

三、线程安全(二).1:不安全原因和措施

1.不安全原因

2.措施


一、锁(一):对象的锁属性[作用/实现/修饰/危害]

知识连接:

锁(二):位置/阻塞/重入/连续

锁(三):锁策略

锁(四):synchronized&ReentrantLock

Java每个类实例对象都有的 装自带属性的 对象头内存空间里面 有锁属性

1.作用

用来锁相对代码块 调度到 相对只单线程内地 此代码块执行


2.实现

2.1线程单代码块竞争锁

一个锁对象 初始为零持有层 只能拥单线程的个代码块竞争到持有,竞争到锁 每进一层代码块 就增一层持有层信息,执行完出一层代码块 就减一层持有层,竞争到锁的代码块 可以给子代码块 可重入加层地共用

2.2锁对象判持有层信息

锁对象 用线程代码块持有层信息

  • 竞争到的非零持有层线程代码块 供锁保障
  • 零持有层线程代码块 阻塞运行供竞争机会无锁就会被阻塞,处于可参与竞争的现阻塞状态
  • 锁的使作

1. 已被一个线程占有的锁 使等 正向获取锁的其它线程

2. 未被占有的锁 正向获取锁的线程竞争


3.修饰

(1)synchronized修饰静态方法,是以 类对象 为锁对象 加锁方法的整个代码块

(2)synchronized修饰非静态方法,是以 this本实例对象 为锁对象 加锁方法的整个代码块

class Counter {
    public int count;
    
    //increase1、2等效:
    synchronized public void increase1() {
        count++;
    }
    public void increase2() {
        synchronized (this) {
            count++;
        }
    }

    //increase3、4等效:
    synchronized public static void increase3() {
        
    }
    public static void increase4() {
        synchronized (Counter.class) {

        }
    }
}
  • synchronized锁特点

synchronized可重入自适应非公平互斥 的锁


4.危害

有加锁就会带来锁竞争锁阻塞 会使cpu性能大幅下降

  • 锁的开销        

1. 加锁获取锁 的开销

2. 解锁释放锁 的开销


二、线程卡住

遇阻象 找另路 地单路线搜索解决,当无另路另路循回阻象时 便卡住无法解决

死锁

锁阻象的 另路循回阻塞时 便形成了死锁

解决方法

通过删除嵌套锁的代码结构统一用锁的顺序 就可破坏掉锁请求的环路结构,解决死锁

例:线程统一先用小锁再用大锁

class Test {
    private static Object locker1 = new Object();
    private static Object locker2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (locker1) {
                // sleep 原先确保 t1和t2都分别拿到loker1和loker2后 再进行后续动作 发现死锁
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (locker2) {
                    System.out.println("加锁成功!");
                }
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (/*locker2->*/locker1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (/*locker1->*/locker2) {
                    System.out.println("加锁成功!");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

三、线程安全(二).1:不安全原因和措施

一线程连读 另线程进写 遇到已选择略读的无感

知识连接:

线程安全(二).2:内存可见性

线程安全(二).3:总

1.不安全原因

数据读取流向

(硬盘->)内存->寄存器->cpu

读硬盘到内存 相对 读内存到寄存器 非常慢、读内存到寄存器 相对 读寄存器到cpu 又非常慢】


编译器为提高代码执行效率,在保持单线程视野 逻辑不变的前提下 调整生成 优化执行的代码内容,但在多线程下 就可能会出现 视野缺陷下带来的出错优化

保障的单线程视野下,要多次读取 单视野固定无变内存 到寄存器时 可能会 简化成 只首读不变的一次 而错过 外线程修改内存时的 如果那时还在读 的正常读取

  • 略掉读取线2犯的错,如果就算没略掉 读取也未同步线1也犯错
  • 主内存 是真正的定位置的内存
  • 工作内存 是不定的 存储数据的"非内存"空间,通常包括 各种cpu寄存器和缓存

2.措施

volatile确定 不会对内存略读,保障了内存的可见性

class Test {
    private static volatile int isQuit = 0;// volatile 确定不会对isQuit进行内存略读

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (isQuit == 0) {
                // 循环体里啥都没干,此时意味着这个循环,一秒钟就会执行很多很多次
                // 编译器在单线程视野内 对内存无变的isQuit 可能会 只首读不变的一次,以略读来提升效率
            }
            System.out.println("t1 退出!");
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            System.out.println("请输入 isQuit: ");
            Scanner scanner = new Scanner(System.in);
            // 一旦用户输入的值不为0 就会使t1线程执行结束
            isQuit = scanner.nextInt();
        });
        t2.start();
    }
}

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐