内卷老员工之synchronized锁

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

synchronized关键字

前言

  • java synchronized提供了一套同步多线程访问对象的机制。java8之前锁的方式比较重度,之后对关键字锁进行了轻量化升级。

synchronized关键字

  • java的同步块用synchronized关键字标记,使得同步代码块中的代码仅在某个线程内部执行,阻塞其他进入该代码块的线程。
  • 标记类型:
    • 实例方法:作用于调用方法的对象
    • 静态方法:作用于所有对象
    • 实例方法内的代码块:作用于调用方法的对象
    • 静态方法内的代码块:作用于所有对象

同步实例方法

  • 同一个类可以有多个实例对象,而同步实例方法则是同一个实例在不同线程中仅有一个线程能够访问相同实例的同步代码块中的方法,不同实例之间不互相影响。
public class myclass{
    private int a = 0;
    public synchronized void add(int value){
        this.a += value;
    }
    public synchronizrd void subtract(int value){
        this.a -= value;
    }
}
复制代码

同步静态方法

  • 同步静态方法在静态方法所属的类上进行同步,因此不同线程访问的都是同一部分代码块,同一时间仅有一个线程可以执行同步静态代码块中的方法。
public class myclass{
    private static int a = 0;
    public static synchronized void add(int value){
        this.a += value;
    }
    public static synchronizrd void subtract(int value){
        this.a -= value;
    }
}
复制代码

同步实例方法内的代码块

  • 有时不需要同步完整的方法,仅需要同步代码中的一部分代码块。代码中使用了this关键字,他是调用add方法的实例,也就是当前的实例对象。因此不同实例对象不会互相影响,而于同步实例方法一致,相同的实例对象在同一时刻只能由同一线程访问同步的代码。
private int a = 0;
public void add(int value){
    synchronized(this){
        this.a += value;
    }
}
复制代码

同步静态方法内的代码块

  • 与实例方法内的代码块同步不同,静态方法内的代码块需要锁定的是class本身。起作用等同于对于静态方法的锁定,同一时刻只能有同一线程访问,与实例对象本身无关。
public class myclass{
    private static int a = 0;
    public static void add(int value){
        synchronized(myclass.class){
            this.a += value;   
        }
    }
    public static void subtract(int value){
        synchronized(myclass.class){
            this.a -= value;   
        }
    }
}
复制代码

lambda表达式中的同步

  • 同样,synchronized可以在lambda表达式中进行使用,使用时的锁定逻辑与正常情况下一致,如下示例中为对类对象的同步。
Consumer<String> func = (String param) -> {

      synchronized(SynchronizedExample.class) {

        System.out.println(
            Thread.currentThread().getName() +
                    " step 1: " + param);

        try {
          Thread.sleep( (long) (Math.random() * 1000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(
            Thread.currentThread().getName() +
                    " step 2: " + param);
      }

    };

复制代码

同步和数据可见性

  • 如果不使用synchronized关键字,则无法获取在其他线程中修改的共享变量的值。同时无法保证一个线程中保存在cpu寄存器中的数据变量何时提交到主内,也无法保证其他线程的变量何时从主内存刷新cpu寄存器中的变量。而使用synchronized关键字当线程进入同步块时会从主内存刷新所有的共享变量的值,离开同步块时会将cpu寄存器中的共享变量值写回到主内存中。

同步和指令重排序

  • 为了解决在同步块内的写入被重排序到同步块外执行的问题,synchronized对同步代码块前后及其中的重排序设置了限制,可以确保不发生指令重排序产生异常。

要同步的对象

  • 实际上,可以选择任何对象作为同步的对象,但是不要选择String或任何原始类型的包装类进行同步,编译器可能会对这些对象进行优化,在不同的地方认为在使用相同的实例,可能会发生重用。

同步代码性能开销

  • 在java中进入或退出同步块会产生很小的性能开销,如果频繁多次进入则需要注意性能上的开销问题。尽量不要使用比必要的代码更粗力度的同步锁,将同步锁进行细化。同时随着java版本的升级,synchronized已经从重度锁转化为梯度锁,当竞争不激烈时并非为重量级锁。

后记

  • 千古兴亡多少事?悠悠。不尽长江滚滚流。
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享