多线程编程之线程的同步机制(下): Synchronized同步代码块

282 阅读6分钟

小知识,大挑战!本文正在参与“   程序员必备小知识   ”创作活动

作者的其他平台:

| CSDN:blog.csdn.net/qq_4115394…

| 掘金:juejin.cn/user/651387…

| 知乎:www.zhihu.com/people/1024…

| GitHub:github.com/JiangXia-10…

| 公众号:1024笔记

本文大概15558字,读完共需35分钟

1 前言

上一篇文章讲了多线程编程中Synchronized同步方法的相关内容,Synchronized除了同步方法之外还可以同步语句块,这篇文章就介绍Synchronized如何同步语句块。

2 正文

1、Synchronized同步方法的缺点

在介绍Synchronized同步语句块之前,先来说说Synchronized同步方法的缺点。先看代码:

public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Demo9Service service = new Demo9Service();
        Thread t1 = new Demo9ThreadA(service);
        t1.setName("A");

        Thread t2 = new Demo9ThreadA(service);
        t2.setName("B");

        t1.start();
        t2.start();

        Thread.sleep(20000);

        long start = Demo9Untils.start1 > Demo9Untils.start2 ? Demo9Untils.start2 : Demo9Untils.start1;
        long end = Demo9Untils.end1 > Demo9Untils.end2 ? Demo9Untils.end1 : Demo9Untils.end2;
        System.out.println("总耗时:" + (end - start) / 1000 + "秒");

    }
}

class Demo9Untils{
    static long start1;
    static long start2;

    static long end1;
    static long end2;
}

