首页 > 其他分享 >多线程二-同步锁

多线程二-同步锁

时间:2024-07-06 21:54:38浏览次数:13  
标签:00 同步 00000000 object 线程 new 多线程 public

关于线程安全问题的简述

多个线程做同一件事的时候

  • 原子性:Syncronized,AtomicXXX,Lock
  • 可见性:Syncronized,volatile
  • 有序性:Syncronized,volatile

原子性问题

代码演示了两个线程分别调用incr()方法来对i进行累加,预期结果应该是20000,但是实际结果却是小于等于20000的值,这就是线程安全问题中原子性的体现。在这段代码中i++属于Java高级语言中的编程指令,而这些指令最终可能会有多条CPU指令组成。通过javap -v Demo.class查看字节码指令如下:

  public void incr();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: dup
         2: getfield      #7                  // Field i:I  访问变量i
         5: iconst_1                         //将整形常量1放入操作数栈
         6: iadd                               //把操作数栈中的常量1出栈并相加,将相加的结果放入操作数栈
         7: putfield      #7                  // Field i:I  访问类变量复制给demo.i这个变量
        10: return
      LineNumberTable:
        line 6: 0
        line 7: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   Lcom/caozz/demo2/thread/Demo;

这三个操作,如果要满足原子性,那么就需要保证某个线程在执行这个指令时,不允许其他线程干扰。然后实际上,确实存在该问题。简单来说就是将变量i加载后,被切换到其他线程,导致的问题。

代码如下:

package com.caozz.demo2.thread;

public class Demo {
    int i;
    public void incr(){
        i++;
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        Thread[] threads = new Thread[2];
        for (int j = 0; j < 2; j++) {
            threads[j] = new Thread(() -> {            //创建两个线程
                for (int k = 0; k < 10000; k++) {      //每个线程跑10000次
                    demo.incr();
                }
            });
            threads[j].start();
        }
        try {
            threads[0].join();
            threads[1].join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(demo.i);
    }
}

Java中的同步锁syncronized

Markword对象头

对象在堆内存中的存储分布
对象在内存中存储分布

  • 对象标记,也就是markword对象头,四个字节,用于存储一些列的标记位,比如哈希值,锁信息,分代年龄 等
  • 类元信息,即Klass Pointer,jdk8默认开启指针压缩后为4字节,可以使用参数-XX:-UseCompressedOops 关闭指针压缩,关闭后长度为8位,其指向的位置是对象对应的class对象的内存地址
  • 实例数据:包括对象的所有成员变量,大小由各个成员变量决定
  • 对齐填充:并非必须,起到占位符作用。由于hotspot虚拟机的内存管理系统要求对象起始地址必须是8字节的整数倍,当对象实例数据部分没有对齐的话需要对其填充来补全。

markword分布

markword分布

通过ClassLayout打印对象头

  • 添加依赖
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
  • 测试代码
package com.caozz.demo2.thread;

import org.openjdk.jol.info.ClassLayout;

import java.util.concurrent.TimeUnit;

public class ClassLayoutTest {
    Object obj = new Object();

    public void testLock(){
        synchronized (this) {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ClassLayoutTest classLayoutTest = new ClassLayoutTest();
        System.out.println(ClassLayout.parseInstance(classLayoutTest).toPrintable());

        System.out.println("-----------------------------------------------");

        ClassLayoutTest classLayoutTest02 = new ClassLayoutTest();
        new Thread(() -> {
            classLayoutTest02.testLock();
        }).start();

        new Thread(() -> {
            classLayoutTest02.testLock();
        }).start();

        new Thread(() -> {
            classLayoutTest02.testLock();
        }).start();

        System.out.println(ClassLayout.parseInstance(classLayoutTest02).toPrintable());
    }
}

  • 结果
com.caozz.demo2.thread.ClassLayoutTest object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           00 18 c0 00 (00000000 00011000 11000000 00000000) (12589056)
     12     4   java.lang.Object ClassLayoutTest.obj                       (object)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

-----------------------------------------------
com.caozz.demo2.thread.ClassLayoutTest object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           a2 00 01 d1 (10100010 00000000 00000001 11010001) (-788463454)
      4     4                    (object header)                           c4 01 00 00 (11000100 00000001 00000000 00000000) (452)
      8     4                    (object header)                           00 18 c0 00 (00000000 00011000 11000000 00000000) (12589056)
     12     4   java.lang.Object ClassLayoutTest.obj                       (object)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
  • 分析
    锁状态为对象头第一部分的第一个字节后三位,上述结果第一个为001,第二个为010,根据markword分布,可知分别为无锁状态以及重量级锁状态

Synchronized锁升级

jdk1.6对锁的实现引入了大量的优化,如自旋锁,自适应自旋锁,锁消除,锁粗化,偏向锁,轻量级锁等技术来减少锁操作的开销。锁主要存在四种状态:无锁,偏向锁,轻量级锁,重量级锁,他们会随着锁竞争的激烈程度而逐渐升级。这么设计的目的是减少重量级锁带来的性能开销。

默认情况下偏向锁是开启状态,偏向锁是在锁对象的对象头记录当前获取到该锁的线程ID,线程下次再来就可以直接获取锁了。当有第二个线程过来竞争锁,偏向锁就会升级为轻量级锁。轻量级锁底层是通过自旋来实现的,不会阻塞线程。如果自旋次数过多,则会升级为重量级锁,重量级锁会阻塞线程。
自旋锁是线程通过CAS获取预期的一个目标,如果没有获取到则循环获取,获取到了则表示获取到了锁。这个过程线程一直在运行相对而言没有使用太多的操作系统资源,比较轻量。
锁升级

偏向锁的开启有个4秒的延迟,这么设计的原因是因为jvm自己有一些默认启动的线程。如果这时候就使用偏向锁,会在成偏向锁不断的升级和撤销,效率极低。当然,延迟也是可以通过参数设置-XX:BiasedLockingStartupDelay=0

CAS机制

CAS,Compare And Swap,或compare and exchangecompare and set
,比较交换的意思。它可以保证在多线程环境下对于一个变量修改的原子性。
原理如下图:
通过查看源码,可以知道它是一个native方法,然后去查看jvm源码unsafe.cpp:

cmpxchg:compare and exchange

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END

cmpxchg的原子性 底层也是通过锁来保证的:atomic_linux_x86.inline.hpp

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP();
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}

Atomic实现原子性

由源码可以知道,他也是一个不断自旋来实现的

