Java 深入浅出分析Synchronized原理与Callable接口

目录
  • 一、基本特点
  • 二、加锁工作过程
    • 偏向锁
    • 轻量级锁
    • 重量级锁
  • 三、其他的优化操作
    • 锁消除
    • 锁粗化
  • 四、Callable 接口

一、基本特点

1. 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁.

2. 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁.

3. 实现轻量级锁的时候大概率用到的自旋锁策略

4. 是一种不公平锁

5. 是一种可重入锁

6. 不是读写锁

二、加锁工作过程

JVM 将 synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁状态。会根据情况,进行依次升级。

偏向锁

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

偏向锁不是真的 "加锁", 只是给对象头中做一个 "偏向锁的标记", 记录这个锁属于哪个线程. 如果后续没有其他线程来竞争该锁, 那么就不用进行其他同步操作了(避免了加锁解锁的开销) 如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别 当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进入一般的轻量级锁状态

偏向锁本质上相当于 "延迟加锁" . 能不加锁就不加锁, 尽量来避免不必要的加锁开销. 但是该做的标记还是得做的, 否则无法区分何时需要真正加锁

偏向锁不是真的加锁, 而只是在锁的对象头中记录一个标记(记录该锁所属的线程). 如果没有其他线 程参与竞争锁, 那么就不会真正执行加锁操作, 从而降低程序开销. 一旦真的涉及到其他的线程竞 争, 再取消偏向锁状态, 进入轻量级锁状态

轻量级锁

随着其他线程进入竞争, 偏向锁状态被消除, 进入轻量级锁状态(自适应的自旋锁). 此处的轻量级锁就是通过 CAS 来实现.

通过 CAS 检查并更新一块内存 (比如 null => 该线程引用)

如果更新成功, 则认为加锁成功

如果更新失败, 则认为锁被占用, 继续自旋式的等待(并不放弃 CPU).

自旋操作是一直让 CPU 空转, 比较浪费 CPU 资源. 因此此处的自旋不会一直持续进行, 而是达到一定的时间/重试次数, 就不再自旋了. 也就是所谓的 "自适应"

重量级锁

如果竞争进一步激烈, 自旋不能快速获取到锁状态, 就会膨胀为重量级锁 此处的重量级锁就是指用到内核提供的 mutex .

执行加锁操作, 先进入内核态.

在内核态判定当前锁是否已经被占用

如果该锁没有占用, 则加锁成功, 并切换回用户态.

如果该锁被占用, 则加锁失败. 此时线程进入锁的等待队列, 挂起. 等待被操作系统唤醒.

经历了一系列的沧海桑田, 这个锁被其他线程释放了, 操作系统也想起了这个挂起的线程, 于是唤醒 这个线程, 尝试重新获取锁

三、其他的优化操作

锁消除

编译器+JVM 判断锁是否可消除. 如果可以, 就直接消除

有些应用程序的代码中, 用到了 synchronized, 但其实没有在多线程环境下. (例如 StringBuffer)

StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
sb.append("d");

此时每个 append 的调用都会涉及加锁和解锁. 但如果只是在单线程中执行这个代码, 那么这些加 锁解锁操作是没有必要的, 白白浪费了一些资源开销.

锁粗化

一段逻辑中如果出现多次加锁解锁, 编译器 + JVM 会自动进行锁的粗化.

领导, 给下属交代工作任务

方式一:

打电话, 交代任务1, 挂电话.

打电话, 交代任务2, 挂电话.

打电话, 交代任务3, 挂电话

方式二:

打电话, 交代任务1, 任务2, 任务3, 挂电话

四、Callable 接口

Callable 是什么

Callable 是一个 interface . 相当于把线程封装了一个 "返回值". 方便程序猿借助多线程的方式计算 结果.

Callable 和 Runnable 相对, 都是描述一个 "任务". Callable 描述的是带有返回值的任务, Runnable 描述的是不带返回值的任务.Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为 Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定. FutureTask 就可以负责这个等待结果出来的工作.

代码示例: 创建线程计算 1 + 2 + 3 + ... + 1000, 不使用 Callable 版本

public class Text {

    static class Result{
        public int sum = 0;
        public Object locker = new Object();
    }

    public static void main(String[] args) throws InterruptedException {
        Result result = new Result();

        Thread t = new Thread(){
            @Override
            public void run() {
                int sum = 0;
                for (int i = 0; i <=10000; i++){
                    sum += i;
                }
                result.sum = sum;

                synchronized (result.locker){
                    result.locker.notify();
                }
            }
        };
        t.start();
        synchronized (result.locker){
            while (result.sum == 0){
                result.locker.wait();
            }
        }
        System.out.println(result.sum);
    }
}

