您现在的位置是:首页 > 正文

java的轻量锁,jvm第7节-锁(偏向锁,轻量锁,自旋锁)

2024-04-01 03:35:45阅读 0

在介绍锁之前我们先介绍一个线程不安全的例子,一个全局的list,开2个线程往里面插入数据,代码如下:

package com.jvm.day6.lock.demo;

import java.util.ArrayList;

import java.util.List;

/**

* 测试都线程共享一个变量带来的现象

* @Author:xuehan

* @Date:2016年3月20日下午3:35:29

*/

class NumberAdd implements Runnable{

public static List numberList =new ArrayList();

public static int startNum;

public NumberAdd(int startNum){

this.startNum = startNum;

}

@Override

public void run() {

int count = 0;

while(count < 1000000){

numberList.add(count ++ );

startNum = startNum + 2;

}

}

}

public class Test{

public static void main(String[] args) throws Exception {

Thread t1 = new Thread(new NumberAdd(1));

Thread t2 = new Thread(new NumberAdd(0));

t1.start();

t2.start();

while(t1.isAlive() || t2.isAlive()){Thread.sleep(2);}

System.out.println("集合大小" + NumberAdd.numberList.size() );

System.out.println( "最后一个值的大" + NumberAdd.numberList.get(NumberAdd.numberList.size() - 1));

for(int i = NumberAdd.numberList.size() - 10 ; i < NumberAdd.numberList.size() -1; i ++){

System.out.println(NumberAdd.numberList.get(i));

}

}

}

de84ba01f3cb037ed7734af3ab9f828c.png

按照开始想的,集合里面应该有200万个数据了,结果却出现了数组越界的错误,为什么呢,这是因为ArrayList不是线程安全的,用来存放数据的elementData是共享的, 线程A往list里添加数据的时候刚验证大小通过,还没有插入,然后轮到线程B执行,线程B刚好插入了list该扩容的最后一个元素,然后list满了,线程A执行,A线程往集合里面插入元素,引起了数据越界。

32a41a9df5f3423cdc5381ce8f9331f6.png

075688b033f960e658973814d0295876.png

jvm锁

每个对象都一个mark头,他的作用是:

Mark Word,对象头的标记,32位

描述对象的hash、锁信息,垃圾回收标记,年龄

指向锁记录的指针

指向monitor的指针

GC标记

偏向锁线程ID

偏向锁

jvm控制,可以设置jvm启动参数

大部分情况是没有竞争的,所以可以通过偏向来提高性能

所谓的偏向,就是偏心,即锁会偏向于当前已经占有锁的线程

将对象头Mark的标记设置为偏向,并将线程ID写入对象头Mark

只要没有竞争,获得偏向锁的线程,在将来进入同步块,不需要做同步

当其他线程请求相同的锁时,偏向模式结束

-XX:+UseBiasedLocking -jdk6需要手动打开

默认启用

在竞争激烈的场合,偏向锁会增加系统负担

偏向锁是系统自带的设置参数开启,java代码如下:

package com.jvm.day6.lock;

import java.util.List;

import java.util.Vector;

/**

*使用 偏向锁-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0

* 不使用偏向锁-XX:-UseBiasedLocking

* 根据测试下面代码使用偏向锁可提高150毫秒执行时间 150/2300,提高效率6%

* @Author:xuehan

* @Date:2016年3月19日下午12:06:15

*/

public class DeflectionLock {

public static List numberList =new Vector();

public static void main(String[] args) throws InterruptedException {

System.out.println((int)'l');

long begin=System.currentTimeMillis();

int count=0;

int startnum=0;

while(count<10000000){

numberList.add(startnum);

startnum+=2;

count++;

}

long end=System.currentTimeMillis();

System.out.println(end-begin);

}

}  根据我的写实偏向锁可以提高性能6%

轻量级锁

BasicObjectLock,这个锁是嵌入到线程栈中的,他有两部组成

BasicLock和

ptr to obj hold the lock,

BasicLock里面存放着对象头,

ptr to obj hold the lock为指向持有对象锁的指针

普通的锁处理性能不够理想,轻量级锁是一种快速的锁定方法。

如果对象没有被锁定

将对象头的Mark指针保存到锁对象中

将对象头设置为指向锁的指针(在线程栈空间中)

如果轻量级锁失败,表示存在竞争,升级为重量级锁(常规锁)

在没有锁竞争的前提下,减少传统锁使用OS互斥量产生的性能损耗

在竞争激烈时,轻量级锁会多做很多额外操作,导致性能下降

自旋锁

当竞争存在时,如果线程可以很快获得锁,那么可以不在OS层挂起线程,让线程做几个空操作(自旋)

JDK1.6中-XX:+UseSpinning开启

JDK1.7中,去掉此参数,改为内置实现

如果同步块很长,自旋失败,会降低系统性能

如果同步块很短,自旋成功,节省线程挂起切换时间,提升系统性能

上面的一些锁不是Java语言层面的锁优化方法

他们是内置于JVM中的获取锁的优化方法和获取锁的步骤

偏向锁可用会先尝试偏向锁

轻量级锁可用会先尝试轻量级锁

以上都失败,尝试自旋锁

再失败,尝试普通锁,使用OS互斥量在操作系统层挂起

我们可以从以下方面对锁进行优化

减少锁的时间

没必须放在同步块的代码尽量不要放在代码块里

减少锁的粒度

将大对象,拆成小对象,大大增加并行度,降低锁竞争

偏向锁,轻量级锁成功率提高

实现的例子如ConcurrentHashMap

