文档章节

Java多线程6:Synchronized锁代码块(this和任意对象)

o
 osc_y8yehimr
发布于 2019/03/20 19:30
字数 2648
阅读 3
收藏 0

精选30+云产品,助力企业轻松上云!>>>

一、Synchronized(this)锁代码块

   用关键字synchronized修饰方法在有些情况下是有弊端的,若是执行该方法所需的时间比较长,线程1执行该方法的时候,线程2就必须等待。这种情况下就可以使用synchronized同步该方法中会引起线程安全的那部分代码,其余不会引起线程安全的就不需要同步,这部分代码就可以多线程并发执行,减少时间提高效率。

  举例:多线程执行同一个方法时,同步方法和同步代码块花费时间的比较

  1、synchronized修饰方法(同步方法)

  synchronized修饰longTimeTask方法,其中花费时间比较长的且与线程安全无关的是37-39行代码,会引起线程安全问题的是42-46。

 1 public class ThreadSynch {
 2 
 3     private int num;
 4 
 5     public synchronized void longTimeTask(String userName){
 6         //定义各线程的进入时间
 7         long thread0StartTime = 0L;
 8         long thread1StartTime = 0L;
 9         long thread2StartTime = 0L;
10         long thread3StartTime = 0L;
11         long thread4StartTime = 0L;
12         //定义各线程执行该方法所需的时间
13         long thread0LastTime;
14         long thread1LastTime;
15         long thread2LastTime;
16         long thread3LastTime;
17         long thread4LastTime;
18         //显示各线程进入的时间
19         if(Thread.currentThread().getName().contains("-0")){
20             thread0StartTime = System.currentTimeMillis();
21             System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread0StartTime);
22         }else if(Thread.currentThread().getName().contains("-1")){
23             thread1StartTime = System.currentTimeMillis();
24             System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread1StartTime);
25         }else if(Thread.currentThread().getName().contains("-2")){
26             thread2StartTime = System.currentTimeMillis();
27             System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread2StartTime);
28         }else if(Thread.currentThread().getName().contains("-3")){
29             thread3StartTime = System.currentTimeMillis();
30             System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread3StartTime);
31         }else if(Thread.currentThread().getName().contains("-4")){
32             thread4StartTime = System.currentTimeMillis();
33             System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread4StartTime);
34         }
35 
36         //花费时间较长,与线程安全无关的代码
37         for(int i = 200000000; i > 0; i--) {
38             String nameID = Thread.currentThread().getName() + Thread.currentThread().getId();
39         }
40 
41         //与线程安全相关的代码块
42         if("zs".equals(userName)){
43             num = 100;
44         }else if("ls".equals(userName)){
45             num = 200;
46         }
47 
48         //显示各线程执行该方法的时间
49         if(Thread.currentThread().getName().contains("0")){
50             thread0LastTime = System.currentTimeMillis() - thread0StartTime;
51             System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread0LastTime + "ms");
52         }else if(Thread.currentThread().getName().contains("1")){
53             thread1LastTime = System.currentTimeMillis() - thread1StartTime;
54             System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread1LastTime + "ms");
55         }else if(Thread.currentThread().getName().contains("2")){
56             thread2LastTime = System.currentTimeMillis() - thread2StartTime;
57             System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread2LastTime + "ms");
58         }else if(Thread.currentThread().getName().contains("3")){
59             thread3LastTime = System.currentTimeMillis() - thread3StartTime;
60             System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread3LastTime + "ms");
61         }else if(Thread.currentThread().getName().contains("4")){
62             thread4LastTime = System.currentTimeMillis() - thread4StartTime;
63             System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread4LastTime + "ms");
64         }
65 
66     }
67 }

  继承Thread的Thread01类,其run方法调用上述对象的longTimeTask方法

public class Thread01 extends Thread{
    private ThreadSynch threadSynch;

    public Thread01(ThreadSynch threadSynch) {
        this.threadSynch = threadSynch;
    }

    @Override
    public void run() {
        threadSynch.longTimeTask("ls");
    }
}

  测试,构建同一对象的多个线程