代码示例: 创建线程计算 1 + 2 + 3 + ... + 1000, 使用 Callable 版本

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Text1 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i <=1000; i++){
                    sum += i;
                }
                return sum;
            }
        };
        //由于Thread不能直接传一个callable实例,就需要一个辅助类来包装
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
        //尝试在主线程获取结果
        //如果FutureTask中的结果还没生成。此时就会阻塞等待
        //一直等到最终的线程把这个结果算出来,get返回
        Integer result = futureTask.get();
        System.out.println(result);
    }
}

到此这篇关于Java 深入浅出分析Synchronized原理与Callable接口的文章就介绍到这了,更多相关Java Synchronized 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java synchronized偏向锁的核心原理详解

    目录 1.偏向锁的核心原理 2.偏向锁的撤销 3.偏向锁的膨胀 4.偏向锁的好处 总结 1. 偏向锁的核心原理 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作. Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程 ID 是自己的就表示没有竞争,不用重新 CAS.以后只要不发生竞争,这个对象就归该线程所有. public class Main { static final Objec

  • 深入了解Java Synchronized锁升级过程

    目录 前言 对象结构 对象头 (1)无锁 (2)偏向锁 (3)轻量级锁 (4)重量级锁 对象体 对齐字节 锁升级 补充:Synchronized底层原理 EOF 前言 首先,synchronized 是什么?我们需要明确的给个定义——同步锁,没错,它就是把锁. 可以用来干嘛?锁,当然当然是用于线程间的同步,以及保护临界区内的资源.我们知道,锁是个非常笼统的概念,像生活中有指纹锁.密码锁等等多个种类,那 synchronized 代表的锁具体是把什么锁呢? 答案是—— Java 内置锁.在 Jav

  • Java synchronized轻量级锁的核心原理详解

    目录 1.轻量级锁的原理 2.轻量级锁的分类 1.普通自旋锁 2.自适应自旋锁 3.轻量级锁的膨胀 总结 问题: 什么是自旋锁? 说一下 synchronized 底层实现原理? 多线程中 synchronized 锁升级的原理是什么? 1. 轻量级锁的原理 引入轻量级锁的主要目的是在多线程竞争不激烈的情况下,通过CAS机制竞争锁减少重量级锁产生的性能损耗.重量级锁使用了操作系统底层的互斥锁(Mutex Lock),会导致线程在用户态和核心态之间频繁切换,从而带来较大的性能损耗. 轻量级锁的使用

  • Java Callable接口实现细节详解

    代码如下 import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author lzq * @data 2020/4/30 0030 - 下午 4:02 */ public class Test2 { public static void main(String[] args) throw

  • 详解Java Callable接口实现多线程的方式

    在Java 1.5以前,创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口.无论我们以怎样的形式实现多线程,都需要调用Thread类中的start方法去向操作系统请求io,cup等资源.因为线程run方法没有返回值,如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦. 而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果. Callable和Future介

  • Java synchronized同步方法详解

    目录 1.synchronized同步方法 2.synchronized方法将对象作为锁 3.多个锁对象 4.如果同步方法内的线程抛出异常会发生什么? 5.静态的同步方法 总结 面试题: 1.如何保证多线程下 i++ 结果正确? 2.一个线程如果出现了运行时异常会怎么样? 3.一个线程运行时发生异常会怎样? 为了避免临界区的竞态条件发生,有多种手段可以达到目的. (1) 阻塞式的解决方案:synchronized,Lock (2) 非阻塞式的解决方案:原子变量 synchronized 即俗称的

  • Java对象级别与类级别的同步锁synchronized语法示例

    目录 1.对象级别的同步锁 2.类级别的同步锁 3.总结 Java synchronized 关键字 可以将一个代码块或一个方法标记为同步代码块.同步代码块是指同一时间只能有一个线程执行的代码,并且执行该代码的线程持有同步锁.synchronized关键字可以作用于 一个代码块 一种方法 当一个方法或代码块被声明为synchronized时,如果一个线程正在执行该synchronized 方法或代码块,其他线程会被阻塞,直到持有同步锁的线程释放.根据锁定的范围可以分为 类级别的锁可以防止多个线程

  • Java多线程之synchronized同步代码块详解

    目录 1.同步方法和同步块,哪种更好? 2.synchronized同步代码块 3.如果同步块内的线程抛出异常会发生什么? 总结 面试题: 1同步方法和同步块,哪种更好? 2.如果同步块内的线程抛出异常会发生什么? 1. 同步方法和同步块,哪种更好? 同步块更好,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率.请知道一条原则:同步的范围越小越好. 对于小的临界区,我们直接在方法声明中设置synchronized同步关键字,可以避免竞态条件的问题.但是对于较大的临界区代码段

  • 基于Java Callable接口实现线程代码实例

    实现Callable接口(jdk8新特性) 可以获得线程的返回值 *前两种方式没有返回值,因为run方法返回void 创建一个未来任务类对象 Futrue task = new Future(Callable<>);重写call()方法 可以使用匿名内部类方式 task.get()方法获取线程返回结果 get方法执行会导致当前方法阻塞 效率较低 代码如下 import java.util.concurrent.Callable; import java.util.concurrent.Exec

  • Java 深入浅出分析Synchronized原理与Callable接口

    目录 一.基本特点 二.加锁工作过程 偏向锁 轻量级锁 重量级锁 三.其他的优化操作 锁消除 锁粗化 四.Callable 接口 一.基本特点 1. 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁. 2. 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁. 3. 实现轻量级锁的时候大概率用到的自旋锁策略 4. 是一种不公平锁 5. 是一种可重入锁 6. 不是读写锁 二.加锁工作过程 JVM 将 synchronized 锁分为 无锁.偏向锁.轻量级锁.重量级锁状态.会根据情况

  • Java 深入浅出解析面向对象之抽象类和接口

    目录 抽象类 声明抽象类 声明抽象方法 案例 使用规则 接口 声明接口 案例 接口特性 抽象类和接口的区别 抽象类 java语言,声明类时 abstract class Db{} 说明Db类为抽象类. java语言中,抽象方法是说没有方法的实现(方法体)此方法为抽象方法,只有抽象类和接口中才可以有抽象方法. 声明抽象类 声明抽象类很简单,加个abstract关节字就行. public abstract class AA { } 声明抽象方法 在抽象类中声明一个抽象方法,抽象方法没有方法体,就是说

  • Java synchronized关键字和Lock接口实现原理

    这篇文章主要介绍了Java synchronized关键字和Lock接口实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 引用 当开发过程中,我们遇到并发问题.怎么解决? 一种解决方式,简单粗暴:上锁.将千军万马都给拦下来,只允许一个人过独木桥.书面意思就是将并行的程序变成串行的程序.现实的锁有门锁.挂锁和抽屉锁等等.在Java中,我们的锁就是synchronized关键字和Lock接口. synchronized关键字 synchron

  • Java深入浅出掌握SpringBoot之MVC自动配置原理篇

    Spring Boot 为 Spring MVC 提供了自动配置,适用于大多数应用程序. 官方文档描述: 自动配置在 Spring 的默认值之上添加了以下功能: 从官方描述解析: If you want to keep Spring Boot MVC features and you want to add additionalMVC configuration (interceptors, formatters, view controllers, and other features), y

  • Java多线程实现Callable接口

    调用方法: /** * 点击量/月(年)Callable */ public void yearlyClickCallable() { // 获取参数 String year = getPara("year"); // 统计数据集X List<String> xList = new ArrayList<String>(); xList.add("January"); xList.add("February"); xList

  • Java cglib动态代理原理分析

    本文分下面三个部分来分析cglib动态代理的原理. cglib 动态代理示例 代理类分析 Fastclass 机制分析 一.cglib 动态代理示例 public class Target{ public void f(){ System.out.println("Target f()"); } public void g(){ System.out.println("Target g()"); } } public class Interceptor implem

  • Java并发之synchronized实现原理深入理解

    目录 synchronized的三种应用方式 synchronized作用于实例方法 synchronized作用于静态方法 synchronized同步代码块 synchronized底层语义原理 理解Java对象头与Monitor synchronized代码块底层原理 synchronized方法底层原理 Java虚拟机对synchronized的优化 偏向锁 轻量级锁 自旋锁 锁消除 关于synchronized 可能需要了解的关键点 synchronized的可重入性 线程中断与syn

  • java中TESTful架构原理分析

    目录 1. 什么是REST 2. 理解RESTful 2. 1 资源与URI 2. 2 统一资源接口 GET POST PUT DELETE 2. 3 资源的表述 在URI里边带上版本号 使用URI后缀来区分表述格式 如何处理不支持的表述格式 2. 4 资源的链接 2. 5 状态的转移 2. 5.1 应用状态与资源状态 2. 5.2 应用状态的转移 3. 总结 1. 什么是REST REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征

  • 深入浅出分析C++ string底层原理

    目录 一.深浅拷贝 浅拷贝: 深拷贝 二.string迭代器原理 三.string的传统写法 1.构造实现 2.其他接口 一.深浅拷贝 浅拷贝: 在实现string时要是不实先string拷贝构造,会自动生成一个拷贝构造函数,但是他只是一个浅拷贝.两个string对象指向同一个地址,在两个对象调用析构函数是,前一个对象调用的析构函数已经释放了这个地址的内从,而后一个会重复释放该块空间,导致出错. 会触发断点,然后报错. class string { public: /*string() :_st

随机推荐