【JUC-2】Synchronized关键字相关知识

news/2024/5/20 10:19:53 标签: java, JUC, 多线程

Synchronized

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

java_11">java对象内存结构

在这里插入图片描述

Mark Word结构

Mark Word (64bits)State
unused:25 | hashcode:31 | unused:1 | age:4bits | biassed_lock:0 | 01Normal
thread: 54 | epoch:2 | unused:1 | age:4 | biassed_lock:0 | 01Biassed
pre_to_lock_record:62 | 00Lightweight Locked
prt_to_heavyweight_monitor:62 | 10heavyweight Locked
  • hashcode: 对象的hashcode

  • age: 对象年龄, 决定是否去老年代

  • biassed_lock: 偏向锁标志, 0表示非偏向锁, 1表示偏向锁

  • Biassed: 偏向锁

  • thread: 线程id

Monitor(锁)

每个JAVA对象都可以关联一个Monitor对象, 如果使用synchonized给对象上锁(重量级)之后, 该对象头的Mark Word就被设置指向Monitor对象的指针, Mark Word中原本的信息如hashcode就会被存到Monitor对象中, 在释放锁的时候, monitor会将这些属性还原到Mark Word中

在这里插入图片描述

  • 刚开始监视器中所有者为NULL

  • 当T1执行synchronized(obj)就会将Monitor的所有者Owner置为T1,Monitor中只能有一个Owner

  • 当T1上锁后,T2/T3/T4也来执行临界区代码(synchonized(obj))就会进入Monitor的EntryList中阻塞

  • T1执行完临界区代码, 就会唤醒EntryList中的线程, 来进行锁竞争, 竞争时是非公平的.

  • 图中waitSet中的T9/T0是之前获得过锁的, 但是不满足进入WATING状态的线程, 跟wait-notify有关

    注意:

    没有添加synchonized关键字的对象不会关联Monitor

synchonized-轻量级锁

当synchonized为轻量级锁的时候,其锁创建过程如下

在这里插入图片描述

  • 每个线程创建时都会创建一个lock record对象, 内部存储锁对象的Mark Word

  • 当thread0执行到sychonized代码块时, lock record对象中的object reference将指向锁对象, 同时采用cas操作, 将obj锁对象中的mark word与lock record对象中的lock record 地址 00进行交换

  • cas成功, 其状态如下; 锁状态00. 表示轻量级锁

在这里插入图片描述

  • cas失败, 则有两种情况

    • 如果是其他线程已经持有了该obj锁对象, 表示有竞争, 会进入锁膨胀

    • 如果是thread0锁冲入导致cas失败, 那么在栈帧中再添加一个lock record作为锁重入计数

在这里插入图片描述

  • 当退出synchonized代码块时,进行锁释放. 重入几次, 就要释放几次

    • 释放成功, 轻量级锁成功释放
    • 释放失败, 说明轻量级锁进入锁膨胀, 要进入重量级锁解锁流程

锁膨胀

当轻量级锁存在锁竞争的情况时, thread1线程竞争失败, 进入所膨胀流程.

  • 为obj锁对象申请monitor锁, 让obj对象的mark word指向monitor地址

  • 然后thread1线程进入monitor的entryList中进行阻塞等待

    注意: 轻量级锁没有阻塞等待队列

在这里插入图片描述

  • thread0 释放轻量级锁时, cas失败, 进入重量级锁释放流程. 将monitor中的owner置为null, 唤醒entryList中等待线程进行锁竞争

重量级锁-自旋优化

  • 当monitor存在多线程锁竞争的时候, 竞争线程不会立即进入EntryList队列中阻塞, 他会重试多次获取锁, 如果重试的时候获取到了monitor, 则进入临界代码块. 自旋获取失败, 进入monitor 的entryList中阻塞等待
  • java6之后自旋是自适应的, 比如之前的一次锁竞争中自旋成功了, 那么认为当前自旋获取锁的概率大, 就会多进行几次自旋, 反之就少自旋, 甚至不自旋

偏向锁

偏向锁, 锁对象的mark word中记录的是线程id, 当线程重入加锁时, 省去了判断mark word中的地址是否为锁记录的地址.

一个对象创建时:

  • 如果开启了偏向锁(默认开启) ,那么对象创建后,markword值为0x05即最后3位为101, 这时它的thread、epoch、 age 都为0

  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加VM参数

    -XX:BiasedLockingStartupDelay=e来禁用延迟

  • 如果没有开启偏向锁,那么对象创建后,markword 值为0x01即最后3位为001,这时它的hashcode、age都为0,第一次用到hashcode时才会赋值

