关闭

多线程编程-synchronized语句块(三)

标签: synchronized语句块synchronized static方法synchronizedclass
15人阅读 评论(0) 收藏 举报
分类:

2.2 synchronized 同步语句块

2.2.1 使用synchronized同步方法时,如果一个线程调用同步方法执行一个长时间的任务,那么其他线程必须等待很长时间,这时用同步语句块可提高执行效率。

同步方法耗时测试代码:

public class Task {
	private String getData1;
	private String getData2;
	
	public synchronized void doLongTimeTask(){
		System.out.println("begin task!");
		try {
			Thread.sleep(2000);
			getData1="耗时任务,1,threadName="+Thread.currentThread().getName();
			getData2="耗时任务,2,threadName="+Thread.currentThread().getName();
			
			System.out.println(getData1);
			System.out.println(getData2);
			System.out.println("end task!");
		} 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 Thread1 extends Thread {
	private Task task;
	
	public Thread1(Task task){
		super();
		this.task = task;
	}
	
	@Override
	public void run(){
		super.run();
		CommonUtils.beginTime1 = System.currentTimeMillis();
		task.doLongTimeTask();
		CommonUtils.endTime1 = System.currentTimeMillis();
	}
}
public class Thread2 extends Thread {
	private Task task;
	
	public Thread2(Task task){
		super();
		this.task = task;
	}
	
	@Override
	public void run(){
		super.run();
		CommonUtils.beginTime2 = System.currentTimeMillis();
		task.doLongTimeTask();
		CommonUtils.endTime2 = System.currentTimeMillis();
	}
}
public class RunDemo {
	public static void main(String[] args) {
		Task task = new Task();
		Thread1 thread1= new Thread1(task);
		thread1.start();
		Thread2 thread2= new Thread2(task);
		thread2.start();		
		try{
			Thread.sleep(6000);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		
		long beginTime = (CommonUtils.beginTime1 > CommonUtils.beginTime2)? 
				CommonUtils.beginTime2 :CommonUtils.beginTime1 ;
		long endTime =  (CommonUtils.endTime1 > CommonUtils.endTime2)? 
				CommonUtils.endTime1 :CommonUtils.endTime2 ;
		
		System.out.println("耗时:"+(endTime - beginTime)/1000);
	}
}

运行结果:

begin task!

耗时任务,1threadName=Thread-0

耗时任务,2threadName=Thread-0

end task!

begin task!

耗时任务,1threadName=Thread-1

耗时任务,2threadName=Thread-1

end task!

耗时:4

从耗时看,synchronized同步方法,弊端很明显。再看synchronized同步块,当两个线程访问同一个object对象中的synchronized同步块时,一段时间内只能有一个线程被执行,另一个线程必须等待。

测试代码,沿用上面2.2.1的代码,修改Task.java

public class Task {
	private String getData1;
	private String getData2;
	
	public void doLongTimeTask(){
		System.out.println("begin task!");
		try {
			Thread.sleep(2000);
			String getDataTmp1="耗时任务,1,threadName="+Thread.currentThread().getName();
			String getDataTmp2="耗时任务,2,threadName="+Thread.currentThread().getName();
			synchronized(this){
				getData1 = getDataTmp1;
				getData2 = getDataTmp2;
			}
			System.out.println(getData1);
			System.out.println(getData2);
			System.out.println("end task!");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

 

运行结果:

begin task!

begin task!

耗时任务,1threadName=a

耗时任务,1threadName=b

耗时任务,2threadName=a

end task!

耗时任务,2threadName=a

end task!

耗时:2

 

耗时减少为2s

2.2.2 当一个线程访问object的一个synchronized(this)同步块时,其他线程对同一个object中所有其他synchronized(this)同步块的访问将被阻塞,也就是说synchronized(this)使用的“对象监视器”是同一个。同synchronized方法一样,这里的this是锁定的当前对象。

使用synchronized(this)同步块时,Java支持将“任意对象”作为“对象监视器”来实现同步功能。这个“任意对象”多数是实例变量或方法参数,即:synchronized(任意对象)

使用任意对象,而不是this对象做为锁,有一个有点就是可以把锁粒度变的很小,从而提高效率。

 

结论:

Synchronized(非this对象x)代码块的写法是将x对象本身作为“对象监视器”,得出下面三个结论(都是用了同一个对象监视器):

1) 多个线程同时执行synchronized(x)同步代码块时呈同步效果。

2) 当其他线程执行x对象中的synchronized同步方法时呈同步效果。

3) 当其他线程执行x对象方法里面的synchronized(this)代码块时也呈同步效果。

2.2.3 静态同步synchronized方法,synchronized(class)代码块

关键字synchronized还可以应用在static静态方法上,这样写,是对当前的*.java文件对应的Class类进行持锁。

在静态static方法上给Class类上锁,和在非static方法上给对象上锁,本质上是不同的,他们并不是同一个锁,一个是Class锁,一个是对象锁。

Class锁可以对类的所有实例起作用。

测试代码:

public class Task {
	synchronized public static void staticMethodA(){
		try{
			System.out.println("线程为:"+Thread.currentThread().getName()
					+"在 "+System.currentTimeMillis()+"进入方法MethodA");
			Thread.sleep(2000);
			System.out.println("线程为:"+Thread.currentThread().getName()
					+"在 "+System.currentTimeMillis()+"离开方法MethodA");	
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
	
	synchronized public static void staticMethodB(){
		try{
			System.out.println("线程为:"+Thread.currentThread().getName()
					+"在 "+System.currentTimeMillis()+"进入方法MethodB");
			Thread.sleep(2000);
			System.out.println("线程为:"+Thread.currentThread().getName()
					+"在 "+System.currentTimeMillis()+"离开方法MethodB");	
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}	
}
public class Thread1 extends Thread {
	private Task task;
	
	public Thread1(Task task){
		super();
		this.task = task;
	}
	
	@Override
	public void run(){
		task.staticMethodA();
	}
}
public class Thread2 extends Thread {
	private Task task;
	
	public Thread2(Task task){
		super();
		this.task = task;
	}
	
	@Override
	public void run(){
		task.staticMethodB();
	}
}

public class RunDemo {
	public static void main(String[] args) {
		Task task1 = new Task();
		Task task2 = new Task();
		Thread1 thread1= new Thread1(task1);
		thread1.setName("a");
		thread1.start();
		Thread2 thread2= new Thread2(task2);
		thread2.setName("b");
		thread2.start();	
		
	}
}
运行结果:

线程为:a在 1515737051093进入方法MethodA
线程为:a在 1515737053093离开方法MethodA
线程为:b在 1515737053093进入方法MethodB
线程为:b在 1515737055093离开方法MethodB

虽然两个线程绑定的是不同的对象,但是静态的同步方法还是同步运行的,如果非static的同步方法,这个测试中肯定是异步执行的。

修改Task.javastaticMethodA方法中使用同步块synchronized(Task.class)):

public class Task {
	public static void staticMethodA(){
		synchronized(Task.class){
			try{
				System.out.println("线程为:"+Thread.currentThread().getName()
						+"在 "+System.currentTimeMillis()+"进入方法MethodA");
				Thread.sleep(2000);
				System.out.println("线程为:"+Thread.currentThread().getName()
						+"在 "+System.currentTimeMillis()+"离开方法MethodA");	
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
	
	synchronized public static void staticMethodB(){
		try{
			System.out.println("线程为:"+Thread.currentThread().getName()
					+"在 "+System.currentTimeMillis()+"进入方法MethodB");
			Thread.sleep(2000);
			System.out.println("线程为:"+Thread.currentThread().getName()
					+"在 "+System.currentTimeMillis()+"离开方法MethodB");	
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

这个测试中无论staticMethodA方法是否是static的,只要同步块是synchronized(Task.class),运行结果都是同步的,这说明synchronized(class)同步块跟synchronized static方法的作用是一样的。

通常情况下,synchronized同步块不建议使用String作为对象锁,而是建议使用new object()实例化的对象的。因为String常量池缓存的特性使得:

String a = “AA”;

String b = “AA”;

a和b 是相等的,虽然a、b不是同一个对象,但是如果把他们作为锁,却是相同的锁。

只要对象不变,即使对象的属性被改变,也不会影响锁的同步。


在Java中,最基本的互斥同步手段就是synchronized关键字,synchronized关键字经过编译之后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。如果Java程序中的synchronized明确指定了对象参数,那就是这个对象的reference;如果没有明确指定,那就根据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或Class对象来作为锁对象。

 

根据虚拟机规范的要求,在执行monitorenter指令时,首先要尝试获取对象的锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1,相应的,在执行monitorexit指令时会将锁计数器减1,当计数器为0时,锁就被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。

 

在虚拟机规范对monitorenter和monitorexit的行为描述中,有两点是需要特别注意的。首先,synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题。其次,同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。Java的线程是映射到操作系统的原生线程之上的,如果要阻塞或唤醒一个线程,都需要操作系统来帮忙完成,这就需要从用户态转换到核心态中,因此状态转换需要耗费很多的处理器时间。对于代码简单的同步块(如被synchronized修饰的getter()或setter()方法),状态转换消耗的时间有可能比用户代码执行的时间还要长。所以synchronized是Java语言中一个重量级(Heavyweight)的操作,在确实必要的情况下才使用这种操作。而虚拟机本身也会进行一些优化,譬如在通知操作系统阻塞线程之前加入一段自旋等待过程,避免频繁地切入到核心态之中。



0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

线程-synchronized方法和同步块的作用范围;synchronized(this)和synchronized(obj)的区别

原文:http://m.blog.csdn.net/blog/u010802573/38661719 参考资源: http://www.cnblogs.com/oracleDBA/archi...
  • lan861698789
  • lan861698789
  • 2015-12-29 12:04
  • 3759

15+N个顶级网上流行的Java多线程面试题及自己总结的答案(遇到即会更新)

1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
  • Crazy_Women
  • Crazy_Women
  • 2014-05-27 17:09
  • 1364

Java多线程之synchronized及死锁编写

java中锁很常见,尤其是在多线程的情况下,我们会经常使用到锁。面试中我们也会经常被问到如何编写一个死锁。 java提供synchronized关键字来提供锁机制,在多线程中为了使程序并行我们会常使用...
  • silk_bar
  • silk_bar
  • 2016-04-03 19:44
  • 1295

《多线程编程》学习之五:synchronized同步语句块,静态同步synchronized方法与synchronized(类名.class)代码块

在介绍同步语句块之前,先做一个实验,验证多个线程调用同一个同步方法是随机的。 例子一: 1)MyList.java 2)测试:         可见,在同步方法中的代码是同步打印的,但线程A和...
  • studyhxz
  • studyhxz
  • 2016-10-20 13:28
  • 614

IOS 多线程编程_NSLock,NSCondition,synchronized和生产者消费者模型

1.NSLock 线程锁, 任何两个线程访问同一共享资源(变量,数组)都需要加锁,保证同一时刻只能有一个线程访问共享资源一个银行账户:有1000块钱,有两个线程同时做一次取钱操作,取钱的金额为800...
  • lcg910978041
  • lcg910978041
  • 2016-05-18 13:54
  • 656

OSATOMIC.h的介绍 OSATOMIC与synchronized 加锁的对比 iOS开发 多线程编程

这段话是从网上copy过来的,总结了一下原子操作的作用。但是文中提到的osbase.h文件找不到。可能是因为版本升级我的lib中没有这个文件。 iOS平台下的原子操作函数都以OSAtomic开头...
  • a21064346
  • a21064346
  • 2012-10-16 14:26
  • 5308

学习笔记之多线程编程synchronized

线程的创建  在不是多继承的情况下,用继承Thread和实现Runnable都一样,,如果一旦出现多继承,则使用实现Runnable的方式来处理多线程问题 检测死锁现象: if (userName.e...
  • qq_38767992
  • qq_38767992
  • 2017-12-21 09:47
  • 21

Java多线程编程2--同步锁定--synchronized同步方法、脏读、锁重入

1、方法内的变量为线程安全   “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题(这是方法内部的变量是私有的特性造成的,所得结果也就是“线程安全”的了。 ...
  • oChangWen
  • oChangWen
  • 2016-05-04 10:52
  • 1172

Java多线程编程-(11)-从volatile和synchronized的底层实现原理看Java虚拟机对锁优化所做的努力

一、背景对于Java来说我们知道,Java代码首先会编译成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上进行执行。Java中所使用的并发机制依赖于J...
  • u010870518
  • u010870518
  • 2017-10-24 10:57
  • 5773

Java多线程编程-(13)-从volatile和synchronized的底层实现原理看Java虚拟机对锁优化所做的努力

Java多线程编程-(12)-单例模式几种写法的错与对 一、背景 对于Java来说我们知道,Java代码首先会编译成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为...
  • bntX2jSQfEHy7
  • bntX2jSQfEHy7
  • 2017-10-25 00:00
  • 277
    个人资料
    • 访问:35507次
    • 积分:1686
    • 等级:
    • 排名:千里之外
    • 原创:130篇
    • 转载:38篇
    • 译文:0篇
    • 评论:9条
    最新评论