第四章 锁的优化及注意事项

1、锁性能的几点建议

减小锁持有时间:

  • 系统持有锁时间越长锁竞争程度就越激烈,只对需要同步的方法加锁,可以减小锁持有时间进而提高锁性能。
  • 减少锁的持有时间有助于降低锁冲突的可能性,进而提高锁的并发能力。

减小锁粒度:

  • 减小锁粒度就是指缩小锁定对象的范围,从而减小锁冲突的可能性,进而提高并发能力。

读写分离锁代替独占锁(锁分离):

  • 使用读写锁可以减少操作之间相互等待,可以有效的提高性能。ConcurrentLinkedQueue中take和put方法分别使用了两个锁避免了锁竞争,提高了性能。

锁粗化:

  • 如果一个锁不停的被进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能优化。
  • 虚拟机在遇到一连串连续的对同一锁不断进行请求和释放的操作时,便会把所有的操作整合成对锁的一次请求,从而减少锁的请求同步次数,这个操作叫锁粗化

2、Java虚拟机对锁优化所做的努力

锁偏向:

  • 如果一个线程获得了锁,那么锁就进入了偏向模式。当这个线程再次请求锁时,无需再做任何同步操作。
  • 使用Java虚拟机参数-XX:USerBiasedLocking可以开启偏向锁。

轻量级锁:

  • 如果偏向锁失败,虚拟机并不会立即挂起线程,而是使用轻量级锁进行操作。
  • 轻量级锁他只是简单的将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁。
  • 如果线程获得轻量级锁成功,则可以顺利进入临界区。如果轻量级锁加锁失败,则表示其他线程抢先夺到锁,那么当前线程的轻量级锁就会膨胀为重量级锁。

自旋锁:

  • 无法获得锁,不知道什么时候可以获得锁,不会把线程挂起而是让当前线程做几个空循环(这也就是自旋锁的意义)。
  • 若经过几个空循环可以获取到锁则进入临界区,如果还是获取不到则系统会真正的挂起线程。

锁消除

  • 去除不可能存在共享资源竞争的锁,通过锁消除可以节省毫无意义的请求锁时间。如在不可能存在并发的场合使用vector。

3、ThreadLocal

ThreadLocal的使用:

  • ThreadLocal是线程的局部变量,只有当前线程才可以访问,因此是线程安全的。
  • ThreadLocal只是起到容器的作用,为每一个线程分配不同对象需要应用层面保证。
    package com.ecut.threadlocal;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors; public class ThreadLocalTest {
    private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(); public static class ParseDate implements Runnable {
    int i = 0; public ParseDate(int i) {
    this.i = i;
    } @Override
    public void run() {
    try {
    if (threadLocal.get() == null) {
    threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    }
    Date d = threadLocal.get().parse("2019-03-03 19:36:15");
    System.out.println(i + "、" + Thread.currentThread() + ":" + d);
    } catch (ParseException e) {
    e.printStackTrace();
    } // sdf.format(System.currentTimeMillis());
    }
    } public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    ParseDate parseDate = new ParseDate(1);
    for (int i = 0; i < 10; i++) {
    executorService.submit(new ParseDate(i));
    }
    }
    }

ThreadLocal的实现原理:

  • 在set时,首先通过getMap()获得当前线程的ThreadLocalMap,并将值设入到这个ThreadLocalMap中,当前线程做为key,需要的值为value。

        public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
    map.set(this, value);
    else
    createMap(t, value);
    }
  • 在get时,获得ThreadLocalMap的对象,然后通过将自己做为key 取得相应的数据。
      public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
    @SuppressWarnings("unchecked")
    T result = (T)e.value;
    return result;
    }
    }
    return setInitialValue();
    }
  • ThreadLocalMap维护的变量是在Thread内部的,线程不退出对象的引用就会一直存在。使用线程池时线程未必会退出,可能会造成内存泄漏(对象无法回收),因此最好要使用ThreadLocal的remove方法。

4、无锁

无锁的好处:

  • 由于非阻塞性,对死锁天生免疫
  • 没有锁竞争带来的系统开销
  • 没有锁调度带来的系统开销

CAS算法:

  • 包含三个参数CAS(V,E,N),V表示要被修改的变量,E表示期望值,N表示要设置的值。
  • 如果V的值和期望值相等则将V的值设置为N,如果不相等表示V的值已经被其他线程修改了,当前线程不做任何操作。
  • 当有多个线程访问的时候只有一个线程成功,其他线程会被告知失败,其他线程可以选择再次执行或者是放弃。

