Java并发之synchronized

并发编程的三个问题

可见性

可见性 是指一个线程对共享变量进行修改,其他线程立即得到修改后的最新值。

可见性演示:
    1、创建一个共享变量
    2、创建一条线程不断读取共享变量
    3、创建另一条线程修改共享变量

public class DemoVisibility {
    // 1、创建一个共享变量
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        // 2、创建一条线程不断读取共享变量
        new Thread(() -> {
            while(flag){

            }
        }).start();

        Thread.sleep(2000);

        // 3、创建另一条线程修改共享变量
        new Thread(() -> {
            flag = false;
            System.out.println("线程修改了变量,变量变为 false。");
        }).start();
    }
    /**运行结果:
     * 线程修改了变量,变量变为 false。
     * 
     * 线程1持续运行,程序并没有结束
     */
}

小结
    并发编程时,会出现可见性问题,当一个线程对共享变量进行修改后,另外的线程并没有立即看到修改后的最新值。

原子性

    原子性 是指再一次或多次操作中,要么所有的操作都执行并且不会受到其他因素干扰而中断,要么所有的操作都不执行。

原子性演示:
    1、定义一个共享变量 number
    2、对 number 进行 1000 次 ++ 操作
    3、使用 5 个线程来进行

public class DemoAtomicity {
    // 1、定义一个共享变量 number
    private static int number = 0;

    public static void main(String[] args) throws InterruptedException {
        // 2、对 number 进行 1000 次 ++ 操作
        Runnable increment = () -> {
            for (int i = 0; i < 1000; i++) {
                number++;
            }
        };
        List<Thread> list = new ArrayList<>();
        // 3、使用 5 个线程来进行
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(increment);
            t.start();
            list.add(t);
        }
        for (Thread t: list) {
            t.join();
        }
        System.out.println("number = " + number);
    }
    /**
     * output1:number = 5000
     * output2:number = 2729
     */
}

小结
    并发编程时,会出现原子性问题,当一个线程对共享变量操作进行到一半时,另外的线程也有可能来操作共享变量,从而干扰了前一个线程的操作。

有序性

    有序性 是指程序中代码的执行顺序,Java在编译时和运行时会对代码进行优化,会导致程序最终的执行顺序不一定就是我们编写代码的顺序。

Java 内存模型 (JMM)

计算机结构

在这里插入图片描述

CPU

    中央处理器,是计算机的控制和运算的核心,我们的程序最终都会编程指令让CPU去执行,处理程序中的数据。

内存

    程序都是在内存中运行的,内存会保存程序运行时的数据,供CPU处理。

缓存

    CPU运算速度和内存的访问速度差异比较大,这导致了CPU每次操作内存都需要耗费很多时间等待,内存的读写速度成为计算机运行的瓶颈。于是就有了在CPU和主内存之间增加缓存的设计。最靠近CPU的缓存成为L1,然后依次是L2、L3和主内存。

CPU Cache 分为三个级别: L1, L2, L3。级别越小越接近 CPU,速度越快,容量越小。
    1、L1 是最接近 CPU 的,容量最小(如:32k),速度最快,每个核上都有一个 一级缓存。
    2、L2 更大一些(如:256k),速度要慢一些,一般情况下,每个核上都有一个独立的 二级缓存。
    3、L3 是三级缓存中容量最大的一级(如:12Mb),同时也是速度最慢的一级,在同一个 CPU 插槽之间的核共享一个 三级缓存。

Java内存模型

    Java内存模型,是JVM规范中所定义的一种内存模型,JMM是标准化的,屏蔽掉了底层不同计算机的区别。
    Java内存模型是一套规范,描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量的底层细节,具体如下:

  • 主内存:所有线程共享,所有共享变量都存储于主内存中
  • 工作内存:每一个线程都有自己的工作内存,工作内存只存储该线程对共享变量的副本,线程对变量的所有操作都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量

JMM 作用

    JMM是一套在多线程读写共享数据时,对共享数据可见性、原子性和有序性的规则和保障。

  • synchronized
  • volatile

主内存与工作内存之间的交互

8个原子操作保证主内存与工作内存的交互不出错:

  • lock、read、load、use、assign、store、write、unlock

· 对一个变量执行lock操作,将会清空工作内存中此变量的值
· 对一个变量执行unlock操作,必须先把此变量同步到主内存中

Synchronized 如何保证并发编程三大特性

    synchronized能够保证在同一时刻只有一个线程执行该段代码,以达到保证并发安全的额效果。

synchronized (锁对象) {
    // 受保护的资源
}

synchronized 保证原子性

synchronized保证同一时间只有一个线程拿到锁,能够进入同步代码块。

