synchronized的优化机制和一些多线程的常见类
admin
2024-01-20 17:50:56
0

1.synchronized

1.1.我们现在知道常用的锁策略

  1. 乐观锁 vs 悲观锁
  2. 读写锁 vs 普通的互斥锁
  3. 重量级锁 vs 轻量级锁
  4. 挂起等待锁 vs 自旋锁
  5. 公平锁 vs 非公平锁
  6. 可重入锁 vs 不可重入锁

那synchronized基于这些策略有哪些特性呢?

  1. synchronized 开始时是乐观锁,如果锁冲突频繁,就转换为悲观锁.
  2. synchronized 不是读写锁,是普通互斥锁
  3. synchronized 开始是轻量级锁实现,如果锁被持有的时间较长,就转换成重量级锁.
  4. synchronized 实现轻量级锁的时候大概率用到的自旋锁策略,重量级锁的部分基于挂起等待实现
  5. synchronized 是一种非公平锁
  6. synchronized 是一种可重入锁

1.2.synchronized 的一些锁优化机制(jdk 1.8)

1.2.1.锁膨胀

JVM 将 synchronized 锁分为四种情况.

  1. 无锁
  2. 偏向锁
  3. 轻量级锁
  4. 重量级锁

四种锁状态也会根据实际情况依次进行升级.

  1. 没有加锁的状态
  2. 首个线程加锁,就会进入偏向锁状态(只是给对象头中做一个 “偏向锁的标记”, 记录这个锁属于哪个线程,并没有真的加锁)
  3. 如果偏向锁后续有其他线程来竞争该锁,那就取消原来的偏向锁状态, 进入一般的轻量级锁状态.(自适应的自旋锁)
  4. 如果竞争进一步激烈,自旋不能快速获取到锁状态,就会膨胀为重量级锁(用到内核提供的mutex.)

1.2.2.锁粗化

锁粗化涉及到锁的粒度

  • 锁的粒度: 加锁代码涉及到的范围.(不是整体多个加锁总和的范围,而是单个锁涉及的范围)
  1. 加锁代码的范围越大,认为锁的粒度越粗.
  2. 加锁代码的范围越小,则认为粒度越细
  • 如果两次加锁之间隔的代码比较多,不会影响整体运行状态的情况下,一般不会优化.
  • 如果之间间隔比较小(中间间隔的代码少),间隔过少的时候可能涉及多次释放锁后,又马上申请锁的操作.就很可能触发这个优化(JVM 就会自动把锁粗化)

1.2.3.锁消除

有这么一种情况: 有些代码,明明不用加锁,结果你给加上锁了.编译器就会发现这个加锁好像没啥必要,就直接把锁给去掉了(锁消除).

当然你可能说,我不会乱加锁的.不过,有的时候加锁操作并不是那么明显,稍不留神就会做出了这种错误的决定.

例如: StringBuffer, Vector…这些类,在标准库中进行了加锁操作,而我们在单个线程中用到这些类的时候,就是单线程进行了加锁解锁.而我们并不会发觉,不过我们也不用担心.因为编译器会发现并处理这一类情况,也就是上述的锁消除.

2.Callable接口

2.1.Callable接口的使用

Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为 Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定.所以 FutureTask 就可以负责这个 等待结果出来 的工作.

  • 创建一个匿名内部类, 实现 Callable 接口. Callable 带有泛型参数. 泛型参数表示返回值的类型.
  • 重写 Callable 的 call 方法, 完成一个任务. 直接通过返回值返回计算结果.
  • 把 callable 实例使用 FutureTask 包装一下.
  • 创建线程, 线程的构造方法传入 FutureTask .此时新线程就会执行 FutureTask 内部的 Callable 的 call 方法, 完成计算. 计算结果就放到了 FutureTask 对象中.
  • 在主线程中调用 task.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果.
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** Callable 接口* java.util.concurrent* 

* Callable 解决了 Runnable 不方便返回结果的问题*/ public class Test {public static void main(String[] args) {Callable callable = new Callable() {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i <= 1000; i++) {sum += i;}return sum;}};//创建线程,完成任务//为了让线程执行 callable 中的任务,光使用构造方法还不够,还需要一个辅助的类//FutureTask 运行结果会保存在这个类中FutureTask task = new FutureTask<>(callable);//任务Thread t = new Thread(task);t.start();try {//如果线程的任务没有执行完,get就会阻塞,一直阻塞到,有return 的结果System.out.println(task.get());} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}} }