无锁线程安全的整数:

  • AtomicInteger相比,它是可变的并且线程安全的。

    package com.ecut.atomic;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { static AtomicInteger i = new AtomicInteger(); public static class AtomicIntegerThread implements Runnable {
    @Override
    public void run() {
    for (int k = 0; k < 10000; k++) {
    i.incrementAndGet();
    }
    }
    } public static void main(String[] args) throws InterruptedException {
    AtomicIntegerThread atomicIntegerThread = new AtomicIntegerThread();
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    for (int k = 0 ; k < 10 ; k++){
    executorService.submit(atomicIntegerThread);
    }
    Thread.sleep(500);
    /* Thread[] thread = new Thread[10];
    for (int k = 0 ; k < 10 ; k++){
    thread[k] = new Thread(new AtomicIntegerThread());
    }
    for (int k = 0 ; k < 10 ; k++){
    thread[k].start();
    }
    for (int k = 0 ; k < 10 ; k++){
    thread[k].join();
    }*/
    System.out.println(i);
    }
    }

    increateAndGet的内部使用CAS将新值写入,并且设置失败会不断重试知道成功。

        public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
    var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;
    }

Java中的指针Unsafe类:

  • 指针是不安全的,因此Java中去除了指针,但是通过Unsafe类封装了一些不安全操作。我们无法使用这个类。

无锁对象的引用:

  • AtomicReference是作用是对”对象”进行原子操作,但是会丢失状态信息
  • 如果需要保存状态信息则可以使用AtomicStampedReference,AtomicStampedReference还维护了一个时间戳,当被修改时需要更新数据本身和时间戳。
  • AtomicIntegerArray是线程安全的数组
    package com.ecut.atomic;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicIntegerArray; public class AtomicIntegerArrayTest {
    static AtomicIntegerArray array = new AtomicIntegerArray(10); public static class AtomicIntegerThread implements Runnable {
    @Override
    public void run() {
    for (int k = 0; k < 10000; k++) {
    array.getAndIncrement(k % array.length());
    }
    }
    } public static void main(String[] args) throws InterruptedException {
    AtomicIntegerThread thread = new AtomicIntegerThread();
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    for (int k = 0; k < 10; k++) {
    executorService.submit(thread);
    }
    Thread.sleep(5000);
    System.out.println(array);
    }
    }

    运行结果如下:

    [10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]

原子操作工具类:

  • AtomicIntegerFiedUpdater它可以让你在不改动原有代码的基础上,让普通的变量也享受CAS操作带来的线程安全性。

    package com.ecut.atomic;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class AtomicIntegerFieldUpdaterTest { public static AtomicInteger allScore = new AtomicInteger(); public final static AtomicIntegerFieldUpdater<Candidate> socoreUpdater =
    AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score"); public static class Candidate {
    int id;
    volatile int score;
    } public static class Vote implements Runnable { Candidate candidate; public Vote(Candidate candidate) {
    this.candidate = candidate;
    } @Override
    public void run() {
    if (Math.random() > 0.4) {
    socoreUpdater.incrementAndGet(candidate);
    allScore.incrementAndGet();
    }
    }
    } public static void main(String[] args) throws InterruptedException {
    Candidate candidate = new Candidate();
    Vote vote = new Vote(candidate);
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    for(int i = 0 ; i < 10000 ; i++){
    executorService.execute(vote);
    }
    Thread.sleep(5000);
    System.out.println(allScore+":"+candidate.score);
    }
    }

    运行结果如下:

    6093:6093

    Candidate.score总是和allScore绝对相等,说AtomicIntegerFieldUpdater很好的保证了Candidate.score的线程安全。

  • 注意事项:
    1、Updater只能修改范围可见的变量,因为Update是通过反射来获取到这个变量的‘’
    2、为了确保变量被正确的读取,它必须是volatile类型的。
    3、由于CAS是通过对象实例中的偏移量直接进行赋值的,因此不支持statcic字段,Unsafe.objectFieldOffset() 不支持静态变量。

源码地址:

https://github.com/SaberZheng/concurrent-test

转载请于明显处标明出处:

https://www.cnblogs.com/AmyZheng/p/10471171.html