public class DemoAtomicity {
    // 1、定义一个共享变量 number
    private static int number = 0;
    private static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 2、对 number 进行 1000 次 ++ 操作
        Runnable increment = () -> {
            for (int i = 0; i < 1000; i++) {
                synchronized (obj){
                    number++;
                }
            }
        };

        List<Thread> list = new ArrayList<>();
        // 3、使用 5 个线程来进行
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(increment);
            t.start();
            list.add(t);
        }
        for (Thread t: list) {
            t.join();
        }
        System.out.println("number = " + number);
    }
    /**
     * output:number = 5000
     */
}

Synchronized 保证可见性

synchronized对应lock和unlock这两个原子操作,会刷新工作内存中共享变量的值。

· 对一个变量执行lock操作,将会清空工作内存中此变量的值
· 对一个变量执行unlock操作,必须先把此变量同步到主内存中

public class DemoVisibility {
    // 1、创建一个共享变量
    private static boolean flag = true;
    private static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 2、创建一条线程不断读取共享变量
        new Thread(() -> {
            while(flag){
                synchronized (obj) {

                }
            }
        }).start();

        Thread.sleep(2000);

        // 3、创建另一条线程修改共享变量
        new Thread(() -> {
            flag = false;
            System.out.println("线程修改了变量,变量变为 false。");
        }).start();
    }
    /**运行结果:
     * 线程修改了变量,变量变为 false。
     *
     * 程序结束
     */
}

synchronized 保证有序性

为什么要重排序

    为了提高程序的执行效率,编译器和CPU会对程序中的代码进行重排序。

as-if-serial语义

    不管编译器和CPU如何重排序,必须保证在单线程情况下程序的结果是正确的。

  • 当操作之间不存在依赖关系时,可以进行重排序

synchronized 保证有序性的原理

    对不存在依赖关系的操作集合加 synchronized 代码块,虽然重排序还是会发生,但 synchronized 保证同一时间只有一个线程拿到锁,能够进入同步代码块,因此重排序不会影响别的线程。

synchronized 特性

可重入

    synchronized 是可重入锁,内部锁对象中有一个计数器(recursions变量)会记录线程获得几次锁,在执行完同步代码块时,计数器数量 -1,直到数量为0,就释放这个锁。

  • 可重入可以避免死锁
  • 可重入可以让我们更好的来封装代码
public class DemoReIn {
    public static void main(String[] args){
        // 3、使用两个线程来执行
        new MyThreadDemo().start();
        new MyThreadDemo().start();
    }

}
// 1、定义一个线程类
class MyThreadDemo extends Thread {
    // 2、在线程类中的 run 方法中使用嵌套的同步代码块
    @Override
    public void run() {
        synchronized (MyThread.class) {
            System.out.println(getName() + " 进入了同步代码块 1");
            synchronized (MyThread.class) {
                System.out.println(getName() + " 进入了同步代码块 2");
            }
        }
    }
    /**
     * Thread-0 进入了同步代码块 1
     * Thread-0 进入了同步代码块 2
     * Thread-1 进入了同步代码块 1
     * Thread-1 进入了同步代码块 2
     */
}

不可中断

    一个线程获得锁后,另一个线程想要获得锁,必须处于阻塞或等待状态,如果第一个线程不释放锁,第二个线程会一直阻塞或等待,不可被中断
    · synchronized 不可被中断
    · Lock 的 lock() 方法 不可被中断
    · Lock 的 tryLock() 方法 可被中断

