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

相关内容

热门资讯

系统如何与安卓互通,技术融合与... 你有没有想过,你的手机系统竟然能和安卓系统这么默契地互通有无?这就像是一场跨越科技界的友谊赛,让我们...
安卓系统 扫码枪,安卓系统下扫... 你有没有想过,在繁忙的超市收银台,那些快速流畅的扫码操作,背后其实隐藏着一个小小的英雄——安卓系统扫...
平板插卡推荐安卓系统,安卓系统... 你有没有想过,你的平板电脑是不是也能像智能手机一样,随时随地扩充存储空间呢?没错,这就是今天我要跟你...
安卓系统固件安装失败,原因排查... 最近是不是你也遇到了安卓系统固件安装失败的问题?别急,让我来给你详细说说这个让人头疼的小麻烦,让你一...
ios系统和安卓区别,系统差异... 你有没有发现,现在手机市场上,iOS系统和安卓系统就像是一对双胞胎,长得差不多,但性格却截然不同。今...
安卓系统2.3优酷,优酷的崛起... 你有没有发现,安卓系统2.3时代的那股怀旧风?那时候,优酷可是视频界的巨头,多少人都是看着优酷长大的...
安卓导航系统密封,安卓导航系统... 你有没有发现,现在手机导航系统越来越智能了?尤其是安卓系统的导航,简直就像一个贴心的导航小助手,带你...
a版安卓11系统,a版深度解析... 你知道吗?最近手机界可是炸开了锅,各大品牌纷纷发布了搭载a版安卓11系统的手机。这可不是什么小打小闹...
安卓系统的模拟吉他,随时随地弹... 你有没有想过,在手机上也能弹奏吉他呢?没错,就是那种模拟吉他的安卓系统应用,让你随时随地都能享受音乐...
王者适配的安卓系统,深度解析适... 你有没有发现,最近玩《王者荣耀》的小伙伴们都在议论纷纷,说新出的安卓系统简直是为王者量身定做的!没错...
安卓系统自动定位关闭,隐私保护... 你有没有发现,手机里的安卓系统有时候会自动定位,这可真是让人又爱又恨啊!有时候,我们并不想让别人知道...
安卓系统电量耗尽测试,全面解析... 手机电量耗尽,这可是每个手机用户都头疼的问题。你有没有想过,你的安卓手机在电量耗尽前,到底经历了哪些...
如何升级车载安卓系统,车载安卓... 亲爱的车主朋友们,你是不是也和我一样,对车载安卓系统升级这件事充满了好奇和期待呢?想象当你驾驶着爱车...
安卓办公哪个系统好,深度解析哪... 你有没有想过,在安卓办公的世界里,哪个系统才是你的最佳拍档呢?在这个信息爆炸的时代,选择一个既强大又...
安卓系统差劲怎么解决,重拾流畅... 你有没有发现,安卓系统有时候真的让人头疼得要命?手机卡顿、应用崩溃、电池续航短,这些问题是不是让你抓...
喜欢安卓系统的原因,探索用户偏... 你有没有发现,身边的朋友、同事,甚至家人,越来越多的人开始使用安卓手机了呢?这可不是简单的潮流,而是...
安卓系统金立手机,品质生活新选... 你有没有发现,最近安卓系统下的金立手机突然火了起来?没错,就是那个曾经陪伴我们走过无数时光的金立手机...
无安卓系统的电视,新型无系统电... 亲爱的读者们,你是否厌倦了那些充斥着安卓系统的电视?想要尝试一些新鲜玩意儿?那就跟我一起探索一下无安...
麒麟系统能刷安卓系统吗,轻松刷... 你有没有想过,你的麒麟手机能不能装上安卓系统呢?这可是个让人好奇不已的问题。现在,就让我来带你一探究...
手机公司安卓系统吗,手机公司引... 你有没有想过,为什么你的手机里装的是安卓系统而不是苹果的iOS呢?这背后可是有着不少故事和门道的哦!...