2.2.Callable 和 Runnable 比较

  • 都是描述一个任务
  • Callable 描述的是带有返回值的任务.
  • Runnable 描述的是不带返回值的任务.

3.JUC(java.util.concurrent) 中的常见类

3.1.ReentrantLock:可重入锁

有三个主要的方法

  1. lock()方法 : 加锁,如果获取不到锁就死等.
  2. unlock()方法 : 解锁
  3. trylock(超时时间)方法 : 也是加锁,不同于lock()方法的是这个方法懂得放弃加锁失败,不会死等,而是等待一定的时间之后就放弃加锁

相对于习惯使用 synchronized 的我来说,这里加锁和解锁 分开的做法,是不太友好的,因为很容易遗漏 unlock(),出现锁死.

3.1.1.ReentrantLock与synchronized的区别

  1. synchronized是一个关键字(背后的逻辑是JVM内部实现的,C++),ReentrantLock是一个标准库中的类(背后的逻辑是Java代码写的)

  2. synchronized 不需要手动释放锁,出了代码块,锁自然释放.ReentrantLock必须要手动释放锁,要谨防忘记释放.

  3. synchronized 如果竞争锁的时候失败,就会阻塞等待.但是ReentrantLock除了阻塞等待这一手之外,还有一手, trylock()超时失败了会返回.

  4. synchronized是一个非公平锁.ReentrantLock 提供了非公平和公平锁两个版本!在构造方法中,通过参数就可以来指定当前是公平锁还是非公平锁.

    • ReentrantLock locker = new ReentrantLock(true);
    • 参数为true 时该类为公平锁
    • 参数默认是false 默认该类为非公平锁

3.1.1.ReentrantLock类使用

import java.util.concurrent.locks.ReentrantLock;/*** ReentrantLock : 可重入锁   */
public class Test2 {public static void main(String[] args) {ReentrantLock locker = new ReentrantLock(true);//参数为true 为公平锁  默认为false 非公平锁locker.lock();//加锁try {} finally {locker.unlock();//解锁}}
}

3.2.Semaphore:信号量

锁就相当于一个二元信号量,可用资源只有一个.计数器非0,即1

Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用.

类的参数为信号量个数

3.2.1.PV操作

  1. acquire()方法 P操作(申请资源):无资源的时候获取会阻塞等待资源释放
  2. release()方法 V操作(释放资源)

3.2.2.Semaphore类使用

import java.util.concurrent.Semaphore;/*** Semaphore 信号量* 锁:二元信号量, 可用资源就一个,计数器 非0,即1*/
public class Test {public static void main(String[] args) throws InterruptedException {Semaphore semaphore = new Semaphore(4);//参数为4 代表可用资源有 4 个//申请资源 P操作semaphore.acquire();//1System.out.println("申请成功");semaphore.acquire();//2System.out.println("申请成功");semaphore.acquire();//3System.out.println("申请成功");semaphore.acquire();//4System.out.println("申请成功");
//        semaphore.acquire();//5 阻塞等待
//        System.out.println("申请成功");semaphore.release();//释放资源 V操作semaphore.acquire();System.out.println("申请成功");}
}

3.3.CountDownLatch 同时等待N个任务执行结束.

类的参数.表示需要等待任务的个数.

3.3.1.方法使用

  • countDown()方法.任务调用此方法表示任务结束.此时CountDownLatch内部的计数器减1.
  • await()方法.调用此方法的线程阻塞等待所有任务执行完毕. 就是计数器减为0的时候.

3.3.2.CountDownLatch类使用

import java.util.concurrent.CountDownLatch;/*** CountDownLatch 同时等待 N 个任务执行结束*/
public class Test {public static void main(String[] args) throws InterruptedException {//构造方法的参数表示 有多少个选手参赛CountDownLatch countDownLatch = new CountDownLatch(10);for (int i = 0; i < 10; i++) {Thread t = new Thread(() -> {try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "到达终点");countDownLatch.countDown();});t.start();}countDownLatch.await();//等待所有线程到达System.out.println("比赛结束");}
}

3.4.CopyOnWriteArrayList 写时拷贝

当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy.复制出一个新的容器后往新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器.

这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为只读的时候不会有线程安全问题.

3.5.ConcurrentHashMap 多线程环境下可以使用的哈希表

ConcurrentHashMap 相比于 Hashtable 又做出了一系列的改进和优化

