资讯专栏INFORMATION COLUMN

synchronized关键字使用详解

Jeffrrey / 1552人阅读

摘要:基本使用同步代码块同步代码块延时秒,方便后面测试作用代码块时,方法中的,是指调用该方法的对象。那么这个时候使用关键字就需要注意了推荐使用同步代码块,同步的代码块中传入外部定义的一个变量。

简述

计算机单线程在执行任务时,是严格按照程序的代码逻辑,按照顺序执行的。因此单位时间内能执行的任务数量有限。为了能在相同的时间内能执行更多的任务,就必须采用多线程的方式来执行(注意:多线程模式无法减少单次任务的执行时间)。但是引入了多线程之后,又带来了线程安全的问题。而为了解决线程安全的问题,又引入了锁的概念。java中常用的锁有synchronizedlock两种,本文我们来分析synchronized的具体用法和使用注意事项。

基本使用

同步代码块

/**
 * 同步代码块
 * @throws Exception
 */
public void synchronizedCode() {
    try {
        synchronized (this) {
            System.out.println(getCurrentTime() + ":I am synchronized Code");
            Thread.sleep(5000);//延时5秒,方便后面测试
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

作用代码块时,synchronized方法中的this,是指调用该方法的对象。需要主要的是,synchronized作用代码块时,只会锁住这一小块代码。代码块的上下部分的其他代码在所有的线程仍然是能同时访问的。同时需要注意的是每个对象有用不同的锁。即不会阻塞不同对象的调用。

同步方法

/**
  * 同步方法
  */
public synchronized void synchronizedMethod() {
    try {
        System.out.println(getCurrentTime() + ":I am synchronized method");
        Thread.sleep(5000);//延时5秒,方便后面测试
    } catch (Exception e) {
        e.printStackTrace();
    }
}

synchronized作用在方法上,其实是缺省了this关键字,实际上是synchronized(this)。this是指调用该方法的对象。此锁也不会阻塞不同对象之间的调用。

同步静态方法

/**
* 同步静态方法
*/
public synchronized static void synchronizedStaticMethod() {
    try {
        System.out.println(getCurrentTime() + ":I am synchronized static method");
        Thread.sleep(5000);//延时5秒,方便后面测试
    } catch (Exception e) {
        e.printStackTrace();
    }
}

使用方式和作用普通方式相同,唯一需要注意的地方是此锁所有对象共用,即不同对象之间会阻塞调用。

测试准备

简单说明一下:有一个线程池,在执行多任务时使用。每个同步方法或者代码块中都有一个休眠5秒的动作,利用打印时间加休眠来看线程之间是否有阻塞效果。然后有一个1秒打印一次时间的方法。

public class Synchronized {
    //打印时间时格式化
    public static final String timeFormat = "HH:mm:ss";
    //执行多任务的线程池
    public static final ExecutorService executor = Executors.newFixedThreadPool(4);

    /**
     * 同步代码块
     * @throws Exception
     */
    public void synchronizedCode() {
        try {
            synchronized (this) {
                System.out.println(getCurrentTime() + ":I am synchronized Code");
                Thread.sleep(5000);//延时5秒,方便后面测试
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 同步方法
     */
    public synchronized void synchronizedMethod() {
        try {
            System.out.println(getCurrentTime() + ":I am synchronized method");
            Thread.sleep(5000);//延时5秒,方便后面测试
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 同步静态方法
     */
    public synchronized static void synchronizedStaticMethod() {
        try {
            System.out.println(getCurrentTime() + ":I am synchronized static method");
            Thread.sleep(5000);//延时5秒,方便后面测试
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 循环打印时间
     */
    public static void printNumber() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        printOnceASecond();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    /**
     * 一秒打印一次时间
     *
     * @throws Exception
     */
    public static void printOnceASecond() throws Exception {
        System.out.println(getCurrentTime());
        Thread.sleep(1000);
    }

    /**
     * 获取当前时间
     *
     * @return
     */
    public static String getCurrentTime() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(timeFormat));
    }
}

OK,接下来我们就来测试下锁的互斥性以及使用注意事项(都是多线程的情况下)。

开始测试

同一个对象同步代码块

public static void main(String[] args) throws Exception {
    printNumber();//控制台循环打印时间
    Synchronized es = new Synchronized();
    executor.execute(() -> es.synchronizedCode());
    executor.execute(() -> es.synchronizedCode());
}

execute

20:34:41:I am synchronized Code
20:34:41
20:34:42
20:34:43
20:34:44
20:34:45
20:34:46:I am synchronized Code

同步代码块中休眠5秒,导致另外一个线程阻塞5秒后再执行。说明代同步码块会阻塞同一个对象的不同线程之间的调用(同步方法和同步静态方法也会阻塞同一个对象的不同线程之间的调用,此处省略测试代码)

不同对象同步代码块

public static void main(String[] args) throws Exception {
    printNumber();//控制台循环打印时间
    Synchronized es = new Synchronized();
    Synchronized es1 = new Synchronized();
    executor.execute(() -> es.synchronizedCode());
    executor.execute(() -> es1.synchronizedCode());
}

execute

20:44:34:I am synchronized Code
20:44:34:I am synchronized Code

由结果可以看出,不同对象之间代码块锁互不影响(多线程也一样)。原因是因为代码块中synchronized (this)

锁的是当前调用对象,不同对象之间不是同一把锁,因此互不影响(同步方法原理也是如此,省略测试代码)。

同一对象同步代码块和方法

public static void main(String[] args) throws Exception {
    printNumber();//控制台循环打印时间
    Synchronized es = new Synchronized();
    executor.execute(() -> es.synchronizedCode());
    executor.execute(() -> es.synchronizedMethod());
}

execute

20:51:27:I am synchronized method
20:51:27
20:51:28
20:51:29
20:51:30
20:51:31
20:51:32:I am synchronized Code

因为同步代码块和同步方法,都是锁当前调用对象,因此执行后打印上述结果应该在意料之中。基于这样的特性,实际开发在使用spring的时候就需要注意了,我们的bean交给spring容器管理之后,默认都是单例的。那么这个时候使用synchronized关键字就需要注意了(推荐使用同步代码块,同步的代码块中传入外部定义的一个变量)。

不同对象静态同步方法

public static void main(String[] args) throws Exception {
    printNumber();//控制台循环打印时间
    Synchronized es = new Synchronized();
    Synchronized es1 = new Synchronized();
    executor.execute(() -> es.synchronizedStaticMethod());
    executor.execute(() -> es1.synchronizedStaticMethod());
}

execute

21:05:39:I am synchronized static method
21:05:40
21:05:41
21:05:42
21:05:43
21:05:44:I am synchronized static method

由上述结果可以看出来,静态同步方法会阻塞所有的对象。原因是所有的静态同步方法都是占用的同一把锁。

相同对象同步方法和静态同步方法

public static void main(String[] args) throws Exception {
    printNumber();//控制台循环打印时间
    Synchronized es = new Synchronized();
    executor.execute(() -> es.synchronizedMethod());
    executor.execute(() -> es.synchronizedStaticMethod());
}

execute

21:11:03:I am synchronized static method
21:11:03:I am synchronized method

由此结果可以看出,同步方法和静态同步方法之间不会造成阻塞的现象。因为他们锁的对象不一样。同步方法占用的锁是调用对象,静态同步方法锁的是编译后的class对象(类锁)。

总结

同一个对象,同步方法、同步代码块之间互斥,同时和自己也互斥。静态同步方法只和自己互斥

不同对象之间,同步方法、同步代码块不会互斥。静态同步方法会互斥

synchronized在占用锁时,必须精确到某一个具体的对象

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74420.html

相关文章

  • 一起学并发编程 - synchronized详解

    摘要:每个对象只有一个锁与之相关联。实现同步则是以系统开销作为代价,甚至可能造成死锁,所以尽量避免滥用。这种机制确保了同一时刻该类实例,所有声明为的函数中只有一个方法处于可执行状态,从而有效避免了类成员变量访问冲突。 synchronized是JAVA语言的一个关键字,使用 synchronized 来修饰方法或代码块的时候,能够保证多个线程中最多只有一个线程执行该段代码 ... 概述 ...

    acrazing 评论0 收藏0
  • Java中wait、notify、notifyAll使用详解

    摘要:用法中规定,在调用者三个方法时,当前线程必须获得对象锁。作用方法作用线程自动释放占有的对象锁,并等待。当生产者生产了一个数据或者消费者消费了一个数据之后,使用方法来通知所有等待当前对象锁的线程,但是一次只会有一个等待的线程能拿到锁。 基础知识 首先我们需要知道,这几个都是Object对象的方法。换言之,Java中所有的对象都有这些方法。 public final native void...

    rozbo 评论0 收藏0
  • Java synchronized 多线程同步问题详解

    摘要:同步代码块二类,锁是小括号中的类对象对象。因为对于同一个实例对象,各线程之间访问其中的同步方法是互斥的。优化同步代码块的方式有,减少同步区域或减小锁的范围。 版权声明:本文由吴仙杰创作整理,转载请注明出处:https://segmentfault.com/a/1190000009225706 1. 引言 在 Java 多线程编程中,我们常需要考虑线程安全问题,其中关键字 synchro...

    Eidesen 评论0 收藏0
  • 后台开发常问面试题集锦(问题搬运工,附链接)

    摘要:基础问题的的性能及原理之区别详解备忘笔记深入理解流水线抽象关键字修饰符知识点总结必看篇中的关键字解析回调机制解读抽象类与三大特征时间和时间戳的相互转换为什么要使用内部类对象锁和类锁的区别,,优缺点及比较提高篇八详解内部类单例模式和 Java基础问题 String的+的性能及原理 java之yield(),sleep(),wait()区别详解-备忘笔记 深入理解Java Stream流水...

    spacewander 评论0 收藏0
  • 后台开发常问面试题集锦(问题搬运工,附链接)

    摘要:基础问题的的性能及原理之区别详解备忘笔记深入理解流水线抽象关键字修饰符知识点总结必看篇中的关键字解析回调机制解读抽象类与三大特征时间和时间戳的相互转换为什么要使用内部类对象锁和类锁的区别,,优缺点及比较提高篇八详解内部类单例模式和 Java基础问题 String的+的性能及原理 java之yield(),sleep(),wait()区别详解-备忘笔记 深入理解Java Stream流水...

    xfee 评论0 收藏0

发表评论

0条评论

Jeffrrey

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<