java对象锁和线程同步

1. 对象锁

1.1 什么是对象锁?

JVM在创建对象的时候为每一个对象关联唯一的一把锁。谁首先获得了这把锁,谁就可以访问这把锁控制的资源。例如:每一个房间里的门上都有一把锁,谁先进入了房间,谁就把房间的门反锁上,这时候别人想进房间就必须排队等待,直到房间里人把门打开之后才能进入。

2.线程安全

2.1 当程序中使用了多线程机制,并且多个线程之间需要访问相同的资源时,可能会导致数据混乱。这样是很不安全的。

例如: 用以下代码来模拟银行的存取款操作。对同一账户,首先存入1000元,然后取出1000元,最后查询余额,应该永远都是1000元才对。

首先来看单线程的情况。

		package synchronize;

		

		import java.util.Random;

		

		public class BankProcess extends Thread{

		private static int account = 3000;

		//存款

		public void save(int money){

		Random r = new Random();

		try {

		this.sleep(100*r.nextInt(4));

		} catch (InterruptedException e) {

		e.printStackTrace();

		}

		account += money;

		}

		//取款

		public void takeout(int money){

		Random r = new Random();

		try {

		this.sleep(100*r.nextInt(4));

		} catch (InterruptedException e) {

		e.printStackTrace();

		}

		account -= money;

		}

		//查询余额

		public int getBalance(){

		System.out.println("余额:"+account+"元");

		return account;

		}

		@Override

		public void run() {

		save(1000);

		takeout(1000);

		getBalance();

		}

		

		public static void main(String[] args){

		new BankProcess().start();

}
}

当只要单个线程时,怎么着都不会有问题。
当存在多个线程时,问题就出现了。我们在main方法中启动多个线程来做试验:

public static void main(String[] args){
		for(int i=0;i<5;i++){
			new BankProcess().start();
		}
		
	}

输出结果:

余额:7000元
余额:6000元
余额:5000元
余额:4000元
余额:2000元

而且每次的运行结果都不一样。
为了避免这种情况的发生,线程同步机制应运而生。

3. 线程同步机制

线程同步机制的意思就是多个线程之间互斥的访问共享资源。

针对以上的例子就是说,在同一时间内,只允许一个线程使用共享资源。(这里的“使用”通常情况下都是指的修改操作,读取操作不会修改数据所以不会造成数据混乱的问题)

在第一个例子中我们看到了,单线程的情况下是怎么着都不会有问题的。所以线程同步其实就是强制构造出了一个单线程的安全环境。

4. synchronized 的使用方法

synchronized锁定的永远是对象,而不是代码。只有获取到对象锁的线程才可以执行synchronized控制的那段代码。要理解synchronized的用法关键在于理解锁定的是哪一个对象。

4.1 同步代码块

public void lockObject(Resource r){
	synchronized(r){
	//do something here		
	}
}

锁定的对象:r
若果一个线程获取到了对象r的锁,那么它就可以进入synchronized控制的代码块中。此时JVM会将此对象锁住,直到该线程离开这段代码块时才将锁释放。

如果没有获取到对象r的锁,那么该线程只能在代码块外等待。直到对象的锁被释放才可进入。

此方法适用于多个线程需要访问到同一个Resource实例的情况。如果是多个线程访问Resource的不同的实例的话,则不会构成同步。

实例代码:

package synchronize;

public class Process extends Thread{
	Resource resource = null;
	
	Process(Resource resource){
		this.resource = resource;
	}
	
	public void lockObject(Resource r){
		synchronized(r){
			System.out.println(this.getName() + " acquired the lock");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(this.getName() + " release the lock");
		}
		
	}
	public static void main(String[] args){
		Resource r = new Resource();
		Process process1 = new Process(r);
		Process process2 = new Process(r);
		process1.start();
		process2.start();
	}
	@Override
	public void run() {
		System.out.println(this.getName()+" is running……");
		this.lockObject(this.resource);
		System.out.println(this.getName()+" ends");
	}
}

输出结果:

Thread-1 is running……
Thread-1 acquired the lock
Thread-0 is running……
Thread-1 release the lock
Thread-1 ends
Thread-0 acquired the lock
Thread-0 release the lock
Thread-0 ends

当Thread-1释放了实例对象r的锁之后,Thread-0才获得了锁。

 

4.2 锁定类对象

public class Process{
	public void lockClass(Resource r){
		synchronized(r.getClass()){
			//do something here			
		}
		//or
		synchronized(Resource.class){
			//do something here
		}
	}
}

锁定的对象:Resource的class对象

jvm在装在一个class文件时会生成一个class对象。

因为在java中一个类的class对象是唯一,此时若传入的参数都是Resource类型则需要互斥的访问这段代码。

如果A线程传入的是Resource类型的参数r,B线程传入到是Object类型的参数b,则A、B线程仍可以同时执行此段代码。

4.3 方法修饰符

4.3.1 synchronized作为方法修饰符修饰非静态方法时,锁定的对象是调用者,被同步的代码是整个方法体。

synchronized public void  lock( ) {
	//do something here
}

它等同于如下代码:

public void  lock() {
	synchronized(this){ 
		//do something here
	}
}

如果调用不是同一个实例,则可以同时执行此段代码。

4.3.2 synchronized作为方法修饰符修饰static方法时有些特殊,它锁定的是调用者所属的类的class对象。

public class Process{
	synchronized static public void  lock() {
		//do something here
	}
}


这样实际上就实现了代码的锁定。即同一时间内只有一个线程可以执行此段代码。

因为能调用此方法的只有Process类对象、process实例对象以及其子类的类对象、实例对象。Process类对象和process实例对象在调用此方法时都会将Process类对象锁定,因而不能同时访问。Process子类的类如果没有继承lock方法,它在调用lock方法时实际上还是调用了父类的lock方法,调用者仍然是Process类;如果Process的子类继承了lock方法,则在调用lock方法时调用者就是Process子类,此时不能形成同步。
 4.4 自定义锁对象

如果没有明确的锁对象,而只是想要同步代码块。通常的做法是自定义锁对象。

public class Process{
	static private byte[] lock = new byte[];
	public void  lock() {
			synchronized(lock){
		//do something here
		}
	}
}

自定义一个长度为0的字节数组对象,是系统开销最小的做法。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值