    public final int getAndSet(int newValue) {
        //private static final Unsafe U = Unsafe.getUnsafe();
        return U.getAndSetInt(this, VALUE, newValue);
    }
    @IntrinsicCandidate
    public final int getAndSetInt(Object o, long offset, int newValue) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, newValue));
        return v;
    }
欢迎大家留言,以便于后面的人更快解决问题!另外亦欢迎大家可以关注我的微信公众号,方便利用零碎时间互相交流。共勉!

标签:00,同步,00000000,object,线程,new,多线程,public
From: https://www.cnblogs.com/caozz/p/18287971/thread002

相关文章

  • 单/多线程--协程--异步爬虫
    免责声明:本文仅做技术交流与学习... 目录了解进程和线程单个线程(主线程)在执行多线程线程池协程(爬虫多用)假异步:(同步)真异步:爬虫代码模版异步-爬虫同步效果--19+秒异步效果--7+秒了解进程和线程​#-------------------->#------>#   ----......
  • 永磁同步电机参数辨识算法--模型参考自适应辨识电感
    本文采用MRAS在线辨识电感参数(Ld、Lq)一、原理介绍从组成部分来看,MRAS由三个重要部分构成分别为参考、可调以及自适应律。参考模型相当于IPMSM参数实时变化的准确值,即作为可调模型的参考值,可调模型依据参数实时变化进行修改待辨识参数。当参考、可调模型等输入时,由于两者内......
  • 坚果云与floccus实现Chrome书签国内跨设备、跨平台同步
      本文介绍基于floccus插件与坚果云协同使用的方法,对浏览器的书签进行实时在线同步的操作。  在工作与学习中,我们时常希望在不同浏览器之间实现书签的同步;而一些传统的浏览器书签同步方案,或多或少都面临着一些问题——比如,Chrome浏览器尽管可以实现比较好的跨设备同步,但由于......
  • 实现返利App中的离线数据同步与存储解决方案
    实现返利App中的离线数据同步与存储解决方案大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿!在开发返利类应用程序时,离线数据同步和有效的本地存储是至关重要的。本文将探讨如何设计和实现一个高效的离线数据......
  • CDC实时同步进行时遇到不可抗力中断了怎么办?
    目录一、CDC技术的概念二、CDC技术的应用场景1.数据复制和同步2.实时数据仓库3.业务过程监控和审计4.ETL进程优化三、CDC与数据管道的关系1.区别CDC(ChangeDataCapture)数据管道(DataPipeline)2.联系CDC是数据管道的一部分数据管道支持CDC的实现四、CDC实时同步......
  • Java计算机毕业设计信阳市多目的地同步导航系统(开题+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信阳市城市化进程的加快,交通网络日益复杂,市民出行需求日益多样化,对导航系统的要求也愈发提高。传统单一目的地的导航系统已难以满足市民在日常出......
  • 多线程一
    线程启动线程生命周期阻塞状态分为Blocked,time-waiting.外在表现区别不大,产生的原因不同,可以通过jstack查看,更具体的状态有助于我们排查线程相关问题。下面这个时更为详细的生命周期图线程停止stop方法:不建议使用,类似于kill-9,不够优雅interrupt():通过Thread.current......
  • 并发、多线程和HTTP连接之间有什么关系?
    一、并发的概念 并发是系统同时处理多个任务或事件的能力。在计算中,这意味着系统能够在同一时间段内处理多个任务,而不是严格按照顺序一个接一个地执行它们。并发提高了系统的效率和资源利用率,从而更好地满足用户的需求。在现代应用程序中,用户可能会同时执行多个操作,例如同时......
  • Rust简明教程第九章-多线程和并发
    并发并发指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果进程是一个程序的实例线程是一个进程中执行的一个单一线性执行流程,一个进程包含多个线程,线程可以并发执行main是主线程,系统的入口区别:并发指一个系统能够......
  • 音视频同步的关键:深入解析PTS和DTS
    ......