public class DemoUnIntr {
    private static Object obj = new Object();
    public static void main(String[] args) throws InterruptedException {
        // 1、定义一个 Runnable
        Runnable run = () -> {
            // 2、在 Runnable 定义同步代码块
            synchronized (obj) {
                String name = Thread.currentThread().getName();
                System.out.println(name + " 进入 同步代码块");
                // 保证不退出同步代码块
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        // 3、先开启一个线程来执行同步代码块
        Thread t1 = new Thread(run);
        t1.start();
        Thread.sleep(1000);
        // 4、后开启一个线程来执行同步代码块(阻塞状态)
        Thread t2 = new Thread(run);
        t2.start();
        //
        Thread.sleep(500);
        System.out.println("t1 状态: " + t1.getState());
        System.out.println("t2 状态: " + t2.getState());
        // 5、停止第二个线程
        System.out.println("执行 t2.interrupt(); 停止线程2前");
        t2.interrupt();
        System.out.println("执行 t2.interrupt(); 停止线程2后");
        Thread.sleep(500);
        System.out.println("t1 状态: " + t1.getState());
        System.out.println("t2 状态: " + t2.getState());
    }
    /**
     * Thread-0 进入 同步代码块
     * t1 状态: TIMED_WAITING
     * t2 状态: BLOCKED
     * 执行 t2.interrupt(); 停止线程2前
     * 执行 t2.interrupt(); 停止线程2后
     * t1 状态: TIMED_WAITING
     * t2 状态: BLOCKED
     * Thread-1 进入 同步代码块
     */
}

Lock 不可中断 及 可中断

不可中断 lock.lock();

public class DemoUnIntr {
    private static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        test01();
    }
    public static void test01() throws InterruptedException {
        Runnable run = () -> {
            String name = Thread.currentThread().getName();
            try {
                lock.lock();
                System.out.println(name + " 获得锁,进入锁执行");
                Thread.sleep(8000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println(name + "释放锁");
            }
        };
        Thread t1 = new Thread(run);
        t1.start();
        Thread.sleep(1000);

        Thread t2 = new Thread(run);
        t2.start();

        Thread.sleep(500);
        System.out.println("t1 状态: " + t1.getState());
        System.out.println("t2 状态: " + t2.getState());
        // 5、停止第二个线程
        System.out.println("执行 t2.interrupt(); 停止线程2前");
        t2.interrupt();
        System.out.println("执行 t2.interrupt(); 停止线程2后");
        Thread.sleep(500);
        System.out.println("t1 状态: " + t1.getState());
        System.out.println("t2 状态: " + t2.getState());
    }
    /**
     * Thread-0 获得锁,进入锁执行
     * t1 状态: TIMED_WAITING
     * t2 状态: WAITING
     * 执行 t2.interrupt(); 停止线程2前
     * 执行 t2.interrupt(); 停止线程2后
     * t1 状态: TIMED_WAITING
     * t2 状态: WAITING
     */
}

可中断 lock.tryLock();

public class DemoUnIntr {
    private static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        test01();
    }
    public static void test01() throws InterruptedException {
        Runnable run = () -> {
            String name = Thread.currentThread().getName();
            boolean sign = false;
            try {
                sign = lock.tryLock(3, TimeUnit.SECONDS);
                if(sign){
                    System.out.println(name + " 获得锁,进入锁执行");
                    Thread.sleep(8000);
                }else{
                    System.out.println(name + " 在指定时间内没有得到锁,去做其他操作");
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if(sign){
                    lock.unlock();
                    System.out.println(name + "释放锁");
                }

            }
        };
        Thread t1 = new Thread(run);
        t1.start();
        Thread.sleep(1000);

        Thread t2 = new Thread(run);
        t2.start();

    }
    /**
     * Thread-0 获得锁,进入锁执行
     * Thread-1 在指定时间内没有得到锁,去做其他操作
     */
}

Synchronized 原理

反汇编

反汇编指令

javap -p -v **.class 

实例

public class DemoSyn {
    private static Object obj = new Object();

    public static void main(String[] args) {
        synchronized (obj) {
            System.out.println("1");
        }
    }

    public synchronized void test() {
        System.out.println("2");
    }
}

在这里插入图片描述

  • monitorenter
        每个锁对象都与一个 monitor 监视器相关联。监视器只有在有 owner 所有者时才被锁定。执行 monitorenter 的线程尝试获取与 monitor 监控器的所有权,如下所示:
        * 如果 monitor 监视器的条目计数 recursions 为 0,则线程进入 monitor 监视器并将其条目计数设置为 1。线程就是监视器的 owner 所有者。
        * 如果线程已经拥有 monitor 监视器的所有权,允许它重入 monitor 监视器,其条目计数 recursions + 1。
        * 如果另一个线程已经占有 monitor 监视器的所有权,则当前尝试获取 monitor 监视器所有权的线程将被阻塞,直到 monitor 监视器的条目计数 recursions 为 0,然后才能重新尝试获取 monitor 所有权。
  • monitorexit
        * 能执行 monitorexit 指令的线程一定拥有当前对象的 monitor 所有权。
        * 执行 monitorexit 指令时会将 monitor 的进入数 recursions - 1,当 recursions 为 0 时,当前线程退出 monitor,不再拥有 monitor 所有权,此时其他被这个 monitor 阻塞的线程可以尝试获取个 monitor 监视器的所有权。

monitorexit 插入在方法结束处和异常处,JVM保证每个 monitorenter 必须有对应的 monitorexit。
所以,出现异常会释放锁

小结

    同步方法反汇编后,可以看出会增加 ACC_SYNCHRONIZED 修饰。会在执行前后分别隐式调用 monitorenter 和 monitorexit。
    每个锁对象都与一个 monitor 监视器相关联,监视器才是真正的锁对象,它内部有两个重要的成员变量:
    * owner: 保存获得锁的线程
    * recursions: 保存线程获得锁的次数

synchronized 和 Lock 的区别

1、synchronized 是关键字,Lock 是接口
2、synchronized 会自动释放锁,Lock 必须手动释放
3、synchronized 是不可中断的,Lock 可以是可中断的,也可以是不可中断的
4、通过 Lock 可以知道线程是否有拿到锁,synchronized 不行
5、synchronized 能锁住方法和代码块,Lock 只能锁住代码块
6、Lock 可以使用读写锁提高多线程读效率,读多个线程可以同时读,写只能一个线程写
7、synchronized 是非公平锁,ReentrantLock 可以控制是否是公平锁,无参-非公平

深入 JVM 源码

在这里插入图片描述

  • _owner:线程拥有者
  • _recursions:线程进入锁次数
  • _cxq:第一次竞争锁失败的线程存放位置
  • _EntryList:第二次及之后竞争锁失败的线程存放位置
  • _WaitSet:处于 wait 状态的线程存放位置

monitor 竞争

1、通过CAS尝试把monitor的owner字段设置为当前线程
2、如果设置之前的owner指向当前线程,说明当前线程再次进入monitor,即重入锁,执行recursions++,记录重入次数
3、如果当前线程是第一次进入该monitor,设置recursions为1,_owner为当前线程,该线程成功获得锁并返回
4、如果竞争失败,则等待锁的释放

monitor 等待

1、当前线程被封装成ObjectWaiter对象node,状态设置为ObjectWaiter::TS_CXQ
2、通过CAS+循环操作把node节点push到_cxq列表中,同一时刻可能有多个线程push自己的节点
3、node节点push到_cxq列表后,通过自旋尝试获取锁,如果仍获取失败,则通过park将当前线程挂起,等待被唤醒
4、当前线程被唤醒时,会从挂起的点继续执行,通过ObjectMonitor::TryLock尝试获取锁

monitor 释放

1、推出同步代码块时会让_recursions–,当_recursions值为0时,说明线程释放了锁
2、根据QMode指定的不同策略,从_cxq或_EntryList中获取头节点,通过ObjectMonitor::ExitEpilog方法唤醒该节点封装的线程,唤醒操作由unpark完成

monitor 是重量级锁

    ObjectMonitor的函数设计内核函数,如Atomic::inc_ptr、park()、unpark()等,执行这些内核函数就会存在操作系统用户态和内核态的转换,这种切换会消耗大量的系统资源。所以synchronized是重量级锁。

JDK 6 对 Synchronized 的优化

CAS(比较再交换)

    CAS可以将比较和交换转换为原子操作,这个原子操作直接由CPU保证。
    CAS可以保证共享变量赋值时的原子操作。CAS操作依赖3个值:内存中的值V、旧的预估值X、要修改的新值B。如果旧的预估值X 等于 要修改的新值B,就将B保存到内存中。

悲观锁

    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据时都会上锁,别人想拿就会阻塞。
    synchronized 悲观锁; ReentrantLock 悲观锁
    性能较差

乐观锁

    每次拿数据都认为别人不会修改,就算改了也没关系,再重试即可。所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去修改这个数据,如果没有就更新,如果有人修改就重试。

    CAS 获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰,结合CAS和volatile可是实现无锁并发,适用于竞争不激烈,多核CPU的场景下。
    1、因为没有使用synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
    2、但如果竞争激烈,重试频繁发生,反而会影响性能

小结

作用:CAS可以将比较和交换转换为原子操作,这个原子操作直接由CPU保证。
原理:CAS操作依赖3个值:内存中的值V、旧的预估值X、要修改的新值B。如果旧的预估值X 等于 要修改的新值B,就将B保存到内存中。

1.6锁升级过程

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

  • 偏向锁:一个线程反复执行同步块时,适合使用偏向锁,当多个线程执行时,转换为轻量级锁
  • 轻量级锁:适用于多个线程交替执行同步块情况,当多个线程同一时刻进入临界区,轻量级锁升级为重量级锁

Java对象布局

对象布局备注
Mark World对象头(8字节)
Klass pointer对象头(默认压缩,4字节,不压缩,8字节)
实例数据
对齐数据
锁状态25bit31bit1bit(CMS_FREE)4bit(分代年龄)1bit(偏向锁)2bit(锁标志位)
无锁unusedhashCode001
锁状态62bit2bit(锁标志位)
重量级锁指向互斥量(重量级锁)的指针01

偏向锁

锁状态54bit2bit1bit(CMS_FREE)4bit(分代年龄)1bit(偏向锁)2bit(锁标志位)
偏向锁ThreadIDEpoch101

偏向锁必须无竞争情况下使用。当线程第一次访问同步块并获取锁时,偏向锁处理流程如下:
    1、JVM将会把对象头中的标志位设为01,即偏向模式
    2、同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark World中,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,偏向锁的效率高

偏向锁的撤销

1、偏向锁的撤销必须等待全局安全点
2、暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态
3、撤销偏向锁(0),恢复到无锁(01)或轻量级锁(00)的状态

偏向锁的优点

  • 适用于只有一个线程反复执行同步块的情况。JDK6之后偏向锁默认开启,但应用程序启动几秒钟之后才激活。

轻量级锁

锁状态62bit2bit(锁标志位)
轻量级锁指向栈中锁记录的指针01

轻量级锁的目的

  • 适用于多线程交替执行同步块的情况,尽量避免重量级锁引起的性能消耗

轻量级锁的原理

    当关闭偏向锁或者多个线程竞争偏向锁,导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁,步骤如下:
    1、判断当前对象是否处于无锁状态(hashCode,0,01),如果是,则JVM首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象当前的Mark World的拷贝,将对象的Mark World复制到栈帧中的Lock Record中,将Lock Record中的owner指向当前对象
    2、JVM利用CAS操作尝试将对象的Mark World更新为指向Lock Record的指针,如果成功表示竞争到锁,则将锁的标志位变成00,执行同步操作
    3、如果失败,则判断当前对象的Mark World是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,直接执行同步代码块;否则只能说明该锁对象被其他线程抢占了,这是轻量级锁需要膨胀为重量级锁,锁标志位变为10,后面等待的线程将会进入阻塞状态

轻量级锁的释放

轻量级锁的释放也是通过CAS操作来进行的:
    1、取出在获取轻量级锁保存在Displaced Mark World中的数据
    2、用CAS操作将取出的数据替换为当前对象的Mark World中,如果成功,则说明释放锁成功
    3、如果CAS操作替换失败,说明有其他线程尝试获取该锁,则需要将轻量级锁升级为重量级锁

  • 对于绝大部分的锁而言,在整个生命周期内是不会存在竞争的。这是轻量级锁提升性能的依据

自旋锁

    前面我们讨论monitor实现锁的时候,知道monitor会阻塞和唤醒线程,线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,这些操作给系统的并发性能带来了很大的压力。
    同时,虚拟机的开发团队也注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间阻塞和唤醒线程并不值得。
    如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程‘稍等一下”。 但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋) ,这项技术就是所谓的自旋锁。
    自旋锁在JDK 1.4.2中就已经引入,只不过默认是关闭的,可以使用XX:+UseSpinning参数来开启,在JDK 6中就已经改为默认开启了。自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时间很长。那么自旋的线程只会白白消耗处理器资源。而不会做任何有用的工作,反而会带来性上的浪费。因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了。自旋次数的默认值是10次用户可以使用参数-XX: PreBlockSpln来更改。

适应性自旋锁

