Java中的关键字synchronized

编辑:Discuz论坛 发布于2019-07-12 09:48

1. 介绍

Java并发系列的文章中,这个是第二篇文章。在前面的一篇文章中,我们学习了Java中的Executor池和Excutors的各种类别。

在这篇文章中,我们会学习synchronized关键字以及我们在多线程的环境中如何使用。

2. 什么是同步?

在一个多线程的环境中,多个线程同时访问相同的资源的情况是存在的。例如,两个线程试图写入同一个文本文件。它们之间没有任何的同步,当两个或多个线程对同一文件具有写访问权时,写入该文件的数据可能会损坏。
同理,在JVM中,每个线程在各自的栈上存储了一份变量的副本。某些其他线程可能会更改这些变量的实际值。但是更改后的值可能不会刷新到其他线程的本地副本中。
这可能导致程序执行错误和非确定性行为。

为了避免这种问题,Java给我们提供了synchronized这有助于实现线程之间的通信,使得只有一个线程访问同步资源,而其他线程等待资源变为空闲。

synchronized关键字可以被用在下面一些不同的方式中,比如一个同步块:

synchronized(someobject){
    //thread-safe code here
}

对方法进行同步:

public synchronized void someMethod(){
    //thread-safe code here
}

3.在JVM中synchronized是如何实现的

当一个线程试图进入一个同步块或者同步方法中的时候,它必须先获得一个同步对象上的锁。一次只可以有一个线程获取锁,并且执行块中的代码。

如果其他线程尝试访问该同步块,则必须等待,直到当前线程执行完同步块的代码。当前线程退出后,锁将被自动释放,其它线程可以获取锁并进入同步代码块。

  • 对于一个synchronized块来说,在synchronized关键字后的括号中指定的对象上获取锁;
  • 对于一个synchronized static方法,锁是在.class对象上获取的;
  • 对于synchronized实例方法来说,锁定是在该类的当前实例上获得的,即该实例(this);

4.同步方法

定义同步方法就像在返回类型之前简单地包含关键字一样简单。我们定义一个顺序打印数字1-5之间的方法。会有两个线程来访问这个方法,所以让我们来看看在没有使用synchronized关键字它们的运行情况, 和我们使用关键字来锁住共享对象会发生什么:

public class NonSynchronizedMethod {

    public void printNumbers() {
        System.out.println("Starting to print Numbers for " + Thread.currentThread().getName());

        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }

        System.out.println("Completed printing Numbers for " + Thread.currentThread().getName());
    }
}