Concurrency(六: 同步代码块)

上文中提及在java中可以使用 synchronized
关键字来解决竟态条件。主要通过 synchronized
关键字来标注代码块,告诉jvm该代码块为临界区代码,以保证每次只会有一个线程能访问到该代码块里的代码,直到一个线程执行完毕后,另一个线程才能执行。

synchronized
使用对象作为同步。若多个同步代码块使用的是同一个对象锁,那么一次只能有一个线程访问多个同步代码块中的一个。若多个同步代码块使用的不是同一个对象锁,那么多个线程能够同时访问多个同步代码块。

synchronized
一共有四种用法,如下所示:

  • 标注实例方法
  • 标注静态方法
  • 标注实例方法里的代码块
  • 标注静态方法里的代码块

标注方法

在方法签名中声明 synchronized
,能够让整个方法标注为代码块。

标注实例方法

public synchronized void method0() {
    // do something
}
复制代码

示例代码中使用的是该方法所属的实例对象作为对象锁。

标注静态方法

public synchronized static void method0() {
    // do something
}
复制代码

示例代码中使用的是该方法所属类声明指向的静态实例对象作为对象锁。

标注实例方法里的代码块

若不希望将整个方法标注为代码块,可以尽在方法中标注部分代码块作为同步代码块。

标注实例方法里的代码块

public void method1() {
        synchronized (this) {
            // do something
        }
    }
复制代码

在实例方法中,通过 synchronized
构造方法的方式来标注代码块,括号中传递的是该同步代码块使用的对象锁,需要实现同步的临界区代码写在 {}
中。代码中的 this
指该代码块所属方法所属的对象实例作为对象锁。该方式与在实例方法签名中声明 synchronized
效果相当。

标注静态方法里的方法块

public synchronized static void method1() {
        synchronized (MyClass.class) {
            // do something
        }
    }
复制代码

在静态方法中,通过 synchronized
构造方法的方式来标注代码块,括号中传递的是该同步代码块使用的对象锁,需要实现同步的临界区代码写在 {}
中。代码中的 MyClass.class
指该代码块所属方法所属类的静态对象实例作为对象锁。该方式与在静态方法签名中声明 synchronized
效果相当。

同步实例

synchronized
编码实例,我们在SynchronizedExample中编写四个方法,分别反映上文提及的四种情况。每个方法中都在线程进入后暂停3s,随后线程退出代码块。

public class SynchronizedExample {
    private static void method(String name) {
        final Thread thread = Thread.currentThread();
        LocalDateTime now = LocalDateTime.now();

        System.out.println(thread.getName() + ": [" + now + "] in " + name);

        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void method0() {
        method("instance-method0");
    }

    public void method1() {
        synchronized (this) {
            method("instance-method1");
        }
    }

    public synchronized static void method2() {
        method("static-method2");
    }

    public synchronized static void method3() {
        synchronized (SynchronizedExample.class) {
            method("static-method3");
        }
    }

    public static void main(String[] args) {
        SynchronizedExample example = new SynchronizedExample();

        Runnable myRunnable0 = () -> {
            example.method0();
            example.method1();
        };

        Runnable myRunnable1 = () -> {
            SynchronizedExample.method2();
            SynchronizedExample.method3();
        };

        IntStream.range(1, 3)
                .forEach(i -> new Thread(myRunnable0, "Thread-" + i).start());

        // 实例同步方法需要12s才能执行完,主线程等待13s后再执行静态同步方法
        try {
            Thread.sleep(13000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        IntStream.range(1, 3)
                .forEach(i -> new Thread(myRunnable1, "Thread-" + i).start());
    }
}
复制代码

执行结果:

Thread-1: [2019-03-15T14:48:50.251] in instance-method0

Thread-2: [2019-03-15T14:48:53.255] in instance-method0

Thread-2: [2019-03-15T14:48:56.258] in instance-method1

Thread-1: [2019-03-15T14:48:59.262] in instance-method1

Thread-1: [2019-03-15T14:49:03.234] in static-method2

Thread-2: [2019-03-15T14:49:06.238] in static-method2

Thread-2: [2019-03-15T14:49:09.243] in static-method3

Thread-1: [2019-03-15T14:49:12.247] in static-method3

从结果可以看出,使用同个对象实例作为对象锁和使用同个静态对象作为对象锁的方法分别被线程1和线程2访问。
从上文打印的时间可以看出每个线程每次仅能访问使用对个对象锁多个同步代码块中的一个。每3s执行完一个同步代码块。

Java Concurrency 工具包

实际上 synchronized
是java中第一次针对竞态条件发布的同步措施,但在实际开发中并不是那么好用,因此在jdk1.5后,发布了整个 并发工具包
,提供了各式各样的多线程安全控件,用于协助开发者编写线程安全的应用程序。

原文 

https://juejin.im/post/5c8b39afe51d452fc0161b67

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Concurrency(六: 同步代码块)

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址