synchronized的原理和Callable接口

编程入门 行业动态 更新时间:2024-10-28 02:20:29

synchronized的原理和Callable<a href=https://www.elefans.com/category/jswz/34/1771365.html style=接口"/>

synchronized的原理和Callable接口

目录

♫synchronized原理

♪锁升级

♪锁优化

 

♫Callable接口


♫synchronized原理

我们知道synchronized锁可以控制多个线程对共享资源的访问,两个线程针对同一变量访问就会产生阻塞等待。而synchronized锁并不是一成不变的,它会根据情况进行一次升级。

♪锁升级

JVM 将synchronized锁分为 无锁 → 偏向锁 → 轻量级锁 → 重量级锁 四种状态。 ♩偏向锁

第一个尝试加锁的线程就会优先进入偏向锁状态。偏向锁并不是真正的加锁,只是给线程一个偏向锁的标记,如果一直没有其他线程尝试加锁,则等到synchronized执行完取消偏向锁标记即可,如果有其他线程尝试加锁,则再真在进行加锁,让其它线程进入阻塞等待。(类似前面介绍过的懒汉模式,能不加锁则不加)

举个例子:假设男主是一个锁, 女主是一个线程。如果只有这一个线程来使用这个锁,那么男主女主即使不领证结婚(避免了高成本操作),也可以一直幸福的生活下去。但是女配出现了,也尝试竞争男主,此时不管领证结婚这个操作成本多高,女主也势必要把这个动作完成了,让女配死心。

♩轻量级锁

当有其他线程尝试加锁 ,偏向锁状态就被消除, 进入了轻量级锁状态 ( 自适应的自旋锁 )。这里的轻量级锁就是通过CAS实现的自旋锁,由于自旋锁需占用CPU不断尝试加锁,如果一直自旋会很浪费CPU资源,故synchronized会通过一个计数器,记录自旋次数,一定超出某个阈值就不再自旋。

♩重量级锁

如果竞争进一步激烈 , 自旋不能快速获取到锁状态 , 就会转换为重量级锁。这里的重量级锁就是使用OS提供的 mutex 锁,此时其它线程尝试加锁就是在内核态来判断锁是否被占用,如果没被占用则返回用户态,否则就进入等待队列,等待操作系统唤醒

♪锁优化

♩锁消除 锁消除是指编译器自动判断程序中某些代码块不需要同步保护,因而消除这些代码块中的锁,从而提高程序的执行效率。 锁消除的主要依据是:如果程序中某些代码块不会被多个线程同时访问,那么这些代码块中的锁就可以被消除,因为不需要花费额外的时间来进行同步操作。 如:StringBuffer的关键方法都带有synchronized, 但如果只是在单线程中执行这写方法, 那么这些加锁操作是没有必要的,此时编译器就会把加锁操作去掉。 ♩锁粗化 锁粗化是指将多个连续的细粒度锁(synchronized包含的代码块较少)合并为一个粗粒度锁(synchronized包含的代码块较多),以减少锁竞争带来的性能开销。 实际开发过程中,使用细粒度锁,是期望释放锁的时候其他线程能使用锁。但是实际上可能并没有其他线程来抢占这个锁。这种情况 JVM 就会自动把锁粗化,避免频繁申请释放锁。 举个例子: 张三要打电话向老师问三个问题,打一次电话问一个问题,连续打了三次电话。锁优化相当于张三打一次电话就把三个问题一起问了。

 

♫Callable接口

Callable  是一个  interface,与 Runnable 类似,也是用来描述一个任务,不同的是 Runnable 描述的任务没有返回值,而 Callable 描述的任务有返回值。如果需要一个线程单独计算出某结果,Callable是比较合适的。 如:创建线程计算 1 + 2 + 3 + ... + 1000, 不使用 Callable 版本: ①.创建一个类 Result, 包含一个 sum 表示最终结果, lock 表示线程同步使用的锁对象。 ②.main 方法中先创建 Result 实例, 然后创建一个 Runable,在 Runnable 里计算1 + 2 + 3 + ... + 1000,将 Runnable 传入线程 thread。 ③.主线程同时使用 wait 等待线程 thread  计算结束。  ( 注意: 如果执行到 wait 之前, 线程 thread  已经计算完了, 就不必等待了)。 ④.当线程 thread  计算完毕后, 通过 notify 唤醒主线程, 主线程再打印结果。
public class Test {static class Result {public int num = 0;public Object lock = new Object();}public static void main(String[] args) throws InterruptedException {Result result = new Result();Runnable runnable = new Runnable() {@Overridepublic void run() {int num = 0;for (int i = 1; i <= 1000; i++) {num += i;}synchronized (result.lock) {result.num = num;result.lock.notify();}}};Thread thread = new Thread(runnable);thread.start();synchronized (result.lock) {while (result.num == 0) {result.lock.wait();}System.out.println(result.num);}}
}
可以看到, 上述代码需要一个辅助类 Result, 还需要使用一系列的加锁和 wait notify 操作, 代码复杂,容易出错。 创建线程计算 1 + 2 + 3 + ... + 1000, 使用 Callable 版本: ①.创建一个匿名内部类, 实现 Callable 接口, Callable 带有泛型参数, 泛型参数表示返回值的类型。 ②.重写 Callable 的 call 方法, 完成累加的过程, 直接通过返回值返回计算结果。 ③.把 callable 实例使用 FutureTask 包装一下。 ④.创建线程 , 线程的构造方法传入 FutureTask . 此时新线程就会执行 FutureTask 内部的 Callable 的 call 方法, 完成计算, 计算结果就放到了 FutureTask 对象中。 ⑤.在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕, 并获取到 FutureTask 中的结果。
public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}return sum;}};FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread thread = new Thread(futureTask);thread.start();int result = futureTask.get();System.out.println(result);}
}
可以看到, 使用 Callable 和 FutureTask 之后, 代码简化了很多, 也不必手动写线程同步代码了。 注: Callable 通常需要搭配 FutureTask 来使用,FutureTask 用来保存 Callable 的返回结果,因为 Callable 往往是在另一个线程中执行的,啥时候执行完并不确定,FutureTask 就可以负责这个等待结果出来的工作。

更多推荐

synchronized的原理和Callable接口

本文发布于:2023-11-15 09:32:08,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1597475.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:接口   原理   synchronized   Callable

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!