synchronized 用法详解

1、简介

在Java中,提供了一个关键字 synchronized 来保证多线程安全问题,synchronized可以修饰一个方法、代码块,能够保证在同一时刻最多只有一个线程执行该段代码。本篇文章将全面讲解synchronized 各种情景下的使用及产生的作用。

2、synchronized 修饰某一非静态方法

我们先看一下示例:

private class MyObject1{

    public synchronized void method1(String name){
        System.out.println(name+": method1  start sleep..."+new Date());
        ThreadUtils.sleep(1000);
        System.out.println(name+": method1  end sleep..."+new Date());
    }

    public synchronized void method2(String name){
        System.out.println(name+": method2  start sleep..."+new Date());
        ThreadUtils.sleep(2000);
        System.out.println(name+": method2  end sleep..."+new Date());
    }
}

测试代码如下:

private void test1(){
    
    MyObject1 myObject1 = new MyObject1();
    MyObject1 myObject2 = new MyObject1();

    Thread thread1  = new Thread(new Runnable() {
        @Override
        public void run() {
            myObject1.method1(Thread.currentThread().getName());
        }
    }, "thread1");

    Thread thread2  = new Thread(new Runnable() {
        @Override
        public void run() {
            myObject2.method1(Thread.currentThread().getName());
        }
    }, "thread2");

    Thread thread3  = new Thread(new Runnable() {
        @Override
        public void run() {
            myObject2.method1(Thread.currentThread().getName());
        }
    }, "thread3");

    Thread thread4  = new Thread(new Runnable() {
        @Override
        public void run() {
            myObject2.method2(Thread.currentThread().getName());
        }
    }, "thread4");
    
    thread1.start();
    thread2.start();
    thread3.start();
    thread4.start();
}

示例输出如下:

thread2: method1  start sleep...Sat Jun 13 09:22:31 CST 2020
thread1: method1  start sleep...Sat Jun 13 09:22:31 CST 2020
thread1: method1  end sleep...Sat Jun 13 09:22:32 CST 2020
thread2: method1  end sleep...Sat Jun 13 09:22:32 CST 2020
thread4: method2  start sleep...Sat Jun 13 09:22:32 CST 2020
thread4: method2  end sleep...Sat Jun 13 09:22:34 CST 2020
thread3: method1  start sleep...Sat Jun 13 09:22:34 CST 2020
thread3: method1  end sleep...Sat Jun 13 09:22:35 CST 2020

通过输出日志,我们可以得到如下总结:

1、不同的对象同步方法,被不同的线程执行,相互之间没有影响;例如 MyObject1 创建了两个对象

myObject1 和 myObject2 , 被两个不同的线程thread1、thread2调用其同步方法method1, 相互之间是没有影响的。

2、相同对象相同的同步方法,被不同线程执行,不同线程之间具有同步关系;例如 myObject2 的 method1 分别被 thread2 和 thread3 线程执行, 两个线程具有同步关系。

3、相同对象不同的同步方法,被不同线程执行,不同线程之间具有同步关系;例如 myObject2 的 method1 和 method2 分别被 thread3 和 thread4 线程执行, 两个线程具有同步关系。

一句话可以概括为:synchronized 修饰某一非静态方法,其实是为这个类添加对象锁,当某个类中 有多个 被synchronized修饰的同步方法,谁先持有了对象锁,谁先执行, 其他线程需要等待,即便线程调用的是不同的同步方法,也需要等待。

 

3、synchronized 修饰某一静态方法

通过上面的非静态方法分析,你是否能够猜想到当synchronized 修饰某一静态方法 具有哪些作用呢?请思考一分钟,再看下面讲解。

实例:

private static class MyObject1{

    public static synchronized void method3(String name){
        System.out.println(name+": method3  start sleep..."+new Date());
        ThreadUtils.sleep(1000);
        System.out.println(name+": method3  end sleep..."+new Date());
    }

    public static synchronized void method33(String name){
        System.out.println(name+": method33  start sleep..."+new Date());
        ThreadUtils.sleep(2000);
        System.out.println(name+": method33  end sleep..."+new Date());
    }
}

测试用例:

private void test(){

        Thread thread3_1 = new Thread(new Runnable() {
            @Override
            public void run() {
                MyObject1.method3(Thread.currentThread().getName());
            }
        }, "thread3_1");

        Thread thread3_2 = new Thread(new Runnable() {
            @Override
            public void run() {
                MyObject1.method33(Thread.currentThread().getName());
            }
        }, "thread3_2");

        Thread thread3_3 = new Thread(new Runnable() {
            @Override
            public void run() {
                MyObject1.method33(Thread.currentThread().getName());
            }
        }, "thread3_3");

        thread3_1.start();
        thread3_2.start();
        thread3_3.start();
    }

