synchronized关键字使用详解

简述

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

基本使用

同步代码块spring

/**
 * 同步代码块
 * @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是指调用该方法的对象。此锁也不会阻塞不一样对象之间的调用。ide

同步静态方法测试

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

使用方式和做用普通方式相同,惟一须要注意的地方是此锁全部对象共用,即不一样对象之间会阻塞调用。this

测试准备

简单说明一下:有一个线程池,在执行多任务时使用。每一个同步方法或者代码块中都有一个休眠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,接下来咱们就来测试下锁的互斥性以及使用注意事项(都是多线程的状况下)。code

开始测试

同一个对象同步代码块orm

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在占用锁时,必须精确到某一个具体的对象
相关文章
相关标签/搜索