Java多线程核心技术一-基础篇synchronzied同步语句块

2023-11-30 02:28

本文主要是介绍Java多线程核心技术一-基础篇synchronzied同步语句块,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 接上篇:Java多线程核心技术二-synchronzied同步方法

1 概述

        用synchronzied关键字声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B现成就要等待比较长的时间,此时可以使用synchronzied同步语句块来解决,已增加运行效率。

        synchronzied方法是将当前对象作为锁,而synchronzied代码块是将任意对象作为锁。锁可以认为是一个标识,持有这个标识的线程就可以执行被同步的代码。

2 synchronzied方法的弊端

        下面证明用synchronzied关键字声明方法时是有弊端的。

public class Task {private String getData1;private String getData2;public synchronized void doLongTimeTask(){try {System.out.println("任务开始");Thread.sleep(3000);getData1 = "长时间处理任务后,从远程返回的值1线程名=" + Thread.currentThread().getName();getData2 = "长时间处理任务后,从远程返回的值2线程名=" + Thread.currentThread().getName();System.out.println(getData1);System.out.println(getData2);System.out.println("任务结束");}catch (InterruptedException e){e.printStackTrace();}}
}
public class CommonUtils {public static long beginTime1;public static long endTime1;public static long beginTime2;public static long endTime2;
}
public class MyThread1 extends Thread{private Task task;public MyThread1(Task task) {this.task = task;}@Overridepublic void run(){CommonUtils.beginTime1 = System.currentTimeMillis();task.doLongTimeTask();CommonUtils.endTime1 = System.currentTimeMillis();}
}
public class MyThread2 extends Thread{private Task task;public MyThread2(Task task) {this.task = task;}@Overridepublic void run(){CommonUtils.beginTime2 = System.currentTimeMillis();task.doLongTimeTask();CommonUtils.endTime2 = System.currentTimeMillis();}
}
public class Run1 {public static void main(String[] args) {Task task = new Task();MyThread1 t1 = new MyThread1(task);t1.start();MyThread2 t2 = new MyThread2(task);t2.start();try {Thread.sleep(10000);}catch (InterruptedException e){e.printStackTrace();}long beginTime = CommonUtils.beginTime1;if(CommonUtils.beginTime2 < CommonUtils.beginTime1){beginTime = CommonUtils.beginTime2;}long endTime = CommonUtils.endTime1;if(CommonUtils.endTime2 > CommonUtils.endTime1){endTime = CommonUtils.endTime2;}System.out.println("耗时:"+((endTime - beginTime) /1000) + "秒");}
}

        通过使用synchronzied关键字来声明方法,从运行的时间上来看,弊端很明显,可以使用synchronzied同步代码块来解决。

3 synchronzied同步代码块的使用

        先来了解一下synchronzied同步代码块的使用方法。当两个并发线程访问同一个对象object中的synchronzied(this)同步代码块时,一个时间内只能执行一个线程,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

public class ObjectService {public void serviceMethod(){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();}}
}

public class ThreadA extends Thread{private ObjectService objectService;public ThreadA(ObjectService objectService) {this.objectService = objectService;}@Overridepublic void run(){objectService.serviceMethod();}
}
public class ThreadB extends Thread{private ObjectService service;public ThreadB(ObjectService service) {this.service = service;}@Overridepublic void run(){service.serviceMethod();}
}
public class Run1 {public static void main(String[] args) {ObjectService service = new ObjectService();ThreadA a = new ThreadA(service);a.setName("a");a.start();ThreadB b = new ThreadB(service);b.setName("b");b.start();}
}

        上面的例子虽然使用了synchronzied同步代码块,但执行的效率没有提高,还是同步运行。如何用synchronzied同步代码块解决程序执行效率慢的问题呢?

4 用同步代码块解决同步方法的弊端

        【示例】 

