• synchronized学习


      现代软件开发中并发已经成为一项基础能力,而Java精心设计的高效并发机制,正是构建大规模应用的基础之一。本文中我们将学习synchronized关键字的基本用法。

      synchronized是Java内建的同步机制,也称为Intrinsic Locking,它提供了互斥的语义和可见性,当任务要执行被synchronized关键字保护的代码片段的时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁;同时,其他试图获取锁的线程只能等待或者阻塞在那里。

     

    synchronized用法

      synchronized可以加在普通方法前、代码块上、静态方法前、类上,加在不同的地方锁是不一样的,如下:

    • 加在普通方法上,锁是当前实例对象;
    private synchronized void f(){
        // doSomething
    }

       注意:synchronized关键字是不能继承的,也就是说,基类的方法 synchronized fun(){} 在继承类中并不自动是 synchronized fun(){} ,而是变成了 fun(){} 。继承时,需要显式的指定它的某个方法为 synchronized 方法。

    • 加在静态方法和类上,锁是当前类的class对象;
    public synchronized class F{
        // doSomething
    }
    
    public class E{
        public static synchronized void f(){
            // doSomething
        }
    }
    • 同步方法块,锁是括号里面的对象,可以是普通对象,也可以是class对象;
    public class F{
        public void f(){
            synchronized(this){
                // doSomething
            }
        }
    
        public void e(){
            synchronized(Object.class){
                // doSomething
            }
        }
    }

      我们先看一个简单的示例:

    public class SynLockTest {
        
        private static int index = 0;
        
        public static void runTest() {
            Thread t1 = new Thread(new Runnable() {
                public void run() {
                    for(int i=0 ; i<1000 ; i++) {
                        synchronized(SynLockTest.class) {
                            index++;
                        }
                    }
                }
            });
            Thread t2 = new Thread(new Runnable() {
                public void run() {
                    for(int i=0 ; i<1000 ; i++) {
                        synchronized(SynLockTest.class) {
                            index++;
                        }                    
                    }
                }
            });
            t1.start();
            t2.start();
        }
        
        public static void main(String[] args) throws Exception{
            runTest();
            Thread.sleep(2000);
            System.out.println(index);
        }
    }

      如上代码中,主线程会启动两个子线程(t1、t2),每个线程的任务是一样的,都是对共享变量index自增1000次,接着主线程休眠2s,再输出index的值,代码中对自增操作进行了同步(synchronized代码块包围),同步锁是SynLockTest这个类的class对象,最终程序输出结果将是2000,如果这里不进行同步或者将同步代码块中的锁改为this,输出结果大多数情况下应该是小于2000的,这是为什么呢?

      首先,Java中的自增操作并不是一次完成的,虚拟机在执行的时候首先要读取index的值,然后将index的值加1,最后将index的值更新,这三步是分开进行的,如果线程t1读取了index的值,这时候线程t1的时间片用完了,被挂起,t2开始执行。。。吭哧吭哧一堆自增,结束之后,t1继续执行,这时t1进行一次自增之后会更新index的值,注意,这里更新的是t1之前所持有的index的值,相当于把t2刚才所做的操作全部覆盖了,相当于t2白做了,所以最终输出结果小于2000,因为部分自增的结果被覆盖了。

      再说把锁换成this之后,这时虽然自增操作虽然被同步块保护了,但是这里获取的锁是匿名类这个对象(Runnable)的锁,而t1和t2中的这个匿名类是不一样的(都是new出来的),所以并没有互斥效果,也就相当于和没有加锁一个效果。

    synchronized作用

      synchronized的作用是通过互斥来实现线程安全,关于线程安全,需要保证几个基本特性,本文简单介绍一下(详细可以参考Java内存模型一文):

    • 原子性,简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
    • 可见性,是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile就是负责保证可见性的。
    • 有序性,是保证线程内串行语义,避免指令重排等。

      关于原子性,可参考如上例子,在自增操作上加上同步控制,保证同一时刻只能有一个线程执行自增操作,并且执行的过程不会被其他线程打断。

      关于可见性,线程在获取到锁时,JVM会把该线程对应的本地内存置为无效,并且会从主内存中读取共享变量。线程释放锁时,JVM会把该线程对应的本地内存中的共享变量立即刷新到主内存中。通过这种方式来保证变量的可见性。

      关于有序性,被同步的代码,同一时刻只能有一个线程会执行,而Java本身是能保证这一点的(线程内表现为串行的语义,Within-Thread As-If-Serial Sematics),所以说在这个层面上synchronized是能实现有序性的。

       这一部分关于synchronized如何实现原子性、可见性、有序性只是简单介绍,后面会从底层实现来详细总结synchronized是如何实现这些功能的。

    总结

    1. synchronized的基本用法,修饰代码块,以及分别加什么锁;
    2. synchronized的作用,可以保证原子性、可见性和有序性的;

      综上,synchronized是万精油,使用起来很方便,直接在要保护的代码块上加上synchronized修饰即可,可读性很高。虽然早期synchronized的性能问题多为人诟病,但是现代JDK对synchronized进行了很大优化,在通用场景下,我们无需过多关注这点。因此,一般以synchronized关键字入手,只有在性能调优时才考虑替换为Lock对象或采用原子类。

      本文只是简单总结了synchronized的用法及作用,并未涉及其底层原理,这部分内容会在后面撰文详述。

  • 相关阅读:
    Java多线程-ThreadLocal和InheritableThreadLocal的使用
    Java多线程-join的使用
    VsCode配置让 ts 文件自动编译为 js文件
    npm和yarn更改依赖包全局下载和缓存路径
    npm和yarn更改淘宝镜像
    Java多线程-管道流实现线程间通信
    Java多线程-生产者/消费者模式实现
    Java多线程-使用 wait / notify 实现线程间的通信
    Java多线程-volatile关键字
    Java多线程-synchronized(非this对象)
  • 原文地址:https://www.cnblogs.com/volcano-liu/p/10131149.html
Copyright © 2020-2023  润新知