Java并发编程之synchronized关键字


Java同步块将方法或代码块标记为同步的。Java中的同步块一次只能在一个线程中执行。因此,可以使用Java同步块来避免竞争条件。
同步机制是Java的第一个用于同步访问由多个线程共享的对象的机制。

synchronized的使用

Java中的同步块用Synchronized关键字标记。Java中的同步块在某些对象上是同步的。在同一对象上同步的所有同步块只能有一个线程同时在其中执行。所有其他试图进入同步块的线程都将被阻塞,直到同步块中的线程退出该块

1.在实例方法中的引用

public synchronized void add(int value){}

注意在add()方法声明中使用了synchronized关键字。这告诉Java该方法是同步的。Java中的同步实例方法在拥有该方法的实例(对象)上是同步的。因此,每个实例都有其同步的方法,在不同的对象上同步:所属的实例。在同步实例方法中,每个实例只能执行一个线程。如果存在多个实例,那么每次可以在每个实例的同步实例方法中执行一个线程。每个实例一个线程。

2.在静态方法中的引用

public synchronized static void add(int value){}

静态方法被标记为synchronized,就像使用synchronized关键字的实例方法一样。
同步静态方法在同步静态方法所属的类的类对象上同步。由于每个类在Java VM中只存在一个类对象,因此同一个类中的静态同步方法中只能有一个线程执行。
如果一个类包含多个静态同步方法,则只能在一个方法中同时执行一个线程。

public synchronized static void add(int value){}
public synchronized static void muli(int value){}

在任何给定时间,只有一个线程可以在两个add()和muli()方法中的任何一个中执行。如果线程A正在执行add(),那么线程B在线程A退出add()之前既不能执行add()也不能执行muli()。
如果静态同步方法位于不同的类中,那么一个线程可以在每个类的静态同步方法中执行。每个类一个线程,不管它调用哪个静态同步方法.

3.实例方法中的代码块

public void add(int value){
     synchronized(this) {
      }
  }

您不必同步整个方法。有时,最好只同步方法的一部分。
请注意,Java同步块构造如何在括号中使用对象。 在示例“ this”中,使用了add方法。 同步结构在括号中使用的对象称为监视对象。 据说该代码已在监视对象上同步。 同步实例方法将其所属的对象用作监视对象。在同一个监视器对象上同步的Java代码块中,只有一个线程可以执行。

4.静态方法中的代码块

public static void add(int value){
      synchronized(this){
      }
  }

同步块也可以在静态方法内部使用。这个和在实例方法中使用代码同步代码块是一样的。

synchronized在lambda表达式中使用

	//在lambda表达式用使用同步块
    Consumer<String> func = (String params) -> {
        synchronized(ThreadSynchQuick.class){
            System.out.println(Thread.currentThread().getName() + " step 1:" + params);
            try{
                Thread.sleep((long)(Math.random() * 1000));
            }catch (Exception e) {
                //TODO: handle exception
            }
            System.out.println(Thread.currentThread().getName() + " step 2: " + params);
        }
    };

    Thread thread01 = new Thread(() -> {
        func.accept("Parameter");
    },"Thread_01");

    Thread thread02 = new Thread(() -> {
        func.accept("Parameter");
    },"Thread_02");

    thread01.start();
    thread02.start();

可以在Java Lambda表达式和匿名类中使用同步块。
注意,synchronized块是在包含lambda表达式的类的类对象上同步的。它也可以在另一个对象上同步。

synchronized和数据可视化

如果不使用synchronized关键字(或Java volatile关键字),就不能保证当一个线程改变了与其他线程共享的变量的值(例如,通过一个所有线程都可以访问的对象),其他线程可以看到改变的值。无法保证一个线程保存在CPU寄存器中的变量何时“提交”到主存,也无法保证其他线程何时“刷新”保存在CPU寄存器中的变量。
synchronized关键字改变了这一点。当线程进入同步块时,它将刷新该线程可见的所有变量的值。当线程退出同步块时,所有对线程可见变量的更改都将提交到主存中。

synchronized和指令重新排序

允许Java编译器和Java虚拟机对代码中的指令进行重新排序,以使它们执行得更快,这通常是通过让CPU并行执行重新排序的指令。
指令重新排序可能会潜在地导致由多个线程同时执行的代码出现问题。例如,如果对同步块内部发生的变量的写操作被重新安排在同步块外部发生。
为了解决这个问题,Java synchronized关键字对同步块之前、内部和之后的指令重新排序施加了一些限制。最终的结果是,您可以确保您的代码正常工作——不会发生指令重新排序,从而导致代码的行为与您所编写的代码的预期行为不同。

synchronized 对象

同步块必须在某些对象上同步。你可以选择任何对象上同步,但建议您不同步在字符串对象,或任何原始类型包装器对象,编译器会优化这些,所以你在代码中使用相同的实例在不同的地方,你以为你使用不同的实例。为了安全起见,同步这个对象或一个新对象()。Java编译器、Java VM或Java库不会在内部缓存或重用它们。

同步块的局限性和替代品

Java中的同步块有几个限制。 例如,Java中的同步块仅一次允许一个线程进入。 但是,如果两个线程只想读取一个共享值而不更新它,该怎么办? 这可能是安全的。 作为同步块的替代方法,您可以使用读/写锁来保护代码,该锁比同步块具有更高级的锁定语义。 Java实际上带有一个内置的ReadWriteLock类你可以使用该类。
如果您希望允许N个线程进入一个同步块,而不只是一个,该怎么办?你可以使用信号量来实现这种行为。Java实际上提供了一个可以使用的内置Java信号量类。
同步块并不保证等待进入它们的线程被授予访问同步块的顺序。如果您需要确保尝试进入同步块的线程按照它们请求访问的顺序获得访问,该怎么办?
如果只有一个线程写入共享变量,而其他线程只读取该变量,那该怎么办?这样,你就可以只使用volatile变量而不进行任何同步。

在群集设置的Synchronized块

请记住,同步块只会阻止同一个Java VM中的线程进入该代码块。如果你有相同的Java应用程序运行在多个Java VM上(在一个集群中),那么每个Java VM中的一个线程可能同时进入那个同步块。
如果需要跨集群中的所有Java vm同步,那么需要使用其他同步机制,而不仅仅是同步块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值