偏向锁-撤销偏向

会撤销偏向的场景

  • 当调用锁对象的hashcode方法时, 偏向锁会撤销, 将mark word中的线程id替换为正常对象的信息(可看mark word结构). 即将biassed状态撤销为normal状态
  • 当多个线程都要使用偏向锁时(不是竞争也会升级), 偏向锁升级为轻量级锁.
  • 调用wait/notify; 这两个方法只有重量级锁, 不管当前用的什么锁, 都会升级重量级锁

偏向锁-批量重偏向

如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象的Thread ID

当撤销偏向锁阈值超过20次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程

偏向锁-批量撤销

当撤销偏向锁阈值超过40次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有

对象都会变为不可偏向的,新建的对象也是不可偏向的

Synchronized锁升级过程

伪代码:

java">private Object lock = new Object();
synchronized (lock) {
    // 代码逻辑   
}

java程序第一次运行一个上述synchronized代码块儿时, 假设当前线程为t0, 锁变化如下

  1. 加偏向锁, lock对象的mark word记录t0线程ID, 将锁标志位修改为01表示当前为偏向锁. 如果后续执行该代码块的线程仍然为t0线程, 那么只需要比较lock对象中的mark word存的是不是t0线程的ID即可, 不用像轻量级锁那样获取地址值进行比较.
  2. 当访问代码块的线程为t1线程时, lock对象的mark word中线程ID与当前线程不相等, 升级为轻量级锁, 并且将lock对象的mark word存入t1线程的栈帧中的lock record对象, 将lock record对象的指针存入lock对象的mark word, 完成交换.
  3. 在场景2的基础上, 又来了线程t3要执行同步代码块, 此时t2没有执行完成, t3无法获得锁, 锁升级为重量级锁, lock对象的mark word记录monitor的地址值, 设置t3线程进入monitor的entryList中等待t2线程释放锁.

锁消除

锁消除指的是在某些情况下,JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。锁消除的依据是逃逸分析的数据支持,如 StringBuffer 的 append() 方法,或 Vector 的 add() 方法,在很多情况下是可以进行锁消除的,比如以下这段代码:

java">public String method() {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < 10; i++) {
        sb.append("i:" + i);
    }
    return sb.toString();
}

StringBuffer对象是局部变量, 且不会从该方法中逃逸出去. 所以StringBuffer对象是没有并发环境的, 所以JVM对其进行了锁消除. 编译时, 将StringBuffer转换成了StringBuilder

锁粗化

锁粗化是指,将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。

java">public String method() {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 10; i++) {
        // 伪代码:加锁操作
        sb.append( + i);
        // 伪代码:解锁操作
    }
    return sb.toString();
}

在循环中每次都需要加锁, 浪费性能, 将锁放到循环外, 只用加锁一次

java">public String method() {
    StringBuilder sb = new StringBuilder();
    // 伪代码:加锁操作
    for (int i = 0; i < 10; i++) {
        sb.append( + i);
    }
    // 伪代码:解锁操作
    return sb.toString();
}

wait-notyfy

  • obj.wait() 让进入object监视器的线程到waitSet等待
  • obj.wait(long n) n毫秒之后, 自动唤醒
  • obj .notify()在object. 上正在waitSet等待的线程中挑一 个唤醒
  • obj . notifyAll()让object. 上正在waitSet等待的线程全部唤醒
  • 它们都是线程之间进行协作的手段,都属于Object对象的方法。obj对象必须被线程锁住,才能调用这几个方法
  • wait调用之后, 会释放锁. 其他线程可获取当前锁进入临界代码区

在这里插入图片描述

如果没有synchonized(obj), 直接执行obj.wait()会报错.

java">public class Test7 {
    private static final Object obj = new Object();

    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        System.out.println("执行wait");
                        obj.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("执行释放");
            }
        });
    }
}

sleep与wait方法的区别

  • sleep是Thread类的静态方法; wait是Object类的普通方法
  • sleep不释放锁; wait释放锁
  • wait需要synchonized才能使用

同步模式-保护性暂停

概念: 即Guarded Suspension,用在一个线程等待另一个线程的执行结果

  • 有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject
  • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见生产者消费者)
  • JDK中, join的实现、Future的实现,采用的就是此模式
  • 因为要等待另一方的结果,因此归类到同步模式

保护性暂停案例, 这也是join()方法的实现原理

