Java 中的锁 2022-02-11 14:06:00 编程 Java 暂无评论 523 次阅读 3444字 修改时间:2022-02-24 10:12:55 ### 对象锁和类锁 - 实例锁 直接使用 synchronize(this),或者直接把synchronize 加在方法上,在这种方式属于对象锁,只能锁住同一个对象的实例。如果new了多个对象,这种方式就没用了 - 类锁 synchronize加在静态方法,或者synchronize块使用静态变量或者类名称做为锁,类信息是存在 JVM 方法区的,并且整个 JVM 只有一份,方法区又是所有线程共享的,所以类锁是所有线程共享的。 ------------ ### synchronize 和 Lock的区别 - synchronize 为关键字,jvm层面的 - synchronize无法判断是否获取到了锁,Lock可以 - synchronize无需手动开启和释放锁(异常也会释放),Lock需要手动开启和释放锁,如果出现异常未释放,有可能会出现死锁 - Lock可以指定阻塞时间,synchronize会一直等待 - synchronize 可重入、不可中断、非公平,Lock 可重入、可中断、可公平(两者皆可) - synchronize 重量级,Lock 轻量级 ------------ ### volatile和synchronize的区别 volatile的作用:保证了线程的可见性,当一个共享变量被volatile修饰,变量被改变了,其他线程立马得知 - volatile只能作用域变量,synchronize可作用域变量、方法、类、同步代码块等 - volatile只能保证可见性和有序性,不能保证原子性,synchronize三者都可以保证。 - volatile不会造成线程阻塞,Synchronize可能会造成线程阻塞。 ------------ ### synchronize 运行状态 共有4种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级。 - 无锁 无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。上面我们介绍的CAS原理及应用即是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。 - 偏向锁 偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。 在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁。其目标就是在只有一个线程执行同步代码块时能够提高性能。 - 轻量级锁 是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能 自旋获取锁,自旋一定次数升级到重量级锁 - 重量级锁 monitor对象 升级为重量级锁时,此时等待锁的线程都会进入阻塞状态。 ------------ **synchronize 使用示例** ```java static String LOCK = ""; @Override public void run() { synchronized(LOCK){ System.out.println(this.getName() + "获得了锁"); for (int i = 0; i < 3; i++) { System.out.println(this.getName() + "--- " + i); } } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new Test3(), "线程1"); thread1.start(); Thread thread2 = new Thread(new Test3(), "线程2"); thread2.start(); } ``` **ReentrantLock 使用示例 ** ```java static Lock lock = new ReentrantLock(); @Override public void run() { lock.lock(); // 获取锁 , 获取不到会阻塞 try { System.out.println(this.getName() + "获得了锁"); for (int i = 0; i < 3; i++) { System.out.println(this.getName() + "--- " + i); } } finally { lock.unlock(); // 释放锁 } } public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new Test3(), "线程1"); thread1.start(); Thread thread2 = new Thread(new Test3(), "线程2"); thread2.start(); } ``` ------------ ### 锁的机制 #### 1.可重入锁 同步方法A()在调用同步方法B()时,由于是可重入锁,所以第二次无需在获取锁。 synchronized和ReentrantLock都是可重入锁 #### 2.可中断锁 如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,可以让它中断,这种就是可中断锁。lock.tryLock方法可以指定等待时间后中断。 synchronized就不是可中断锁,而lock是可中断锁。 #### 3.公平锁和非公平锁 比如同是有多个线程在等待一个锁,当这个锁被释放时,最先请求的线程会获得该所,这种就是公平锁。相反非公平锁则是所有等待中的线程都一视同仁,重新分配锁 synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。 lock默认是非公平锁,可以设置为公平锁。new ReentrantLock(true),true为公平锁 #### 4.自旋锁 自旋锁:自我旋转(循环)检测锁是否被释放,而不是挂起或者睡眠。 非自旋:获取不到锁时,阻塞睡眠挂起,直到获取到锁后被唤醒 自旋的好处是:为了避免阻塞和唤醒线程带来的高昂开销 自旋的坏处是:长时间占据CPU资源 用一句话总结自旋锁的好处,那就是自旋锁用循环去不停地尝试获取锁,让线程始终处于 Runnable 状态,节省了线程状态切换带来的开销。 示例:CAS(compare and set)原子操作 ```java static AtomicInteger sign = new AtomicInteger(); public static void lock() { while (!sign.compareAndSet(0, 1)) { System.out.println(Thread.currentThread().getName() + "---自旋等待中"); } } public static void unlock() { sign.compareAndSet(1, 0); } ``` 标签: Java
评论已关闭