public class Test {
    public static void main(String[] args) {
        ThreadSynch threadSynch = new ThreadSynch();
        //五个线程使用同一个对象构建
        Thread thread01 = new Thread01(threadSynch);
        Thread thread02 = new Thread01(threadSynch);
        Thread thread03 = new Thread01(threadSynch);
        Thread thread04 = new Thread01(threadSynch);
        Thread thread05 = new Thread01(threadSynch);
        //五个线程同时调用该对象中的方法
        thread01.start();
        thread02.start();
        thread03.start();
        thread04.start();
        thread05.start();
    }
}

  结果:

Thread-0进入时间为====1553150692703
Thread-0执行时间为===8437ms
Thread-3进入时间为====1553150701140
Thread-3执行时间为===7014ms
Thread-1进入时间为====1553150708154
Thread-1执行时间为===7002ms
Thread-4进入时间为====1553150715157
Thread-4执行时间为===7121ms
Thread-2进入时间为====1553150722278
Thread-2执行时间为===7147ms

  说明:因为synchronized修饰的是整个方法,所以线程Thread-0访问longTimeTask方法的时候,其余四个线程都处于阻塞状态,待其执行结束释放锁的时候,线程Thread-3开始执行,其余三个线程还是处于阻塞状态,所以,这五个线程执行完毕所需的时间是各自执行时间的相加,8.4 + 7.0 + 7.0 + 7.1 + 7.1 = 36.6s。

  2、synchronized修饰代码块(同步代码块)

  synchronized由同步方法改为同步方法中引起线程安全问题的代码块,其余都不变

public class ThreadSynch {

    private int num;

    public void longTimeTask(String userName){
        long thread0StartTime = 0L;
        long thread1StartTime = 0L;
        long thread2StartTime = 0L;
        long thread3StartTime = 0L;
        long thread4StartTime = 0L;
        long thread0LastTime;
        long thread1LastTime;
        long thread2LastTime;
        long thread3LastTime;
        long thread4LastTime;
        if(Thread.currentThread().getName().contains("-0")){
            thread0StartTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread0StartTime);
        }else if(Thread.currentThread().getName().contains("-1")){
            thread1StartTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread1StartTime);
        }else if(Thread.currentThread().getName().contains("-2")){
            thread2StartTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread2StartTime);
        }else if(Thread.currentThread().getName().contains("-3")){
            thread3StartTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread3StartTime);
        }else if(Thread.currentThread().getName().contains("-4")){
            thread4StartTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "进入时间为====" + thread4StartTime);
        }

        //花费时间较长,与线程安全无关的代码
        for(int i = 200000000; i > 0; i--) {
            String nameID = Thread.currentThread().getName() + Thread.currentThread().getId();
        }

        //与线程安全相关的代码块用synchronized修饰
        synchronized(this){
            if("zs".equals(userName)){
                num = 100;
            }else if("ls".equals(userName)){
                num = 200;
            }
        }

        if(Thread.currentThread().getName().contains("0")){
            thread0LastTime = System.currentTimeMillis() - thread0StartTime;
            System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread0LastTime + "ms");
        }else if(Thread.currentThread().getName().contains("1")){
            thread1LastTime = System.currentTimeMillis() - thread1StartTime;
            System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread1LastTime + "ms");
        }else if(Thread.currentThread().getName().contains("2")){
            thread2LastTime = System.currentTimeMillis() - thread2StartTime;
            System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread2LastTime + "ms");
        }else if(Thread.currentThread().getName().contains("3")){
            thread3LastTime = System.currentTimeMillis() - thread3StartTime;
            System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread3LastTime + "ms");
        }else if(Thread.currentThread().getName().contains("4")){
            thread4LastTime = System.currentTimeMillis() - thread4StartTime;
            System.out.println(Thread.currentThread().getName() + "执行时间为===" + thread4LastTime + "ms");
        }

    }
}

  同样的五个线程访问,看一下结果:

Thread-0进入时间为====1553151204348
Thread-3进入时间为====1553151204348
Thread-1进入时间为====1553151204348
Thread-2进入时间为====1553151204348
Thread-4进入时间为====1553151204380
Thread-3执行时间为===19330ms
Thread-2执行时间为===19383ms
Thread-1执行时间为===19854ms
Thread-4执行时间为===20498ms
Thread-0执行时间为===20782ms

  说明:因为synchronized修饰的是方法中会引起线程安全问题的代码块,所以仅仅是这一部分代码无法并发执行。可以看到Thread-0,Thread-1,Thread-2,Thread-3,Thread-4几乎同时进入longTimeTask方法,并发执行for循环中花费时间较长的代码,由结果看,Thread-3最先执行完这部分代码,开始执行synchronized修饰的代码块,其余四个线程随后进入阻塞状态。因为同步代码块中执行时间较短,Thread-3执行完后,Thread-2开始执行,最后是Thread-0执行,至此,五个线程执行完毕,所花费的时间就是Thread-0花费的时间,即20.8s。

  可以看到,在longTimeTask方法中,synchronized由修饰方法改为修饰代码块,多线程执行所花费的时间由36.6s变成20.8s,执行时间明显减少,效率提升。

 二、任意对象作为对象监视器

  2.1 上述同步代码块使用的是synchronized(this)格式,其实Java还支持对“任意对象”作为对象监视器来实现同步的功能。这种任意对象大多是该方法所属类中的实例变量或该方法的参数,不然抛开这个类去使用别的对象作为对象监视器,意义不大。使用的格式是synchronized(非this的任意对象)。

  举例:以ThreadSynch类中的变量student作为对象监视器去同步代码块

public class ThreadSynch {

    private Student student = new Student();
    private String schoolName;

    public void setNameAndPassWord(String name,String age){
        synchronized(student){
            System.out.println(Thread.currentThread().getName() + "===" + "进入同步代码块");
            try {
                Thread.sleep(3000);
                this.student.setName(name);
                this.student.setAge(age);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "===" + "离开同步代码块");
        }
    }
}

  Thread01的run方法调用setNameAndPassWord方法

public class Thread01 extends Thread{
    private ThreadSynch threadSynch;

    public Thread01(ThreadSynch threadSynch) {
        this.threadSynch = threadSynch;
    }

    @Override
    public void run() {
        threadSynch.setNameAndPassWord("ls","11");
    }
}

  测试:

public class Test {
    public static void main(String[] args) {
        ThreadSynch threadSynch = new ThreadSynch();
        //三个线程使用同一个对象构建
        Thread thread01 = new Thread01(threadSynch);
        Thread thread02 = new Thread01(threadSynch);
        Thread thread03 = new Thread01(threadSynch);
        //三个线程同时调用该对象中的方法
        thread01.start();
        thread02.start();
        thread03.start();
    }
}

  结果:

Thread-1===进入同步代码块
Thread-1===离开同步代码块
Thread-2===进入同步代码块
Thread-2===离开同步代码块
Thread-0===进入同步代码块
Thread-0===离开同步代码块

  说明:Thread-0,Thread-1,Thread-2执行到同步代码块synchronized(student)时,都会去获取与student对象关联的monitor,判断该monitor是否被别的线程所有,因为三个线程中的student都是同一个对象,所以一个线程执行的时候,与student关联的那个monitor会被当前线程所有,别的线程都会处于阻塞状态。

  稍微改一下ThreadSynch类中setNameAndPassWord的方法,添加7-9行的代码

 1 public class ThreadSynch {
 2 
 3     private Student student = new Student();
 4     private String schoolName;
 5 
 6     public void setNameAndPassWord(String name,String age){
 7         if(Thread.currentThread().getName().contains("1")){
 8             student = new Student();
 9         }
10         synchronized(student){
11             System.out.println(Thread.currentThread().getName() + "===" + "进入同步代码块");
12             try {
13                 Thread.sleep(3000);
14                 this.student.setName(name);
15                 this.student.setAge(age);
16             } catch (InterruptedException e) {
17                 e.printStackTrace();
18             }
19             System.out.println(Thread.currentThread().getName() + "===" + "离开同步代码块");
20         }
21     }
22 }

  其余都不变,看一下结果:

Thread-0===进入同步代码块
Thread-1===进入同步代码块
Thread-0===离开同步代码块
Thread-1===离开同步代码块
Thread-2===进入同步代码块
Thread-2===离开同步代码块

  说明:可以看到,Thread-0和Thread-1同时进入同步代码块。分析一下原因,Thread-0执行到synchronized(student)时,会去获取与该student对象关联的monitor的所有权,该monitor没有被别的线程占有,Thread-0进入同步代码块中。Thread-1执行setNameAndPassWord方法的时候,新添加的7-9行的代码将student变量指向了一个新的student对象,此时的student对象和Thread-0时的student对象已经不是同一个了,对应的monitor也不是Thread-0时的那个monitor,所以Thread-1在Thread-0还未离开同步代码块的时候,也可以进入到同步代码块中执行。但Thread-2执行同步代码块时的student还是Thread-1时的那个student,所以Thread-2只能等到Thread-1执行结束,才能进入同步代码块中。

  所以,多个线程访问同步代码块时,只要synchronized(this对象/非this对象)中的对象是同一个对象,那么同一时间只能有一个线程可以执行同步代码块中的内容。这里注意一下当任意对象是string类型时,使用不当可能会有一些麻烦。具体就是以下两个例子:

public class Test {
    public static void main(String[] args) {
        String str1 = "111";
        String str2 = "111";
        System.out.println(str1 == str2);

        String str3 = new String("222");
        String str4 = new String("222");
        System.out.println(str3 == str4);

    }
}

  结果:

true
false

  多线程并发执行时,当synchronized(str1)由str1变成str2时,其余线程是否还会处于阻塞状态(会)。

  多线程并发执行时,当synchronized(str3)由str3变成str4时,其余线程是否还会处于阻塞状态(不会)。

  具体的string常量与new String对象的区别,参见这篇文章从为什么String=String谈到StringBuilder和StringBuffer

 

参考资料:

Java多线程5:synchronized锁方法块

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
面试中关于synchronized关键字

一、 说说对synchronized关键字的了解 synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或代码块在任意时刻只能有一个线程执行。 另外,...

Bb进阶
2019/08/13
21
0
Java多线程之synchronized关键词(Demo详解)

你好我是辰兮,很高兴你能来阅读,本篇文章为大家讲解Java多线程之synchronized关键词,下面有案例的截图和相关代码可以自行实践,相关的更多面试知识已经提前整理好文章可以阅读学习,分享获...

辰兮要努力
06/01
0
0
对synchronized的一点理解

一、synchronized的使用 (一)、synchronized同步方法 1. “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题。 2. 如果多个线程共同访问1个对象...

osc_yfquc6et
2018/05/11
3
0
Java基础-多线程-③线程同步之synchronized

Java基础-多线程-③线程同步之synchronized 使用线程同步解决多线程安全问题   上一篇 Java基础-多线程-②多线程的安全问题 中我们说到多线程可能引发的安全问题,原因在于多个线程共享了数...

皇太极
2015/08/17
3
0
Java线程和进程相关面试题与答案总结

有几天没有写一写博客了,今天就带给大家一些面试题和参考答案吧! 这些都是上海尚学堂Java培训的学员去面试时遇到的问题,今天总结出来的是Java线程相关类的面试题。把参考答案和解析也发布...

osc_j0zz20y1
2018/07/04
7
0

没有更多内容

加载失败,请刷新页面

加载更多

es集群笔记

es 集群的默认配置是当集群中的某个节点磁盘达到使用率为 85% 的时候, 就不会在该节点进行创建副本, 当磁盘使用率达到 90% 的时候, 尝试将该节点的副本重分配到其他节点. 当磁盘使用率达到 ...

gaolongquan
11分钟前
4
0
VS Code编写Vue过程中出现空格不规范报错的问题

报错内容: 解决办法: 1.注释或删除这些代码 注释掉之后(重启vue服务),再进行编写的时候,空格不规范的情况下就不会再报错了。 2.如果没在webpack.dev.conf.js文件中找到注释代码就在webpack...

安然_oschina
13分钟前
9
0
域名防封_域名防红_微信域名防拦截

最近微信开始大封杀,不知道原因是什么,可能是因为违规网站太多了吧,很多网站都被错杀了,下面我们聊一下怎样才能避免域名被封杀呢。 在各种不同的域名当中,能够做出了更适合的选择,这些...

戚馨逸
17分钟前
13
0
数据结构(六)——循环链表

一、循序链表简介 1、循环链表的定义 循环链表的任意元素都有一个前驱和一个后继,所有数据元素在关系上构成逻辑上的环。 循环链表是一种特殊的单链表,尾结点的指针指向首结点的地址。 循环...

rainbowcode
21分钟前
13
0
六边形寻路格子绘制

using System.Collections;using System.Collections.Generic;using UnityEngine;public class AdventureIsland : MonoBehaviour{ static AdventureIsland instante; pu......

江湖令
22分钟前
15
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部