示例输出如下:

thread3_1: method3  start sleep...Sat Jun 13 09:53:29 CST 2020
thread3_1: method3  end sleep...Sat Jun 13 09:53:30 CST 2020
thread3_3: method33  start sleep...Sat Jun 13 09:53:30 CST 2020
thread3_3: method33  end sleep...Sat Jun 13 09:53:32 CST 2020
thread3_2: method33  start sleep...Sat Jun 13 09:53:32 CST 2020
thread3_2: method33  end sleep...Sat Jun 13 09:53:34 CST 2020

通过输出日志,我们可以得到如下总结:

1、不同的静态同步方法,被不同的线程执行,不同线程之间具有同步关系;例如 两个不同的线程thread3_1、thread3_2调用其同步方法method3、method33, 相互之间是同步执行的。

2、相同的静态同步方法,被不同线程执行,不同线程之间具有同步关系;例如 两个不同的线程thread3_2、thread3_3调用其同步方法method33、method33, 相互之间是同步执行的。

一句话可以概括为:synchronized 修饰某一静态方法,其实是为这个类添加类锁,当某个类中 有多个 被synchronized修饰的同步方法,谁先持有了类锁,谁先执行, 其他线程需要等待,即便线程调用的是不同的同步方法,也需要等待。

 

4、synchronized 修饰同步代码块

synchronized 修饰同步代码块 通常 有两种方式,分别如下:

方式一:

synchronized (MyObject1.class){
    // 具体代码
    ………………
}

方式二:

synchronized (this){
    // 具体代码
    ………………
}

这里不在用示例进行对比,大家可以自己去写测试用例,其实和上面1、2的出的结论是一样的,当线程执行到synchronized (this) 代码块中时, 当前线程持有了该对象的锁,具有的特性如1;当线程执行到synchronized (MyObject1.class) 代码块中时,当前线程持有了该类的锁,具有的特性如2。

 

5、synchronized 一些特殊使用

5.1 变量的一致性

首先我们看如下示例:

private static class MyService{
        private boolean isContinueRun = true;

        public void runMethod(){
            while (isContinueRun){
                // 具体执行代码
            }
            System.out.println(Thread.currentThread().getName()+" is stop ..."+new Date());
        }

        public void setContinueRun(boolean continueRun) {
            System.out.println(Thread.currentThread().getName()+" set continueRun is "+continueRun+"..."+new Date());
            isContinueRun = continueRun;
        }

        public boolean isContinueRun() {
            return isContinueRun;
        }
}

测试用例:

private void test(){

    MyService myService = new MyService();

    new Thread(new Runnable() {
        @Override
        public void run() {
            myService.runMethod();
        }
    }).start();
    ThreadUtils.sleep(500);
    new Thread(new Runnable() {
        @Override
        public void run() {
            myService.setContinueRun(false);
        }
    }).start();
    ThreadUtils.sleep(500);
    System.out.println(Thread.currentThread().getName()+" isContinueRun: "+myService.isContinueRun()+"...  "+new Date());
}

示例输出如下:

Thread-1 set continueRun is false...Sat Jun 13 10:40:26 CST 2020
main isContinueRun: false...  Sat Jun 13 10:40:27 CST 2020

程序并未执行完成。

原因很简单,每个线程都有自己的栈空间,它会把共享内存中的数据拷贝一份存放到自己的栈空间,因此当第二个线程 修改 共享内存中的isContinueRun时,只会修改自己栈及共享内存中的数据,并不会涉及到第一个线程栈中的数据。

要解决这个问题很简单,通常我们只需要将isContinueRun 添加要给关键字volatile 即可,该关键字修饰的变量具有线程可见性。

其实,我们还有另外一种解决方法,修改MyService中的runMethod,具体如下:

public void runMethod(){
    while (isContinueRun){
        synchronized (this){}         
    }
    System.out.println(Thread.currentThread().getName()+" is stop ..."+new Date());
}

或者

public void runMethod(){
    String str = new String();
    while (isContinueRun){
        synchronized (str){}       
    }
    System.out.println(Thread.currentThread().getName()+" is stop ..."+new Date());
}

运行结果如下:

Thread-1 set continueRun is false...Sat Jun 13 11:13:41 CST 2020
Thread-0 is stop ...Sat Jun 13 11:13:41 CST 2020
main isContinueRun: false...  Sat Jun 13 11:13:41 CST 2020

我们可以看到 第一个线程能够正常退出。

总结:synchronized 不仅可以解决同一时刻,只有一个线程执行该同步代码,还可以解决一个线程看到对象处于不一致的状态,保证进入同步方法或同步代码块之前的每个线程 修改的变量的效果。

 

参考文献

《Java多线程编程核心技术》

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值