class Demo9Service{
    synchronized public void foo(){
        try{
            System.out.println("开始任务");
            Thread.sleep(5000);
            System.out.println("长时任务处理完成,线程" + Thread.currentThread().getName());
            System.out.println("结束任务");
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

class Demo9ThreadA extends Thread{
    public Demo9Service service;

    public Demo9ThreadA(Demo9Service service){
        this.service = service;
    }

    @Override
    public void run() {
        Demo9Untils.start1 = System.currentTimeMillis();
        service.foo();
        Demo9Untils.end1 = System.currentTimeMillis();
    }
}

class Demo9ThreadB extends Thread{
    public Demo9Service service;

    public Demo9ThreadB(Demo9Service service){
        this.service = service;
    }

    @Override
    public void run() {
        Demo9Untils.start2 = System.currentTimeMillis();
        service.foo();
        Demo9Untils.end2 = System.currentTimeMillis();
    }
}

结果:

图片

2、Synchronized同步代码块

再来看看synchronized同步代码块的代码:

public class Demo10 {
    public static void main(String[] args) {
        Demo10Service service = new Demo10Service();
        Thread t1 = new Demo10Thread(service);
        t1.setName("A");
        t1.start();
        Thread t2 = new Demo10Thread(service);
        t2.setName("B");
        t2.start();
    }
}
class Demo10Service{
    public void foo(){
        try {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + "开始于" + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "结束于" + System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Demo10Thread extends Thread{
    private Demo10Service service;
    public Demo10Thread(Demo10Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.foo();
    }
}

结果:

图片

通过上面的示例可以发现Synchronized同步方法时间耗费过长。

3、使用同步代码块解决同块方法的问题

那么如何使用同步代码块解决同步方法的问题,代码如下:

public class Demo11 {
    public static void main(String[] args) throws InterruptedException {
        Demo11Service service = new Demo11Service();
        Thread t1 = new Demo11ThreadA(service);
        t1.setName("A");
        t1.start();

        Thread t2 = new Demo11ThreadB(service);
        t2.setName("B");
        t2.start();

        Thread.sleep(10000);
        long start = Demo11Utils.start1 > Demo11Utils.start2 ? Demo11Utils.start2 : Demo11Utils.start1;
        long end = Demo11Utils.end1 > Demo11Utils.end2 ? Demo11Utils.end1 : Demo11Utils.end2;
        System.out.println("耗时:" + (end - start) /1000 + "秒");

    }
}

class Demo11Utils{
    static long start1;
    static long start2;
    static long end1;
    static long end2;
}

class Demo11Service{
    /*synchronized*/ public void foo(){
        try {
            System.out.println(Thread.currentThread().getName() + "开始任务");
            Thread.sleep(3000);
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + "处理计算结果");
            }
            System.out.println(Thread.currentThread().getName() + "结束任务");
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

class Demo11ThreadA extends Thread{
    private Demo11Service service;
    public Demo11ThreadA(Demo11Service service){
        this.service = service;
    }

    @Override
    public void run() {
        Demo11Utils.start1 = System.currentTimeMillis();
        service.foo();
        Demo11Utils.end1 = System.currentTimeMillis();
    }
}

class Demo11ThreadB extends Thread{
    private Demo11Service service;
    public Demo11ThreadB(Demo11Service service){
        this.service = service;
    }

    @Override
    public void run() {
        Demo11Utils.start2 = System.currentTimeMillis();
        service.foo();
        Demo11Utils.end2 = System.currentTimeMillis();
    }
}

结果:

图片

可见耗时明显缩短!

4、synchronized代码块间的同步

/**
 * synchronized代码块的同步
 */
public class Demo13 {
    public static void main(String[] args) throws InterruptedException {
        DemoService13 service13 = new DemoService13();
        Thread t1 = new Demo13Thread1(service13);
        t1.start();
        Thread.sleep(100);
        Thread t2 = new Demo13Thread2(service13);
        t2.start();
    }
}

//声明一个服务
class DemoService13{
    public void foo1(){
        //同步代码块
        synchronized (this){
            //睡眠2s
            try {
                System.out.println("foo1方法开始时间:"+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("foo1方法结束时间:"+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void foo2(){
        synchronized (this){
            System.out.println("foo2方法开始时间: "+System.currentTimeMillis());
            System.out.println("foo2方法结束时间: "+System.currentTimeMillis());
        }
    }
}

//声明一个子线程
class Demo13Thread1 extends Thread{
    private DemoService13 service13;

    //构造方法
    public Demo13Thread1(DemoService13 service){
        this.service13 = service;
    }

    public void run(){
        service13.foo1();
    }

}

//再声明一个子线程
class Demo13Thread2 extends Thread{
    private DemoService13 service13;

    //构造方法
    public Demo13Thread2(DemoService13 service){
        this.service13 = service;
    }

    public void run(){
        service13.foo2();
    }

}

结果:

图片

使用同步synchronize(this)代码时需要注意,当一个线程访问object的一个synchronized(this)同步代码块时,其它线程对这个object的其它syncrhonized(this)同步的访问会被阻塞,说明synchronized使用的对象锁是同一个。

synchroinze(this)代码块锁定的是当前对象。比如:

/**
 *synchroinze(this)代码块是锁定当前对象
 */
public class Demo14 {
    public static void main(String[] args) throws InterruptedException {
        Demo14Service service = new Demo14Service();
        Thread t1 = new Demo14ThreadA(service);
        Thread t2 = new Demo14ThreadB(service);
        t2.start();
        Thread.sleep(10);
        t1.start();
    }
}

class Demo14Service{
    synchronized public void foo1(){
        System.out.println("foo1方法正在运行");
    }

    public void foo2(){
        try {
            synchronized (this) {
                System.out.println("foo2方法开始");
                Thread.sleep(2000);
                System.out.println("foo2方法结束");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class Demo14ThreadA extends Thread{
    private Demo14Service service;
    public Demo14ThreadA(Demo14Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.foo1();
    }
}

class Demo14ThreadB extends Thread{
    private Demo14Service service;
    public Demo14ThreadB(Demo14Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.foo2();
    }
}

结果:

图片

synchronized(this)代码块与synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。

5、使用任意对象作为对象锁

除了可以使用syncrhonized(this)来同步代码块,java还支持任意对象作为对象锁来实现同步的功能。这个任意对象就是成员变量或者方法中的参数,语法格式为:

synchronized(lockobj)

在多个线程持有的对象锁为同一个对象的情况下,同一时间只有一个线程可以执行synchoronized(lockobj)同步代码块中的代码。如果使用的不是同一个对象锁,运行的结果就是异步调用,交叉输出结果。

比如:

/**
 * 使用任意对象作为对象锁
 */
public class Demo15 {
    public static void main(String[] args) {
        Demo15Service service = new Demo15Service();
        Thread t1 = new Demo15Thread(service);
        t1.setName("线程1");
        t1.start();
        Thread t2 = new Demo15Thread(service);
        t2.setName("线程2");
        t2.start();
    }
}

class Demo15Service{
    //锁对象
    private Object lockobject = new Object();

    public void foo(){
        //synchronized锁定对象
        synchronized (lockobject){
            try {
                System.out.println(Thread.currentThread().getName() + "开始于" + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "结束于" + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Demo15Thread extends Thread{
    private Demo15Service service;
    public Demo15Thread(Demo15Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.foo();
    }
}

结果:

图片

与synchronized (this)相比非tihs对象具有一定的优点。

一个类有很多个synchrnoized方法,虽然可以实现同步,但是会受到阻塞,影响运行效率。如果使用同步代码块非this对象,则synchronized非this对象的代码块中的程序与同步方法是不与其它的锁(this)争抢this锁,大大提高运行的效率。

/**
 * 非this对象具有的优点
 */

public class Demo16 {
    public static void main(String[] args) throws InterruptedException {
        Demo16Service service = new Demo16Service();
        Thread t1 = new Demo16ThreadA(service);
        t1.start();
        Thread.sleep(10);
        Thread t2 = new Demo16ThreadB(service);
        t2.start();
    }
}

class Demo16Service{
    private Object lockObject = new Object();
    public void foo(){
        try {
            synchronized (lockObject) {
                System.out.println("foo方法开始时间" + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("foot方法结束时间" + System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    synchronized public void foo2(){
        System.out.println("foo2方法开始时间" + System.currentTimeMillis());
        System.out.println("foo2方法结束时间" + System.currentTimeMillis());
    }
}

class Demo16ThreadA extends Thread{
    private Demo16Service service;
    public Demo16ThreadA(Demo16Service service){
        this.service  =service;
    }

    @Override
    public void run() {
        service.foo();
    }
}

class Demo16ThreadB extends Thread{
    private Demo16Service service;
    public Demo16ThreadB(Demo16Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.foo2();
    }
}

结果:

图片

5、synchronized(非this对象)解决脏读

import java.util.ArrayList;
import java.util.List;
public class Demo17 {
    public static void main(String[] args) throws InterruptedException {
        Demo17List list = new Demo17List();
        Thread t1 = new Demo17ThreadA(list);
        t1.start();
        Thread t2 = new Demo17ThreadB(list);
        t2.start();
        Thread.sleep(5000);
        System.out.println("list size is " + list.size());
    }
}

class Demo17List{
    private List list = new ArrayList();
    synchronized public void add(Object obj){
        list.add(obj);
    }
    synchronized public int size(){
        return list.size();
    }
}

class Demo17Service{
    private Object lockObject = new Object();
    public void add(Demo17List list, Object obj){
        try {
            synchronized (list) {
                if (list.size() < 1) {
                    Thread.sleep(2000);
                    list.add(obj);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class Demo17ThreadA extends Thread{
    private Demo17List list;
    public Demo17ThreadA(Demo17List list){
        this.list = list;
    }

    @Override
    public void run() {
        Demo17Service service = new Demo17Service();
        service.add(list, "a");
    }
}

class Demo17ThreadB extends Thread{
    private Demo17List list;
    public Demo17ThreadB(Demo17List list){
        this.list = list;
    }

    @Override
    public void run() {
        Demo17Service service = new Demo17Service();
        service.add(list, "b");
    }
}

结果:

图片

对于synchronized(lockobj)格式的写法是将lockobj本身作为对象锁,总结上面的结果这样得出以下的结论:

a. 当多个线程同时执行syncrhonized(lockobj)同步代码块时结果是同步效果;

b. 当其它线程执行lockobj对象中的synchronized同步方法时也是同步效果;

c. 当其它线程执行lockobj对象方法里的synchronize(this)代码块时也是同步效果

如果其它线程调用不加synchronized关键字的方法时,还是异步调用。

6、半异步半同步

在并发模式中的同步和异步与IO模型中的同步和异步是完全不同的概念。

在IO模型中,同步和异步区分的是内核向应用程序通知的是何种IO事件(是就绪事件还是完成事件),以及该由谁来完成IO读写(是应用程序还是内核)。

而在并发模式中的同步指的是程序完全按照代码序列的顺序执行,异步指的是程序的执行需要由系统事件来驱动。常见的系统事件包括中断信号等。

按照同步方式运行的线程称为同步线程,按照异步方式运行的线程成为异步线程。显然异步线程的执行效率高,实时性强,这是很多嵌入式程序采用的模型。但编写异步方式执行的程序相对复杂,难于调试和扩展,且不适合大量的并发。而同步线程则相反,它虽然效率比较低,实时性较差,但逻辑简单。因此,对于某些既要求较好的实时性,又要求同时处理多个客户请求的应用程序,就可以同时使用同步线程和异步线程来实现。即使用半同步/半异步模式来实现!

如果使用synchronized关键字来实现半同步/半异步代码如下:

/**
 * 半同步和半异步
 */
public class Demo12 {
    public static void main(String[] args) {
        Demo12Service service = new Demo12Service();
        Thread t1 = new Demo12Thread(service);
        t1.setName("A");
        t1.start();

        Thread t2 = new Demo12Thread(service);
        t2.setName("B");
        t2.start();
    }
}

class Demo12Service {
    public void foo(){
        try{
            for (int i = 0; i < 100; i++) {
                System.out.println("非同步线程" + Thread.currentThread().getName() + ", i=" + i);
                Thread.sleep(10);
            }
            System.out.println();
            synchronized (this){
                for (int i = 0; i < 100; i++) {
                    System.out.println("同步线程" + Thread.currentThread().getName() + ", i=" + i);
                    Thread.sleep(10);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class Demo12Thread extends Thread{
    private Demo12Service service;
    public Demo12Thread(Demo12Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.foo();
    }
}

结果:

图片

不在synchronized块中的就是异步执行,在synchroinzed块中的代码就是同步执行。

非同步的输出是交叉的,而同步线程只有线程a执行完了才执行线程B。

7、synchronized同步静态方法与synchronized(class)代码块

前面讲了synchronized关键字修饰方法,但是并没有讲修饰静态方法,关键字synchronized还可以修饰静态方法,synchronized修饰静态方法就是对象当前的*.java文件对应的Class进行加锁。比如:

public class Demo18 {
    public static void main(String[] args) {
        Thread t1 = new Demo18ThreadA();
        t1.setName("A");
        t1.start();

        Thread t2 = new Demo18ThreadB();
        t2.setName("B");
        t2.start();
    }
}

class Demo18Service{
    synchronized public static void foo1(){
        System.out.println(Thread.currentThread().getName() + "进入方法foo1在" + System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束方法foo1在" + System.currentTimeMillis());
    }
    synchronized public static void foo2(){
        System.out.println(Thread.currentThread().getName() + "进入方法foo2在" + System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束方法foo2在" + System.currentTimeMillis());
    }
}

class Demo18ThreadA extends Thread{
    @Override
    public void run() {
        Demo18Service.foo1();
    }
}

class Demo18ThreadB extends Thread{
    @Override
    public void run() {
        Demo18Service.foo2();
    }
}

结果:

图片

从上面的结果可以发现在静态方法上使用synchronized修饰与在非静态方法上使用的结果是一致的,都是同步运行。但是其实从本质来讲它们是有区别的,在静态方法上使用synchronized是给Class类上锁,而在非静态方法上使用syncrhonized是给对象上锁。

public class Demo19 {
    public static void main(String[] args) {
        Demo19Service service = new Demo19Service();
        Thread t1 = new Demo19ThreadA(service);
        t1.setName("A");
        t1.start();
        Thread t2 = new Demo19ThreadB(service);
        t2.setName("B");
        t2.start();
    }
}

class Demo19Service{
    synchronized public static void foo1(){
        System.out.println(Thread.currentThread().getName() + "进入foo1方法在" + System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束foo1方法在" + System.currentTimeMillis());
    }

    synchronized public void foo2(){
        System.out.println(Thread.currentThread().getName() + "进入foo2方法在" + System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束foo2方法在" + System.currentTimeMillis());
    }
}

class Demo19ThreadA extends Thread{
    private Demo19Service service;
    public Demo19ThreadA(Demo19Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.foo2();
    }
}

class Demo19ThreadB extends Thread{
    private Demo19Service service;
    public Demo19ThreadB(Demo19Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.foo1();
    }
}

结果:

图片

上面以异步方式运行的原因是因为持有锁是不一样的,非静态方法持有提对象锁,而静态方法持有的是Class锁,Class锁可以对类的所有对象实例起作用。

同步synchronized(class)代码块的作用其实和synchronized static方法的作用是一样的

public class Demo20 {
    public static void main(String[] args) {
        Demo20Service service = new Demo20Service();
        Thread t1 = new Demo20ThreadA(service);
        t1.setName("A");
        t1.start();

        Thread t2 = new Demo20ThreadA(service);
        t2.setName("B");
        t2.start();
    }
}

class Demo20Service{
    synchronized public static void foo1(){
        System.out.println(Thread.currentThread().getName() + "进入foo1方法在" + System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "结束foo1方法在" + System.currentTimeMillis());
    }
    public static void foo2(){
        synchronized (Demo20Service.class) {
            System.out.println(Thread.currentThread().getName() + "进入foo1方法在" + System.currentTimeMillis());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束foo1方法在" + System.currentTimeMillis());
        }
    }
}

class Demo20ThreadA extends Thread{
    private Demo20Service service;
    public Demo20ThreadA(Demo20Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.foo1();
    }
}

class Demo20ThreadB extends Thread{
    private Demo20Service service;
    public Demo20ThreadB(Demo20Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.foo2();
    }
}

结果:

图片

8、synchronized(String)*

当使用synchronized关键字将String作为锁对象时:

public class Demo21 {
    public static void main(String[] args) {
        Thread t1 = new Demo21ThreadA();
        t1.setName("A");
        t1.start();

        Thread t2 = new Demo21ThreadB();
        t2.setName("B");
        t2.start();
    }
}

class Demo21Service{
    public static void foo1(String lockObject){
        try {
            synchronized (lockObject) {
                while (true) {
                    System.out.println("线程" + Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public static void foo2(Object lockObject){
        try {
            synchronized (lockObject) {
                while (true) {
                    System.out.println("线程" + Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class Demo21ThreadA extends Thread{
    @Override
    public void run() {
        Demo21Service.foo1("AA");
    }
}

class Demo21ThreadB extends Thread{
    @Override
    public void run() {
        Demo21Service.foo2("AA");
    }
}

结果:

图片

通过上面结果可以发现只会打印线程B。只有线程B在运行的原因是,两个线程的对象锁都是使用AA,两个线程持有的相同的锁,所以造成线程B能运行。这就是String常量所带来的问题。所以不应该使用字符串类型作为对象锁,而应该使用其它类型,例如new Object(),对象就不会放入缓存 。

3 总结

多线程编程之线程的同步机制(上): Synchronized同步方法和本篇文章主要讲了在多线程编程中Synchronized关键字对于同步机制的作用。Synchronized关键字不仅仅能够修饰方法还能够修饰代码块。

如果觉得这篇文章不错对你有帮助,就点赞分享给更多的人吧!

相关推荐: