一、Synchronized(this)锁代码块

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

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

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

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

 public class ThreadSynch {

     private int num;

     public synchronized 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();
} //与线程安全相关的代码块
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的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行的代码

 public class ThreadSynch {

     private Student student = new Student();
private String schoolName; public void setNameAndPassWord(String name,String age){
if(Thread.currentThread().getName().contains("1")){
student = new Student();
}
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() + "===" + "离开同步代码块");
}
}
}

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

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锁方法块

Java多线程6:Synchronized锁代码块(this和任意对象)的更多相关文章

  1. java 多线程9 : synchronized锁机制 之 代码块锁

    synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...

  2. java 多线程: Thread 并发访问-代码块同步synchronized {};String作为被锁的对象

    方法同步的弊端 方法同步的时候,如果一个方法需要线程安全控制的代码速度其实很快,但是还有其他的业务逻辑代码耗时非常长(比如网络请求),这样所有的线程就在这一块就等待着了,这样造成了极大的资源浪费如果并 ...

  3. synchronized锁代码块(七)

    synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...

  4. java中的synchronized同步代码块和同步方法的区别

    下面这两段代码有什么区别? //下列两个方法有什么区别 public synchronized void method1(){} public void method2(){ synchronized ...

  5. java 多线程8 : synchronized锁机制 之 方法锁

    脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数 ...

  6. Java多线程学习——synchronized锁机制

    Java在多线程中使用同步锁机制时,一定要注意锁对对象,下面的例子就是没锁对对象(每个线程使用一个被锁住的对象时,得先看该对象的被锁住部分是否有人在使用) 例子:两个人操作同一个银行账户,丈夫在ATM ...

  7. Java多线程5:synchronized锁方法块

    synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用 ...

  8. 深入理解Java并发synchronized同步化的代码块不是this对象时的操作

    本文仅仅是为了说明synchronized关键字同步的是对象不是方法,列子的确有失偏颇. 一.明确一点synchronized同步的是对象不是方法也不是代码块  我有关synchronized同步的是 ...

  9. Java多线程同步方法Synchronized和volatile

    11 同步方法  synchronized – 同时解决了有序性.可见性问题  volatile – 结果可见性问题 12 同步- synchronized synchronized可以在任意对象上加 ...

随机推荐

  1. ROS 创建服务和请求

    教程 维基 http://wiki.ros.org/cn/ROS/Tutorials 快速过程 创建包 $ cd ~/catkin_ws $ mkdir ~/catkin_ws/src $ cd ~/ ...

  2. sqlachemy 查询当日数据,

    Tokens.query.filter(Tokens.user_id == user_id, db.cast(Tokens.create_time, db.DATE) == db.cast(curre ...

  3. Flask-SQLAlchemy常用操作

    一.SQLAlchemy介绍 SQLAlchemy是一个基于Python实现的ORM框架.该框架建立在 DB API之上,使用关系对象映射进行数据库操作,简言之便是:将类和对象转换成SQL,然后使用数 ...

  4. 解决 Vim 的 quickfix 插件错误信息乱码问题

      将以下代码插入 vim 配置文件即可,       function! QfMakeConv()        let qflist = getqflist()        for i in q ...

  5. Java中class的getName()和getCanonicalName()两个方法的区别

    getName()返回的是虚拟机里面的class的表示 getCanonicalName()返回的是更容易理解的表示 对于普通类来说,二者没什么区别,只是对于特殊的类型上有点表示差异 比如byte[] ...

  6. hdu - 2586 (LCA板子题)

    传送门 (这次的英文题面要比上一个容易看多了) (英语蒟蒻的卑微) 又是一个很裸的LCA题 (显然,这次不太容易打暴力咧) (但听说还是有大佬用dfs直接a掉了) 正好 趁这个机会复习一下LCA 这里 ...

  7. [Micropython]TPYBoardV102 DIY智能温控小风扇

    1.实验目的 1. 学习在PC机系统中扩展简单I/O 接口的方法. 2. 进一步学习编制数据输出程序的设计方法. 3. 学习DS18B20的接线方法,并利用DS18B20检测当前温度. 4.学习三极管 ...

  8. jquery append()与html()注意项

    项目中,涉及到有一部分js代码是从数据库中读取出来动态加载到一个<script>标签中的情况.使用到了jquery的append()和html()两个函数. 应用场景如下: 页面端,有一个 ...

  9. SpringBoot整合定时任务异步任务

    1.定时任务 1.开启定时任务 @SpringBootApplication //开启定时任务 @EnableScheduling public class SpringBootDemoApplica ...

  10. ASP.NET Core 添加区域步骤(详细)

    1 前言 早就想总结一下,但是没时间,这次有时间了,就详细的把步骤写出来. 2 步骤 2.1 添加区域 右键项目->添加->区域,如图1: 区域名称:Ceshi 添加完之后解决方案管理器会 ...