synchronized 同步操作应该是细粒度

2024-02-29 05:32

本文主要是介绍synchronized 同步操作应该是细粒度,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

synchronized
Java良好的支持多线程。使用java,我们可以很轻松的编程一个多线程程序。但是使用多线程可能会引起并发访问的问题。synchronized和ThreadLocal都是用来解决多线程并发访问的问题。大家可能对synchronized较为熟悉,而对ThreadLocal就要陌生得多了。
并发问题。当一个对象被两个线程同时访问时,可能有一个线程会得到不可预期的结果。

一个简单的java类Studnet

Java代码 复制代码
  1. publicclassStudent{
  2. privateintage=0;
  3. publicintgetAge(){
  4. returnthis.age;
  5. }
  6. publicvoidsetAge(intage){
  7. this.age=age;
  8. }
  9. }
public class Student {private int age=0;public int getAge() {return this.age;}public void setAge(int age) {this.age = age;}
}


一个多线程类ThreadDemo.
这个类有一个Student的私有变量,在run方法中,它随机产生一个整数。然后设置到student变量中,从student中读取设置后的值。然后睡眠5秒钟,最后再次读student的age值。

Java代码 复制代码
  1. publicclassThreadDemoimplementsRunnable{
  2. Studentstudent=newStudent();
  3. publicstaticvoidmain(String[]agrs){
  4. ThreadDemotd=newThreadDemo();
  5. Threadt1=newThread(td,"a");
  6. Threadt2=newThread(td,"b");
  7. t1.start();
  8. t2.start();
  9. }
  10. /*(non-Javadoc)
  11. *@seejava.lang.Runnable#run()
  12. */
  13. publicvoidrun(){
  14. accessStudent();
  15. }
  16. publicvoidaccessStudent(){
  17. StringcurrentThreadName=Thread.currentThread().getName();
  18. System.out.println(currentThreadName+"isrunning!");
  19. //System.out.println("firstreadageis:"+this.student.getAge());
  20. Randomrandom=newRandom();
  21. intage=random.nextInt(100);
  22. System.out.println("thread"+currentThreadName+"setageto:"+age);
  23. this.student.setAge(age);
  24. System.out.println("thread"+currentThreadName+"firstreadageis:"+this.student.getAge());
  25. try{
  26. Thread.sleep(5000);
  27. }
  28. catch(InterruptedExceptionex){
  29. ex.printStackTrace();
  30. }
  31. System.out.println("thread"+currentThreadName+"secondreadageis:"+this.student.getAge());
  32. }
  33. }