使用若干个Segment :Segment[] segments

Segment中维护HashEntry

put操作时

先定位到Segment,锁定一个Segment,执行put

在减小锁粒度后, ConcurrentHashMap允许若干个线程同时进入

锁分离

根据功能进行锁分离

ReadWriteLock

读多写少的情况,可以提高性能

be6ec6ec94d584fe86a27f3c43b9b0c4.png读写分离思想可以延伸,只要操作互不影响,锁就可以分离

LinkedBlockingQueue

队列

链表

c6968890af7bc352628ab1b7f17d1891.png锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能尽早的获得资源执行任务。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化

for(int i=0;i<1000;i++){

synchronized(lock){}

}

// 应该写成

synchronized(lock){

for(int i =0; i < 1000; i ++){}

}

这时候我们要增加锁的持有时间不要让请求和释放锁频繁的发生

锁消除

在java方法体里如果不是共享的变量不需要同步操作的,这时候jvm会自动的优化把锁去掉,如StingBuffer和Vector,使用锁消除

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks

关闭锁消除

-server -XX:+DoEscapeAnalysis -XX:-EliminateLocks

无锁

锁是悲观的操作

无锁是乐观的操作

无锁的一种实现方式

CAS(Compare And Swap),CAS是原子的

非阻塞的同步

CAS(V,E,N)

在应用层面判断多线程的干扰,如果有干扰,则通知线程重试,一般这样做会让程序变的复杂,但性能更加好。

网站文章

  • 前端之JS

    参考博客:http://www.cnblogs.com/liwenzhou/p/8004649.html JS基础语法: 0. JS引入方式   1. 直接在script标签里面写代码  2. 在单独的JS文件中写代码,然后通过script标签的src属性引入写好的JS文件1. 变量  1. var 变量名; var 变量名 = 值  2. 变量的命名 字母 数字 下划...

    2024-04-01 03:35:38
  • Ajax中的错误处理 - 优化你的编程体验

    Ajax中的错误处理 - 优化你的编程体验

    此外,我们还了解了一些高级技巧,如显示错误信息、错误分类处理和重试机制,以进一步优化错误处理的效果。在实际的开发中,根据具体的需求和场景,我们可以根据本文提供的示例代码进行适当的修改和定制,以满足实际...

    2024-04-01 03:34:58
  • 【若泽大数据实战第十八天】Hive - 创建测试表dual的使用

    -- 创建 dual 表(只有在测试的时候用insert)hive&gt; create table dual(x string);OKTime taken: 0.282 secondshive&gt; insert into table dual values('');Query ID = hadoop_20180611233030_645e070e-77f9-4ea4-8b32-ee3...

    2024-04-01 03:34:50
  • leetcode刷题:查找重复的电子邮箱

    编写一个 SQL 查询,查找 Person 表中所有重复的电子邮箱。示例:+—-+———+ | Id | Email | +—-+———+ | 1 | a@b.com | | 2 | c@d.com | | 3 | a@b.com | +—-+———+ 根据以上输入,你的查询应返回以下结果:+———+ | Email | +———+ | a@b.com...

    2024-04-01 03:34:42
  • 494. Target Sum题解(DP法)

    494. Target Sum题解(DP法)

    题目链接:https://leetcode.com/problems/target-sum/ 题解: 首先转化问题:令取正的数为A,取负的数为B,则要求sum(A)-sum(B)=S,两边加上sum(A)+sum(B),得到sum(A)-sum(B)+sum(A)+sum(B)=S+sum(A)+sum(B),即2*sum(A)=S+sum(nums),原问题转化为子数组和问题,使子数组和...

    2024-04-01 03:34:34
  • 最短路算法(3种算法)

    1.最短路 最短路,顾名思义,最短的路径。我们把边带有权值的图称为带权图。边的权值可以理解为两点之间的距离。一张图中任意两点之间会有不同的路径相连。最短路径就是指连接两点的这些路径中最短的一条。我们有...

    2024-04-01 03:33:53
  • 定时器工作方式

    定时器工作方式

    1、工作方式控制寄存器(TCON) TMOD(89H) 地址90H8FH 8EH8DH8CH 8BH8AH89H 符号GATEC/T M1M0GATE C/TM1M0 1)GATE:门控制位。  GATE=1,外部启动方式(也曾硬件启动方式),定时/计数器的启动受外部输入引脚INT0‾\overline{INT0}INT0或I

    2024-04-01 03:33:45
  • golang写入csv

    package mainimport ( "encoding/csv" "fmt" "os")func main() { file, err := os.OpenFile("111.csv", os.O_CREATE|os.O_RDWR, 0644) if err != nil { fmt.Println("open fi...

    2024-04-01 03:33:39
  • 树莓派4B ubuntu20.04 python控制pwm 蜂鸣器播放歌曲 教程

    树莓派4B ubuntu20.04 python控制pwm 蜂鸣器播放歌曲 教程

    本文参考自:https://shumeipai.nxez.com/2020/11/18/raspberry-pi-controls-the-buzzer-to-play-music.html驱动蜂鸣器...

    2024-04-01 03:32:59
  • Fiddler “抓包“最新详细教程,黑客入门到精通,从这篇文章开始

    Fiddler “抓包“最新详细教程,黑客入门到精通,从这篇文章开始

    Fiddler工具介绍是一个通过的方式来进行抓包工具,运行时会在本地建立一个代理服务,默认地址:127.0.0.1:8888。Fiddler开启之后,配置代理,再打开IE浏览器,IE的PROXY会自动...

    2024-04-01 03:32:53