    在JDK 6中引入了自适应的自旋锁。自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如100次循环。
    另外。如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虚拟机就会变得越来越“聪明”了。

锁消除

    锁消除是指虚拟机即时编译器(JIT)在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
    锁消除的主要判定依据来源于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当做栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。
    变量是否逃逸,对于虚拟机来说需要使用数据流分析来确定,但是程序员自己应该是很清楚的,怎么会在明知道不存在数据争用的情况下要求同步呢?实际上有许多同步措施并不是程序员自己加入的,同步的代码在Java程序中的普遍程度也许超过了大部分读者的想象。

锁粗化

    原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快拿到锁。
    大部分情况下,上面的原则都是正确的, 但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。
    JVM会探测到一连串细小的操作都使用同一个对象加锁,将同步代码块的范围放大,放到这串操作的外面,这样只需要加一次锁即可。

平时写代码如何对 synchronized 优化

1、减少 synchronized 的范围

同步代码块尽量短,减少同步代码块中代码的执行时间,减少锁的竞争。可能偏向锁、轻量级锁就搞得定,避免使用重量级锁。

2、降低 synchronized 锁的粒度

将一个锁拆分为多个锁提高并发度

  • Hashtable 锁定整张表
  • ConcurrentHashMap 锁定桶
  • LinkedBlockingQueue 入队和出队使用不同的锁,读写分离

4、读写分离

读取时不加锁,写入和删除时加锁

  • ConcurrentHashMap
  • CopyOnWriteArrayList
  • ConyOnWriteSet
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值