public class ThreadDemo implements Runnable{Student student = new Student();public static void main(String[] agrs) {ThreadDemo td = new ThreadDemo();Thread t1 = new Thread(td,"a");Thread t2 = new Thread(td,"b");t1.start();t2.start();}
/* (non-Javadoc)* @see java.lang.Runnable#run()*/public void run() {accessStudent();}public void accessStudent() {String currentThreadName = Thread.currentThread().getName();System.out.println(currentThreadName+" is running!");// System.out.println("first  read age is:"+this.student.getAge());Random random = new Random();int age = random.nextInt(100);System.out.println("thread "+currentThreadName +" set age to:"+age);this.student.setAge(age);System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());try {Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());}}

运行这个程序,屏幕输出如下:
a is running!
b is running!
thread b set age to:33
thread b first read age is:33
thread a set age to:81
thread a first read age is:81
thread b second read age is:81
thread a second read age is:81

需要注意的是,线程a在同一个方法中,第一次读取student的age值与第二次读取值不一致。这就是出现了并发问题。

synchronized
上面的例子,我们模似了一个并发问题。Java提供了同步机制来解决并发问题。synchonzied关键字可以用来同步变量,方法,甚至同步一个代码块。
使用了同步后,一个线程正在访问同步对象时,另外一个线程必须等待。
Synchronized同步方法
现在我们可以对accessStudent方法实施同步。
public synchronized void accessStudent()
再次运行程序,屏幕输出如下:
a is running!
thread a set age to:49
thread a first read age is:49
thread a second read age is:49
b is running!
thread b set age to:17
thread b first read age is:17
thread b second read age is:17

加上了同步后,线程b必须等待线程a执行完毕后,线程b才开始执行。

对方法进行同步的代价是非常昂贵的。特别是当被同步的方法执行一个冗长的操作。这个方法执行会花费很长的时间,对这样的方法进行同步可能会使系统性能成数量级的下降。

Synchronized同步块
在accessStudent方法中,我们真实需要保护的是student变量,所以我们可以进行一个更细粒度的加锁。我们仅仅对student相关的代码块进行同步。

Java代码 复制代码
  1. synchronized(this){
  2. Randomrandom=newRandom();
  3. intage=random.nextInt(100);
  4. System.out.println("thread"+currentThreadName+"setageto:"+age);
  5. this.student.setAge(age);
  6. System.out.println("thread"+currentThreadName+"firstreadageis:"+this.student.getAge());
  7. try{
  8. Thread.sleep(5000);
  9. }
  10. catch(InterruptedExceptionex){
  11. ex.printStackTrace();
  12. }
  13. }
	    synchronized(this) {Random random = new Random();int age = random.nextInt(100);System.out.println("thread "+currentThreadName +" set age to:"+age);this.student.setAge(age);System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());try {Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}

运行方法后,屏幕输出:
a is running!
thread a set age to:18
thread a first read age is:18
b is running!
thread a second read age is:18
thread b set age to:62
thread b first read age is:62
thread b second read age is:62

需要特别注意这个输出结果。
这个执行过程比上面的方法同步要快得多了。
只有对student进行访问的代码是同步的,而其它与部份代码却是异步的了。而student的值并没有被错误的修改。如果是在一个真实的系统中,accessStudent方法的操作又比较耗时的情况下。使用同步的速度几乎与没有同步一样快。

使用同步锁
稍微把上面的例子改一下,在ThreadDemo中有一个私有变量count,。
private int count=0;
在accessStudent()中, 线程每访问一次,count都自加一次, 用来记数线程访问的次数。

Java代码 复制代码
  1. try{
  2. this.count++;
  3. Thread.sleep(5000);
  4. }catch(InterruptedExceptionex){
  5. ex.printStackTrace();
  6. }
	    try {this.count++;Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}

为了模拟线程,所以让它每次自加后都睡眠5秒。
accessStuden()方法的完整代码如下:

Java代码 复制代码
  1. StringcurrentThreadName=Thread.currentThread().getName();
  2. System.out.println(currentThreadName+"isrunning!");
  3. try{
  4. this.count++;
  5. Thread.sleep(5000);
  6. }catch(InterruptedExceptionex){
  7. ex.printStackTrace();
  8. }
  9. System.out.println("thread"+currentThreadName+"readcount:"+this.count);
  10. synchronized(this){
  11. Randomrandom=newRandom();
  12. intage=random.nextInt(100);
  13. System.out.println("thread"+currentThreadName+"setageto:"+age);
  14. this.student.setAge(age);
  15. System.out.println("thread"+currentThreadName+"firstreadageis:"+this.student.getAge());
  16. try{
  17. Thread.sleep(5000);
  18. }
  19. catch(InterruptedExceptionex){
  20. ex.printStackTrace();
  21. }
  22. }
  23. System.out.println("thread"+currentThreadName+"secondreadageis:"+this.student.getAge());
   	    String currentThreadName = Thread.currentThread().getName();System.out.println(currentThreadName+" is running!");try {this.count++;Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}System.out.println("thread "+currentThreadName+" read count:"+this.count);synchronized(this) {Random random = new Random();int age = random.nextInt(100);System.out.println("thread "+currentThreadName +" set age to:"+age);this.student.setAge(age);System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());try {Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());

运行程序后,屏幕输出:
a is running!
b is running!
thread a read count:2
thread a set age to:49
thread a first read age is:49
thread b read count:2
thread a second read age is:49
thread b set age to:7
thread b first read age is:7
thread b second read age is:7

我们仍然对student对象以synchronized(this)操作进行同步。
我们需要在两个线程中共享count失败。

所以仍然需要对count的访问进行同步操作。

Java代码 复制代码
  1. synchronized(this){
  2. try{
  3. this.count++;
  4. Thread.sleep(5000);
  5. }catch(InterruptedExceptionex){
  6. ex.printStackTrace();
  7. }
  8. }
  9. System.out.println("thread"+currentThreadName+"readcount:"+this.count);
  10. synchronized(this){
  11. Randomrandom=newRandom();
  12. intage=random.nextInt(100);
  13. System.out.println("thread"+currentThreadName+"setageto:"+age);
  14. this.student.setAge(age);
  15. System.out.println("thread"+currentThreadName+"firstreadageis:"+this.student.getAge());
  16. try{
  17. Thread.sleep(5000);
  18. }
  19. catch(InterruptedExceptionex){
  20. ex.printStackTrace();
  21. }
  22. }
  23. System.out.println("thread"+currentThreadName+"secondreadageis:"+this.student.getAge());
  24. longendTime=System.currentTimeMillis();
  25. longspendTime=endTime-startTime;
  26. System.out.println("花费时间:"+spendTime+"毫秒");
		 synchronized(this) {try {this.count++;Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}System.out.println("thread "+currentThreadName+" read count:"+this.count);synchronized(this) {Random random = new Random();int age = random.nextInt(100);System.out.println("thread "+currentThreadName +" set age to:"+age);this.student.setAge(age);System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());try {Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());long endTime = System.currentTimeMillis();long spendTime = endTime - startTime;System.out.println("花费时间:"+spendTime +"毫秒");


程序运行后,屏幕输出
a is running!
b is running!
thread a read count:1
thread a set age to:97
thread a first read age is:97
thread a second read age is:97
花费时间:10015毫秒
thread b read count:2
thread b set age to:47
thread b first read age is:47
thread b second read age is:47
花费时间:20124毫秒

我们在同一个方法中,多次使用synchronized(this)进行加锁。有可能会导致太多额外的等待。
应该使用不同的对象锁进行同步。

设置两个锁对象,分别用于student和count的访问加锁。

Java代码 复制代码
  1. privateObjectstudentLock=newObject();
  2. privateObjectcountLock=newObject();
  3. accessStudent()方法如下:
  4. longstartTime=System.currentTimeMillis();
  5. StringcurrentThreadName=Thread.currentThread().getName();
  6. System.out.println(currentThreadName+"isrunning!");
  7. //System.out.println("firstreadageis:"+this.student.getAge());
  8. synchronized(countLock){
  9. try{
  10. this.count++;
  11. Thread.sleep(5000);
  12. }catch(InterruptedExceptionex){
  13. ex.printStackTrace();
  14. }
  15. }
  16. System.out.println("thread"+currentThreadName+"readcount:"+this.count);
  17. synchronized(studentLock){
  18. Randomrandom=newRandom();
  19. intage=random.nextInt(100);
  20. System.out.println("thread"+currentThreadName+"setageto:"+age);
  21. this.student.setAge(age);
  22. System.out.println("thread"+currentThreadName+"firstreadageis:"+this.student.getAge());
  23. try{
  24. Thread.sleep(5000);
  25. }
  26. catch(InterruptedExceptionex){
  27. ex.printStackTrace();
  28. }
  29. }
  30. System.out.println("thread"+currentThreadName+"secondreadageis:"+this.student.getAge());
  31. longendTime=System.currentTimeMillis();
  32. longspendTime=endTime-startTime;
  33. System.out.println("花费时间:"+spendTime+"毫秒");
 private Object studentLock = new Object();
private Object countLock = new Object();accessStudent()方法如下:long startTime = System.currentTimeMillis();String currentThreadName = Thread.currentThread().getName();System.out.println(currentThreadName+" is running!");// System.out.println("first  read age is:"+this.student.getAge());synchronized(countLock) {try {this.count++;Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}System.out.println("thread "+currentThreadName+" read count:"+this.count);synchronized(studentLock) {Random random = new Random();int age = random.nextInt(100);System.out.println("thread "+currentThreadName +" set age to:"+age);this.student.setAge(age);System.out.println("thread "+currentThreadName+" first  read age is:"+this.student.getAge());try {Thread.sleep(5000);}catch(InterruptedException ex) {ex.printStackTrace();}}System.out.println("thread "+currentThreadName +" second read age is:"+this.student.getAge());long endTime = System.currentTimeMillis();long spendTime = endTime - startTime;System.out.println("花费时间:"+spendTime +"毫秒");


这样对count和student加上了两把不同的锁。

运行程序后,屏幕输出:
a is running!
b is running!
thread a read count:1
thread a set age to:48
thread a first read age is:48
thread a second read age is:48
花费时间:10016毫秒
thread b read count:2
thread b set age to:68
thread b first read age is:68
thread b second read age is:68
花费时间:20046毫秒
与两次使用synchronized(this)相比,使用不同的对象锁,在性能上可以得到更大的提升。

由此可见synchronized是实现java的同步机制。同步机制是为了实现同步多线程对相同资源的并发访问控制。保证多线程之间的通信。
可见,同步的主要目的是保证多线程间的数据共享。同步会带来巨大的性能开销,所以同步操作应该是细粒度的。如果同步使用得当,带来的性能开销是微不足道的。使用同步真正的风险是复杂性和可能破坏资源安全,而不是性能。

这篇关于synchronized 同步操作应该是细粒度的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

关键字synchronized、volatile的比较

关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字的执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。多线程访问volatile不会发生阻塞,而synchronize

编程应该用 Mac 还是 PC ?

『有人的地方,就有江湖』—徐克。笑傲江湖。     序     一个竞争的市场,就会有对立的产生,这世界存在著很多不同的领域,领域好比是个江湖的缩影,因此就有许多门派的纷争,例如说浏览器领域有著最大宗的IE派,门派成长速度飞快,武功版号跳的跟台湾物价指数一样快的Chrome门,不断被模仿,一直被超越的Opera派;韧性极强,一直对抗几大势力的Firefox派等等,程序语言也有自己的领域

面试官:synchronized的锁升级过程是怎样的?

大家好,我是大明哥,一个专注「死磕 Java」系列创作的硬核程序员。 回答 在 JDK 1.6之前,synchronized 是一个重量级、效率比较低下的锁,但是在JDK 1.6后,JVM 为了提高锁的获取与释放效,,对 synchronized 进行了优化,引入了偏向锁和轻量级锁,至此,锁的状态有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁。 锁升级就是无锁 —>

细粒度锁的实现

最近在工作上碰见了一些高并发的场景需要加锁来保证业务逻辑的正确性,并且要求加锁后性能不能受到太大的影响。初步的想法是通过数据的时间戳,id等关键字来加锁,从而保证不同类型数据处理的并发性。而java自身api提供的锁粒度太大,很难同时满足这些需求,于是自己动手写了几个简单的扩展...     1. 分段锁         借鉴concurrentHashMap的分段思想,先生成一定数量的锁,

虚拟主机应该如何设置

假设我们在一个独立的环境下,现在我的根目录在D盘下的wamp下的www下!现在先来配置虚拟主机: 假设我们在一个独立的环境下,现在我的根目录在D盘下的wamp下的www下!现在先来配置虚拟主机: 1.先打开apache的配置文件httpd.conf,并去掉#Include 0conf/extra/httpd-vhosts.conf前面的#号。 2.打开apache的apach

为什么你应该从现在开始就写博客---刘未鹏

(一)为什么你应该(从现在开始就)写博客 用一句话来说就是,写一个博客有很多好处,却没有任何明显的坏处。(阿灵顿的情况属于例外,而非常态,就像不能拿抽烟活到一百岁的英国老太太的个例来反驳抽烟对健康的极大损伤一样) 让我说得更明确一点:用博客的形式来记录下你有价值的思考,会带来很多好处,却没有任何明显的坏处。Note:碎碎念不算思考、心情琐记不算思考、唠唠叨叨也不算思考、没话找话也不算思考

教你如何应对算法备案难点以及备案后应该做什么?

教你如何应对算法备案难点以及备案后应该做什么? 自去年六月至今年八月,网信办已公布七批备案清单,共计1919项算法成功备案,标志着算法备案步入常态。主体备案虽门槛较低,算法备案却考验专业,尤其是《算法安全自评估报告》成为监管焦点,逾百项审查要点需逐一回应。算法数据的输入输出、模型架构、训练数据、策略逻辑与风险管理,每一环皆需详尽说明。下面,小编给大家着重讲讲互联网算法备案申请的难点以及备案