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

相关文章

Spring Boot 常用注解整理(最全收藏版)

《SpringBoot常用注解整理(最全收藏版)》本文系统整理了常用的Spring/SpringBoot注解,按照功能分类进行介绍,每个注解都会涵盖其含义、提供来源、应用场景以及代码示例,帮助开发... 目录Spring & Spring Boot 常用注解整理一、Spring Boot 核心注解二、Spr

SpringBoot实现接口数据加解密的三种实战方案

《SpringBoot实现接口数据加解密的三种实战方案》在金融支付、用户隐私信息传输等场景中,接口数据若以明文传输,极易被中间人攻击窃取,SpringBoot提供了多种优雅的加解密实现方案,本文将从原... 目录一、为什么需要接口数据加解密?二、核心加解密算法选择1. 对称加密(AES)2. 非对称加密(R

详解如何在SpringBoot控制器中处理用户数据

《详解如何在SpringBoot控制器中处理用户数据》在SpringBoot应用开发中,控制器(Controller)扮演着至关重要的角色,它负责接收用户请求、处理数据并返回响应,本文将深入浅出地讲解... 目录一、获取请求参数1.1 获取查询参数1.2 获取路径参数二、处理表单提交2.1 处理表单数据三、

java变量内存中存储的使用方式

《java变量内存中存储的使用方式》:本文主要介绍java变量内存中存储的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、变量的定义3、 变量的类型4、 变量的作用域5、 内存中的存储方式总结1、介绍在 Java 中,变量是用于存储程序中数据

如何合理管控Java语言的异常

《如何合理管控Java语言的异常》:本文主要介绍如何合理管控Java语言的异常问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、Thorwable类3、Error4、Exception类4.1、检查异常4.2、运行时异常5、处理方式5.1. 捕获异常

Spring Boot集成SLF4j从基础到高级实践(最新推荐)

《SpringBoot集成SLF4j从基础到高级实践(最新推荐)》SLF4j(SimpleLoggingFacadeforJava)是一个日志门面(Facade),不是具体的日志实现,这篇文章主要介... 目录一、日志框架概述与SLF4j简介1.1 为什么需要日志框架1.2 主流日志框架对比1.3 SLF4

Spring Boot集成Logback终极指南之从基础到高级配置实战指南

《SpringBoot集成Logback终极指南之从基础到高级配置实战指南》Logback是一个可靠、通用且快速的Java日志框架,作为Log4j的继承者,由Log4j创始人设计,:本文主要介绍... 目录一、Logback简介与Spring Boot集成基础1.1 Logback是什么?1.2 Sprin

重新对Java的类加载器的学习方式

《重新对Java的类加载器的学习方式》:本文主要介绍重新对Java的类加载器的学习方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍1.1、简介1.2、符号引用和直接引用1、符号引用2、直接引用3、符号转直接的过程2、加载流程3、类加载的分类3.1、显示

Java资源管理和引用体系的使用详解

《Java资源管理和引用体系的使用详解》:本文主要介绍Java资源管理和引用体系的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Java的引用体系1、强引用 (Strong Reference)2、软引用 (Soft Reference)3、弱引用 (W

SpringBoot实现二维码生成的详细步骤与完整代码

《SpringBoot实现二维码生成的详细步骤与完整代码》如今,二维码的应用场景非常广泛,从支付到信息分享,二维码都扮演着重要角色,SpringBoot是一个非常流行的Java基于Spring框架的微... 目录一、环境搭建二、创建 Spring Boot 项目三、引入二维码生成依赖四、编写二维码生成代码五