public class Task {private String getData1;private String getData2;public  void doLongTimeTask(){try {System.out.println("开始任务:");Thread.sleep(3000);String privateGetData1 = "长时间处理任务后,从远程返回的值1 线程名 = " + Thread.currentThread().getName();String privateGetData2 = "长时间处理任务后,从远程返回的值2 线程名 = " + Thread.currentThread().getName();synchronized (this){getData1 = privateGetData1;getData2 = privateGetData2;System.out.println(getData1);System.out.println(getData2);System.out.println("任务结束。");}}catch (InterruptedException e){e.printStackTrace();}}
}
public class MyThread1 extends Thread{private Task task;public MyThread1(Task task) {this.task = task;}@Overridepublic void run(){CommonUtils.beginTime1 = System.currentTimeMillis();task.doLongTimeTask();CommonUtils.endTime1 = System.currentTimeMillis();}
}
public class MyThread2 extends Thread{private Task task;public MyThread2(Task task) {this.task = task;}@Overridepublic void run(){CommonUtils.beginTime2 = System.currentTimeMillis();task.doLongTimeTask();CommonUtils.endTime2 = System.currentTimeMillis();}
}
public class Run1 {public static void main(String[] args) {Task task = new Task();MyThread1 t1 = new MyThread1(task);t1.start();MyThread2 t2 = new MyThread2(task);t2.start();try {Thread.sleep(10000);}catch (InterruptedException e){e.printStackTrace();}long beginTime = CommonUtils.beginTime1;if(CommonUtils.beginTime2 < CommonUtils.beginTime1){beginTime = CommonUtils.beginTime2;}long endTime = CommonUtils.endTime1;if(CommonUtils.endTime2 > CommonUtils.endTime1){endTime = CommonUtils.endTime2;}System.out.println("耗时:"+((endTime - beginTime) /1000) + "秒");}
}

        通过上面的示例可以看出,当一个线程访问object对象的一个synchronzied同步代码块时,另一个线程仍然可以访问该对象中的非synchronzied同步代码块。在这个示例中,虽然时间缩短了,加快了运行效率,但同步synchronzied代码块真的是同步的吗?它真的持有当前调用对象的锁吗?是的,但必须通过下个例子来验证。

5 一半异步,一半同步 

        本节示例用于说明不在synchronzied代码块中就是异步执行,在synchronzied代码块中就是同步执行。

public class Task {public void doLongTimeTask(){for (int i = 0; i < 100; i++) {System.out.println("没有执行同步方法的线程名 = " + Thread.currentThread().getName() + "i=" + (i+1));}System.out.println("");synchronized (this){for (int i = 0; i < 100; i++) {System.out.println("执行同步方法的线程名 = " + Thread.currentThread().getName() + "i=" + (i+1));}}}
}
public class MyThread1 extends Thread{private Task task;public MyThread1(Task task) {this.task = task;}@Overridepublic  void run(){task.doLongTimeTask();}
}
public class MyThread2 extends Thread{private Task task;public MyThread2(Task task) {this.task = task;}@Overridepublic  void run(){task.doLongTimeTask();}
}
public class Run1 {public static void main(String[] args) {Task task = new Task();MyThread1 a = new MyThread1(task);a.start();MyThread2 b = new MyThread2(task);b.start();}
}

根据运行结果可知,在执行没有同步代码块的时候,两个线程之间互相竞争资源。进入同步代码块后,两个线程被排队执行。

6 synchronzied代码块间的同步性

        在使用synchronzied同步代码块时需要注意,当一个线程访问object的一个synchronzied同步代码块时,其他线程对同一个object中的所有其他synchronzied同步代码块的访问都被阻塞,说明synchronzied使用的对象监视器是一个,即使用的“锁”是一个。通过示例验证。

public class ObjectService {public void serviceMethodA(){try {synchronized (this){System.out.println("方法A开始执行时间" + System.currentTimeMillis());Thread.sleep(2000);System.out.println("方法A结束时间" + System.currentTimeMillis());}}catch (InterruptedException e){}}public void serviceMethodB(){synchronized (this){System.out.println("方法B开始执行时间 = " + System.currentTimeMillis());System.out.println("方法B结束时间 = " + System.currentTimeMillis());}}
}
public class ThreadA extends Thread{private ObjectService objectService;public ThreadA(ObjectService objectService) {this.objectService = objectService;}@Overridepublic void  run(){objectService.serviceMethodA();}
}
public class ThreadB extends Thread{private ObjectService objectService;public ThreadB(ObjectService objectService) {this.objectService = objectService;}@Overridepublic void run(){objectService.serviceMethodB();}
}
public class Run1 {public static void main(String[] args) {ObjectService service = new ObjectService();ThreadA a = new ThreadA(service);a.setName("a");a.start();ThreadB b = new ThreadB(service);b.setName("b");b.start();}
}

7 将任意对象作为锁

        多个线程调用同一个对象中的不同名称的synchronzied同步方法或同步代码块,是按顺序执行,也就是同步的。

        如果在一个类中同时存在synchronzied同步方法和同步代码块,对其他synchronzied同步方法或同步代码块调用会呈同步的效果,执行特性如下:

        1、同一个时间只有一个线程可以执行synchronzied同步方法中的代码。

        2、同一时间只有一个线程可以执行synchronzied同步代码块中的代码。

        其中Java还支持将任意对象作为锁,来实现同步的功能,这个任意对象大多数是实例变量及方法的参数,格式为synchronzied(非this对象)。

        synchronzied(非this对象)同步代码块的执行特性是:在多个线程争抢相同的非this对象的锁时,同一时间只有一个线程可以执行synchronzied同步代码块中的代码。

public class Service {private String usernameParam;private String passwordParam;private String anyString = new String();public void  setUsernamePassword(String username,String password){try {synchronized (anyString){System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "进入同步代码块");usernameParam = username;Thread.sleep(3000);passwordParam = password;System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "离开同步代码块");}}catch (InterruptedException e){e.printStackTrace();}}
}
public class ThreadA extends Thread{private Service service;public ThreadA(Service service) {this.service = service;}@Overridepublic void run(){service.setUsernamePassword("a","aa");}
}
public class ThreadB extends Thread{private Service service;public ThreadB(Service service) {this.service = service;}@Overridepublic void run(){service.setUsernamePassword("b","bb");}
}
public class Run1 {public static void main(String[] args) {Service service = new Service();ThreadA a = new ThreadA(service);a.setName("A");a.start();ThreadB b = new ThreadB(service);b.setName("B");b.start();}
}

总结:锁非this对象具有一定的优点,就是如果一个类中有很多synchronzied方法,这时虽然能是实现同步,但影响运行效率,如果使用同步代码块锁非this对象,则synchronzied代码块中的程序与同步方法是异步的,因为是两把锁,不与其他锁this同步方法争抢this锁,可以提高运行效率。

8 验证方法被调用是随机的

        同步代码块放在非同步synchronzied方法中进行声明,并不能保证调用方法的线程的执行顺序,即线程调用方法是无序的,下面来验证多个线程调用同一个方法是随机的。

public class MyList {private List list = new ArrayList<>();synchronized public void add(String username){System.out.println("线程 " + Thread.currentThread().getName() + "执行了 add 方法");list.add(username);System.out.println("线程 " + Thread.currentThread().getName() + "退出了 add 方法");}}

public class ThreadA extends Thread{private MyList myList;public ThreadA(MyList myList) {this.myList = myList;}@Overridepublic void run(){for (int i = 0; i < 50000; i++) {myList.add("thread_a " + (i+1));}}
}
public class ThreadB extends Thread{private MyList myList;public ThreadB(MyList myList) {this.myList = myList;}@Overridepublic void run(){for (int i = 0; i < 50000; i++) {myList.add("thread_b"+ (i+1));}}
}
public class Run1 {public static void main(String[] args) {MyList myList = new MyList();ThreadA a = new ThreadA(myList);a.setName("a");a.start();ThreadB b = new ThreadB(myList);b.setName("b");b.start();}
}

        从运行结果来看,同步方法中的代码是同步输出的,所以线程的“执行”与“退出”是成对出现的,但是方法被调用是随机的,也就是线程A和线程B的执行是异步的。 

9 静态同步:synchronzied方法与synchronzied(class)代码块

        synchronzied关键字还可以应用在静态方法上,如果这些写,那是对当前的*.java文件对应的Class类的对象进行持锁,Class类的对象是单例的,更具体的说,在静态方法上使用synchronzied关键字声明同步方法时,是使用当前静态方法所在类对应Class类的单例对象作为锁的。

public class Service {synchronized public static void printA(){try {System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "进入printA方法");Thread.sleep(3000);System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "退出printA方法");}catch (InterruptedException e){e.printStackTrace();}}synchronized public static void printB(){System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "进入printA方法");System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "退出printA方法");}
}
public class ThreadA extends Thread{@Overridepublic void run(){Service.printA();}
}
public class ThreadB extends Thread{@Overridepublic void run(){Service.printB();}
}
public class Run1 {public static void main(String[] args) {ThreadA a = new ThreadA();a.setName("a");a.start();ThreadB b = new ThreadB();b.setName("b");b.start();}
}

        虽然运行结果与将synchronzied关键字加到非static静态方法上的效果一样,都是同步的效果,但还是有本质上的不同。synchronzied关键字加到static静态方法上是将Class类的对象作为锁,而synchronzied关键字加到非static静态方法上是将方法所在类的对象作为锁。