《实战Java高并发程序设计》读书笔记四的更多相关文章

  1. 【鸟哥的Linux私房菜】笔记1

    Linux是什么 从操作系统与cpu架构关系到linux  Richard Mathew Stallman GPL 关于GNU计划 Linux的发展 Linux的核心版本 Linux的特色 Linux ...

  2. 【鸟哥的Linux私房菜】笔记3

    正确地开机 最好不要使用root账号登陆!GNOME图形界面 View items as a list X WindowShell 文本交互界面bash是Shell的名称,Linux的默认壳程序就是b ...

  3. 【鸟哥的Linux私房菜】笔记2

    Linux的应用 学习资源整理 安装记录 >< 1.Linux的应用: 网络服务器 数据库 学术机构的高效运算任务 嵌入式系统 ... 2.挂载与磁盘分区 学习资源整理 学习 1.书上的网 ...

  4. 《鸟哥的Linux私房菜》笔记——02. 关于Linux

    Unix 历史 1969年以前:伟大的梦想--Bell, MIT 与 GE 的「Multics」系统 1969年:Ken Thompson 的小型 file server system 1973年:U ...

  5. 《鸟哥的Linux私房菜》笔记——03. 磁盘分区

    Everything is a file. 常见硬件对应于 Linux 下的文件(/dev目录下) 装置 装置在Linux内的档名 SCSI/SATA/U盘硬盘机 /dev/sd[a-p] U盘 /d ...

  6. 鸟哥的linux私房菜学习笔记 __ 命令与文件的搜寻

    连续输入两次[tab]按键就能够知道使用者有多少命令可以下达.那你知不知道这些命令的完整档名放在哪里?举例来说,ls 这个常用的命令放在哪里呢? 就透过 which 或 type 来找寻吧! 范例一: ...

  7. 【鸟哥的Linux私房菜】笔记

    操作系统核心的功能! 驱动程序与操作系统的关系 2. [计算机组成之组件] 3.CPU实际要处理的数据完全来自于主存储器,这是一个很重要的概念! 4.CPU是整个计算机系统最重要的部分,那么目前世界上 ...

  8. 《鸟哥的Linux私房菜》笔记——04. 简单命令行

    键入命令 [dmtsai@study ~]$ command [-options] parameter1 parameter2 ... 指令 選項 參數(1) 參數(2) 注意:有时也可以使用 + 放 ...

  9. 鸟哥的Linux私房菜学习笔记——文件权限与目录配置

    Linux的文件权限和目录配置 在linux中的每个用户必需属于一个组,不能独立于组外.在linux中每个文件有所有者.所在组.其它组的概念. (1)所有者 一般为文件的创建者,谁创建了该文件,就是天 ...

  10. 鸟哥的Linux私房菜学习笔记(1)

    2014/10/29 1.档案的权限管理分为三个部分: 拥有者.群组.其他 2.ls -al 命令可以看到档案的详细信息 3.档案的属性中由十个部分构成 第一个部分是档案类型 -代表档案.d代表文件夹 ...

随机推荐

  1. HDU1074 Doing Homework(状压dp)

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=1074 题意:给定有n门课的作业,每门课交作业有截止时间,和完成作业所花费的时间,如果超过规定时间完成,每超 ...

  2. bzoj3626: [LNOI2014]LCA (树链剖分)

    很神奇的方法 感觉是有生之年都想不到正解的这种 考虑对i 到根的节点权值 + 1,则从根到z的路径和就是lca(i,z)的深度 所以依次把0 ~ n - 1的点权值 + 1 对于询问[l, r] 这个 ...

  3. 每天进步一点点------Allegro 布线时显示延迟以及相对延迟信息

    PROPAGATION_DELAYPROPAGATION_DELAY这个设定主要用来对Net绝对长度的设定,如要求设定一组Net的长度要在Min Mil到 Max Mil之间的话,就可以用这种设定来完 ...

  4. 每天进步一点点------Allegro 怎样把铺铜显示关掉,但是走线要显示?

    [背景] 铺铜是PCB布线的末尾环节,在PCB设计后期审查中,我们会检查走线的规则,但是铺铜后,不容易看见走线的效果,这时我们需要关闭铺铜显示,但是走线任然要显示. [解决方法] 执行Setup-&g ...

  5. Ad Hoc类问题

    __________________________________ Ad Hoc类问题的方法:(1)机理分析法.分析题目描述,推出算法. (2)统计分析法.追寻最终的数学模型. Problem 1: ...

  6. 当用命令导入csv文件时提示错误[Err] 1290 - The MySQL server is running with the --secure-file-priv option so it cannot execute this statement

        安装之后没有my.ini配置文件怎么办,因为自己安装的是zip压缩版的mysql,所以再5.7之后就没有my.ini配置文件,所以有时候需要去自己创建一个叫my.ini的配置文件,但是特别 要 ...

  7. 题解 P5530 [BalticOI 2002]双调路径

    P5530 [BalticOI 2002]双调路径 输入样例: 4 5 1 4 2 1 2 1 3 4 3 1 2 3 1 2 3 1 1 4 2 4 2 4 样例如下图 样例说明: 从1到4有4条路 ...

  8. win7安装mysql数据库

    1. 软件准备,以64位系统为例如果是32位的下载32位压缩包即可] https://dev.mysql.com/downloads/mysql/ 2.下载解压到本地,将解压路径的bin目录配置到环境 ...

  9. IntelliJ IDEA 2017.3尚硅谷-----修改当前主题字体、字体大小、行间距、控制台、注释

  10. Qt核心剖析:信息隐藏

    原文 http://devbean.blog.51cto.com/448512/326686 (一) 如果你阅读了 Qt 的源代码,你会看到一堆奇奇怪怪的宏,例如 Q_D,Q_Q.我们的Qt源码之旅就 ...