【Java 并发编程】线程锁机制 ( 线程安全 | 锁机制 | 类锁 | 对象锁 | 轻量级锁 | 重量级锁 )

2023-03-29 16:51:13 浏览数 (1)

文章目录

  • 一、线程安全
  • 二、锁机制 ( 类锁 | 对象锁 )
  • 三、锁分类 ( 轻量级锁 | 重量级锁 )

一、线程安全


多个线程同时访问 同一个共享变量 时 , 只要能保证 数据一致性 , 那么该变量是线程安全的 ; 这里的数据是指主内存中的共享变量以及各个线程中的变量副本 , 保证这些变量一致 , 就是线程安全 ;

线程安全 就是保证 线程操作的 原子性 , 可见性 , 有序性 ;

volatile 关键字可以保证 可见性 与 有序性 ;

synchronized 关键字可以保证 原子性 ;

二、锁机制 ( 类锁 | 对象锁 )


synchronized 是 Java 提供的一种锁机制 ;

在普通方法上加锁 , 相当于对 this 进行加锁 ; 下面两个类的 fun 方法的线程锁是等效的 ;

代码语言:javascript复制
public class Student {
    private synchronized void fun() {
    }
}
代码语言:javascript复制
public class Student {
    private void fun() {
        synchronized(this){
        }
    }
}

加锁的代码块 , 在同一个时间 , 只能由

1

个线程访问 ;

对象锁 : synchronized() 代码块中 , 括号中的参数是 作用范围 ; synchronized(this) 表示作用范围只针对当前对象 , 如果 创建了多个对象 , 这几个对象中的锁都是 不同的锁 , 相互之间没有任何关系 ;

代码语言:javascript复制
Student s1 = new Student();
Student s2 = new Student();

只有当多个线程 , 访问同一个对象时 , 锁才有意义 ;

如 : 线程 A 访问 s1 对象的 fun 方法 , 线程 B 访问 s2 对象的 fun 方法 , 两个方法之间 没有互斥效果 ; 线程 A 访问 s1 对象的 fun 方法 , 线程 B 也想访问 s1 对象的 fun 方法 , 此时必须 等待线程 A 访问完毕 , 释放锁之后 , 才能由线程 B 访问 s1 ;

类锁 : 如果加锁的对象是静态方法 , 那么相当于在 Student.class 类上进行加锁 ; Student.class 对象全局只有

1

个 , 调用所有对象的 fun 方法 , 都是互斥的 ;

代码语言:javascript复制
public class Student {
    private synchronized static void fun() {
    }
}

等价于

代码语言:javascript复制
public class Student {
    private static void fun() {
        synchronized(Student.class){
        }
    }
}

三、锁分类 ( 轻量级锁 | 重量级锁 )


如果线程 A 获得锁之后 , 执行线程内容 , 其它线程等待解锁时有两种情况 :

  • 轻量级锁 : 又称为 自旋锁 , 线程 盲等待 或 自旋等待 , 即 while 循环 , 没有进入阻塞状态 , 没有进入等待队列中排队 ; ( 轻量级 )
  • 重量级锁 : 线程进入 等待队列 , 排队等待线程 A 执行完毕 ; 在该队列的线程 , 需要 等待 OS 进行线程调度 , 一旦涉及到操作系统 , 量级就变重 , 效率变低 ; ( 重量级 )

轻量级锁弊端 : 轻量级锁 不一定 比重量级锁 更好 ; 轻量级锁 等待过程中 , 高速执行循环代码 , 如果循环的时间很短 , 时间效率上很高 ; 但是一旦执行时间很长 , 比如连续执行十几秒甚至几分钟 , 浪费了大量的 CPU 资源 ;

使用场景 :

  • 轻量级锁 : 轻量级锁只适合 线程少 , 等待时间短的 应用场景 , 如果线程很多 , 等待时间过长 , 会造成 CPU 大量浪费 ;
  • 重量级锁 : 重量级锁等待过程中 , 线程处于阻塞状态 , 效率可能低一些 , 但是不会造成资源浪费 , 如果 线程很多 , 或 等待时间很长 , 适合使用重量级锁 ;

0 人点赞