  • 读操作没有加锁(但是使用了 volatile 保证从内存读取结果),只对写操作进行加锁.加锁的方式仍然是用 synchronized,但不是对整个对象加锁, 而是 “锁桶” (用每个链表的头结点作为锁对象,让锁加到每个链表的头结点上), 大大降低了锁冲突的概率.
  • 充分利用 CAS 特性. 比如 size 属性通过 CAS 来更新. 避免出现重量级锁的情况.
  • 优化了扩容方式: 化整为零
    • 发现需要扩容的线程, 只需要创建一个新的数组, 同时只搬几个元素过去.
    • 扩容期间, 新老数组同时存在.
    • 后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程. 每个操作负责搬运一小 部分元素.
    • 搬完最后一个元素再把老数组删掉.
    • 这个期间, 插入只往新数组加.
    • 这个期间, 查找需要同时查新数组和老数组.

相关内容

热门资讯

安卓免费私有云盘系统,探索安卓... 你有没有想过,你的手机里那些珍贵的照片、文件和视频,如果有一天突然丢失了,那该有多心疼啊!别担心,今...
安卓系统怎么装分身,安卓系统轻... 你是不是也和我一样,对安卓系统的分身功能充满了好奇?想要在手机上同时使用两个账号,但又不知道怎么操作...
安卓系统手机照片云恢复 手机里的照片丢失了,是不是瞬间感觉心情down到了谷底?别急,今天就来给你支个招,让你的安卓系统手机...
安卓系统的缓存怎么清,轻松优化... 手机用久了是不是感觉越来越卡?别急,今天就来教你怎么给安卓系统的缓存来个大扫除,让你的手机恢复活力!...
安卓4.4系统gps补丁 你知道吗?安卓4.4系统,也就是传说中的KitKat,可是智能手机界的一颗璀璨明珠呢!不过,就像所有...
安卓系统主界面设置,个性化与便... 你有没有发现,每次打开安卓手机,那主界面就像是个五彩斑斓的小天地?今天,就让我带你一起探索这个神奇的...
鼎桥是安卓系统吗,安卓系统背后... 你有没有听说过鼎桥这个品牌?最近它在手机市场上可是挺火的呢!不过,说到鼎桥,很多人都会好奇一个问题:...
红米note安卓7.0系统,畅... 你有没有发现,最近你的手机是不是有点儿不一样了?没错,就是那个陪伴你度过了无数日夜的红米Note,它...
安卓手机怎么安xp系统下载地址... 你有没有想过,把Windows XP这样的经典操作系统安装在安卓手机上?听起来是不是有点不可思议?但...
ios系统能转回安卓吗,iOS... 你有没有想过,手机系统之间的转换,就像是从一个熟悉的地方跳到另一个全新的世界呢?今天,我们就来聊聊这...
安卓系统怎么分屏聊天 你是不是也和我一样,手机里同时挂着好几个聊天窗口,一边和闺蜜聊着天,一边还得回复领导的邮件,感觉手忙...
苹果查安卓手机定位系统,安卓手... 你有没有想过,你的手机里藏着一个小秘密?没错,就是那个能让你随时随地找到它的神奇功能——定位系统。今...
安卓原生系统关闭锁定,轻松关闭... 你有没有发现,手机用久了,有时候安卓系统的锁定功能就像是个小顽皮,时不时地跳出来给你点小麻烦?别急,...
安卓系统的屏幕怎么调,安卓屏幕... 手机屏幕亮度总是调得刚刚好,是不是有点儿小烦恼呢?别急,今天就来手把手教你如何轻松调整安卓系统的屏幕...
光遇安卓系统崩了,官方紧急应对 最近光遇的安卓玩家们可真是有点头疼呢!你有没有发现,最近光遇安卓系统突然“崩了”,让不少玩家都陷入了...
安卓系统直播触摸大屏 你有没有想过,在安卓系统的世界里,直播和触摸大屏的结合能擦出怎样的火花呢?想象你正躺在沙发上,手捧着...
安卓系统映像预览版,前沿技术一... 你知道吗?最近安卓系统又出新花样了!这不,安卓系统映像预览版新鲜出炉,让我来带你一探究竟,看看这个新...
安卓系统如何防止死机,安卓系统... 手机突然卡顿,屏幕上那些图标就像被施了魔法一样,动也不动。你是不是也遇到过安卓系统死机的情况,心里那...
安卓系统手机自动播放,安卓手机... 你有没有发现,最近你的安卓手机是不是有点儿“调皮”呢?它总是在你最不想被打扰的时候,自动播放那些让你...
mini主机双系统安卓,轻松实... 你有没有想过,家里的电脑已经满足不了你的需求了?想要一台既能运行Windows系统,又能畅玩安卓应用...