java">package com.heima.test;

import java.io.Serializable;
import java.util.List;

public class GuardedObject implements Serializable {

    private List<String> response;

    public List<String> getResponse() {
        return response;
    }

    public List<String> get(long n) throws InterruptedException {

        synchronized (this) {
            long start = System.currentTimeMillis();
            long passed = 0L;
            while (null == response) {
                passed = System.currentTimeMillis() - start;
                System.out.println("获取进入等待,等待时间:" + n);
                wait(n - passed);
            }
            return response;
        }
    }

    public void complete(List<String> list) {
        synchronized (this) {
            response = list;
            System.out.println("唤醒其他线程");
            this.notifyAll();
        }
    }
}

java">public class Test7 {

    public static void main(String[] args) {
        GuardedObject guarded = new GuardedObject();
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    List<String> list = guarded.get(2000);
                    if (null != list) {
                        list.forEach(System.out::println);
                    } else {
                        System.out.println("null");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        Thread t2 = new Thread(() -> {
            HttpURLConnection conn = null;
            try {
                conn = (HttpURLConnection) new URL("http://www.baidu.com").openConnection();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            List<String> list = new ArrayList<>();
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    list.add(line);
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            guarded.complete(list);
        }, "t2");

        t.start();
        t2.start();
    }
}

http://www.niftyadmin.cn/n/1176400.html

相关文章

第九章 动画和效果

本项目是《beginning iOS8 programming with swift》中的项目学习笔记》全部笔记目录 ------------------------------------------------------------------------------------------------------------------ 1. 拖拽一个ToolBar到Detail控制器中的Table下面&#xff0c;设…

CentOS7下的YUM源服务器搭建详解,过程写的很详细(转)

因为近期公司需要搭建一个YUM源服务器给大量的linux(mini)使用&#xff0c;所以因此在网上找了很多的教程&#xff0c;却没有一个特别详细的&#xff0c;很多都有遗漏&#xff0c;参差不齐。所以&#xff0c;打算自己做完之后方便以后查阅&#xff0c;特出此文档。 一&#xff…

mft文件记录属性头包括_4-ntfs属性分析.ppt

《4-ntfs属性分析.ppt》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《4-ntfs属性分析.ppt(24页珍藏版)》请在微传网上搜索。1、NTFS属性分析,,MFT文件记录结构,主文件表的文件记录由记录头和属性列表组成&#xff0c;大小为1KB或一个簇大小。属性列表长度可变&#…

vue2.0指不了南

零&#xff0e;混沌 vue是什么 vue是一套构建用户界面的渐进式框架。 他的特点&#xff1a; 简洁&#xff1a;页面由HTML模板Json数据Vue实例组成数据驱动&#xff1a;自动计算属性和追踪依赖的模板表达式组件化&#xff1a;用可复用、解耦的组件来构造页面轻量&#xff1a;代码…

CSS3学习笔记--transform中的Matrix(矩阵)

transform: matrix(a,b,c,d,e,f) ,如下图矩阵所示&#xff0c;任意点&#xff08;x,y,1&#xff09;经过matrix变化为&#xff08;axcye,bxdyf,1)&#xff0c;由此可以知道&#xff0c;matrix参数与translate/scale/skew/rotate函数参数的对应关系为&#xff1a; translate(tx,…

阿里云CentOS7系统搭建JavaWeb环境

一&#xff0c;准备工作 1&#xff0c;安装目录 我们创建如下路径/usr/develop&#xff0c;然后在develop目录下面创建java,tomcat和mysql三个目录即可。 二&#xff0c;配置JDK 1.理解wget命令 wget命令是一个从网络上下载文件的自由工具&#xff0c;它支持http协议&#xff0…

jsp上传到webservice_java 发布webservice服务

关键点&#xff1a;1&#xff1a;类上要注解WebService2&#xff1a;方法上注解WebMethod&#xff0c;方法参数&#xff1a;WebParam来接3&#xff1a;发布配置&#xff1a;Beanpublic ServletRegistrationBean dispatcherServlet() {ServletRegistrationBean sbean new Servl…

TortoiseGit使用入门

2019独角兽企业重金招聘Python工程师标准>>> 首先要确定TortoiseGit已找到msysgit,如果先安装msysgit 再装TortoiseGit, 一般TortoiseGit 就会自动的识别。 设置与查询的方法,这里从开始菜单进入设置。 这是TortoiseGit的设置界面&#xff0c;可以看到用来定位MSysG…