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 的线程, 都会参与搬家的过程. 每个操作负责搬运一小 部分元素.
    • 搬完最后一个元素再把老数组删掉.
    • 这个期间, 插入只往新数组加.
    • 这个期间, 查找需要同时查新数组和老数组.

相关内容

热门资讯

安卓9.0系统挂机游戏,轻松享... 你有没有发现,自从安卓9.0系统更新后,手机里的游戏体验简直就像坐上了火箭!今天,就让我带你一起探索...
安卓系统怎么用迅雷下载,安卓系... 你有没有想过,在安卓系统上下载文件竟然也能这么简单?没错,今天就要来给你揭秘,如何用迅雷在安卓系统上...
安卓手机刷成学生系统,探索全新... 你有没有想过,你的安卓手机其实可以变身成一个充满学习氛围的学生系统呢?没错,就是那种看起来简洁、功能...
ios能迁移安卓系统吗,iOS... 你有没有想过,你的iPhone里的那些宝贝应用,能不能搬到安卓手机上继续使用呢?这可是不少手机用户的...
荣耀10安卓11系统,畅享极致... 你知道吗?最近手机界可是热闹非凡呢!荣耀10这款手机,自从升级到了安卓11系统,简直就像脱胎换骨了一...
安卓系统pc版电脑配置,打造流... 你有没有想过,安卓系统竟然也能在电脑上运行呢?没错,就是那个我们手机上常用的安卓系统,现在也能在PC...
tcllinux系统刷安卓系统... 你有没有想过,你的TCL Linux系统竟然也能升级成安卓系统呢?没错,就是那个我们日常使用的安卓系...
安卓13系统更新蓝牙,蓝牙功能... 你有没有发现,最近你的安卓手机好像变得不一样了?没错,就是那个神秘的安卓13系统更新,它悄悄地来到了...
安卓系统钉钉打开声音,安卓系统... 你有没有遇到过这种情况?手机里装了钉钉,可每次打开它,那声音就“嗖”地一下跳出来,吓你一跳。别急,今...
理想汽车操作系统安卓,基于安卓... 你有没有想过,一辆汽车,除了能带你去你想去的地方,还能像智能手机一样,给你带来智能化的体验呢?没错,...
安卓系统越狱还能升级吗,升级之... 你有没有想过,你的安卓手机越狱后,还能不能愉快地升级系统呢?这可是不少手机爱好者关心的大问题。今天,...
安卓系统蓝牙耳机拼多多,畅享无... 你有没有发现,最近蓝牙耳机在市场上可是火得一塌糊涂呢!尤其是安卓系统的用户,对于蓝牙耳机的要求那可是...
安卓变苹果系统桌面,桌面系统变... 你知道吗?最近有个大新闻在科技圈里炸开了锅,那就是安卓用户纷纷转向苹果系统桌面。这可不是闹着玩的,这...
鸿蒙系统怎么下安卓,鸿蒙系统下... 你有没有想过,你的手机里那个神秘的鸿蒙系统,竟然也能和安卓世界来一场亲密接触呢?没错,今天就要来揭秘...
手机安卓系统流程排行,便捷操作... 你有没有发现,现在手机的世界里,安卓系统就像是个大舞台,各种版本层出不穷,让人眼花缭乱。今天,就让我...
安卓系统左上角hd,左上角HD... 你有没有发现,每次打开安卓手机,左上角那个小小的HD标识总是默默地在那里,仿佛在诉说着什么?今天,就...
安卓系统软件文件,架构解析与功... 你有没有发现,手机里的安卓系统软件文件就像是一个神秘的宝库,里面藏着无数的宝藏?今天,就让我带你一起...
安卓系统输入法回车,探索安卓输... 你有没有发现,在使用安卓手机的时候,输入法回车键的奇妙之处?它就像是我们指尖的魔法师,轻轻一点,文字...
安卓修改系统时间的软件,轻松掌... 你有没有想过,有时候手机上的时间不对劲,是不是觉得生活节奏都被打乱了?别急,今天就来给你揭秘那些神奇...
安卓系统能改成鸿蒙吗,系统迁移... 你有没有想过,你的安卓手机能不能变成一个鸿蒙系统的“小清新”呢?这可不是天方夜谭哦,今天就来聊聊这个...