10 同步synchronzied方法可以对类的所有对象实例起作用

        Class锁可以对同一个类的所有对象实例起作用,实现同步效果。

public class Service {synchronized public static void printA(){try {System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "进入printA方法");Thread.sleep(3000);System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "退出printA方法");}catch (InterruptedException e){e.printStackTrace();}}synchronized public static void printB(){System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "进入printB方法");System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "退出printB方法");}
}
public class ThreadA extends Thread{private Service service;public ThreadA(Service service) {this.service = service;}@Overridepublic void run(){service.printA();}
}
public class ThreadB extends Thread{private Service service;public ThreadB(Service service) {this.service = service;}@Overridepublic void run(){service.printB();}
}
public class Run1 {public static void main(String[] args) {Service s1 = new Service();ThreadA a = new ThreadA(s1);a.setName("A");a.start();Service s2 = new Service();ThreadB b = new ThreadB(s2);b.setName("B");b.start();}
}

        虽然是不同的对象,但静态的同步方法还是同步运行了。

11 同步synchronzied(class)代码块可以对类的所有对象实例起作用

        同步代码块的作用其实和同步静态方法的作用一样。

public class Service {public void printA(){synchronized (Service.class){try {System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "进入printA方法");Thread.sleep(3000);System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "退出printA方法");}catch (InterruptedException e){e.printStackTrace();}}}public void printB(){synchronized (Service.class){System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "进入printB方法");System.out.println("线程名为:"+ Thread.currentThread().getName() + "在" +System.currentTimeMillis() + "退出printB方法");}}
}
public class ThreadA extends Thread{private Service service;public ThreadA(Service service) {this.service = service;}@Overridepublic void run(){service.printA();}
}

public class ThreadB extends Thread{private Service service;public ThreadB(Service service) {this.service = service;}@Overridepublic void run(){service.printB();}
}
public class Run1 {public static void main(String[] args) {Service s1 = new Service();Service s2 = new Service();ThreadA a = new ThreadA(s1);a.setName("A");a.start();ThreadB b = new ThreadB(s2);b.setName("B");b.start();}
}

这篇关于Java多线程核心技术一-基础篇synchronzied同步语句块的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/435257

相关文章

Java中对象的创建和销毁过程详析

《Java中对象的创建和销毁过程详析》:本文主要介绍Java中对象的创建和销毁过程,对象的创建过程包括类加载检查、内存分配、初始化零值内存、设置对象头和执行init方法,对象的销毁过程由垃圾回收机... 目录前言对象的创建过程1. 类加载检查2China编程. 分配内存3. 初始化零值4. 设置对象头5. 执行

SpringBoot整合easy-es的详细过程

《SpringBoot整合easy-es的详细过程》本文介绍了EasyES,一个基于Elasticsearch的ORM框架,旨在简化开发流程并提高效率,EasyES支持SpringBoot框架,并提供... 目录一、easy-es简介二、实现基于Spring Boot框架的应用程序代码1.添加相关依赖2.添

通俗易懂的Java常见限流算法具体实现

《通俗易懂的Java常见限流算法具体实现》:本文主要介绍Java常见限流算法具体实现的相关资料,包括漏桶算法、令牌桶算法、Nginx限流和Redis+Lua限流的实现原理和具体步骤,并比较了它们的... 目录一、漏桶算法1.漏桶算法的思想和原理2.具体实现二、令牌桶算法1.令牌桶算法流程:2.具体实现2.1

SpringBoot中整合RabbitMQ(测试+部署上线最新完整)的过程

《SpringBoot中整合RabbitMQ(测试+部署上线最新完整)的过程》本文详细介绍了如何在虚拟机和宝塔面板中安装RabbitMQ,并使用Java代码实现消息的发送和接收,通过异步通讯,可以优化... 目录一、RabbitMQ安装二、启动RabbitMQ三、javascript编写Java代码1、引入

spring-boot-starter-thymeleaf加载外部html文件方式

《spring-boot-starter-thymeleaf加载外部html文件方式》本文介绍了在SpringMVC中使用Thymeleaf模板引擎加载外部HTML文件的方法,以及在SpringBoo... 目录1.Thymeleaf介绍2.springboot使用thymeleaf2.1.引入spring

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在