Synchronize关键字使用、对象锁的同步与异步

本文通过示例分析了Java中对象锁的同步与异步特性。在非静态同步方法中,不同对象可异步访问,而在静态同步方法中,所有对象共享同一锁。此外,同步方法可防止脏读问题,确保数据一致性。示例展示了synchronized关键字在解决线程安全和资源共享中的作用。
摘要由CSDN通过智能技术生成

示例1、简单来说,此段代码展示的是两个不同的对象访问同一个同步方法的过程,实现依据不同的tag对变量num进行赋值

public class MultiThread {

	private  int num = 0; 
	
	
	public synchronized void printNum(String tag){
		try {			
			if(tag.equals("a")){
				num = 100;
				System.out.println("tag a, set num over!");
				Thread.sleep(1000);
			} else {
				num = 200;
				System.out.println("tag b, set num over!");
			}			
			System.out.println("tag " + tag + ", num = " + num);
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}	
	
	public static void main(String[] args) {
		//这是两个不同的对象
		 MultiThread m1 = new MultiThread();
		 MultiThread m2 = new MultiThread();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				m1.printNum("a");
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override 
			public void run() {
				m2.printNum("b");
			}
		});			
		t1.start();
		t2.start();		
	}	
}

输出结果如下:
tag a, set num over!
tag b, set num over!
tag b, num = 200
tag a, num = 100

        由上述结果可以看出,t1、t2线程对变量num的操作并无互相影响。关键字synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,所以t1/t2两个对象获取的是两个不同的锁,代码中哪个线程先执行synchronized关键字的方法,哪个线程就持有该方法所属对象的锁。

        若对代码做如下改变,将synchronize方法同时变为static修饰的静态方法:

public class MultiThread {

	private static int num = 0; 
	
	
	public static synchronized void printNum(String tag){
		try {			
			if(tag.equals("a")){
				num = 100;
				System.out.println("tag a, set num over!");
				Thread.sleep(1000);
			} else {
				num = 200;
				System.out.println("tag b, set num over!");
			}			
			System.out.println("tag " + tag + ", num = " + num);
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}	

输出结果如下:
tag a, set num over!
tag a, num = 100
tag b, set num over!
tag b, num = 200

        在静态方法上加synchronized关键字,表示锁为类一级别的锁(独占.class类),在这种情况下,即使是两个不同的对象竞争的也是同一把锁。因此当t1进程获取锁时,t2必须等到t1操作完成释放锁之后才能进行下一步操作。

示例2、对象锁的同步异步问题

        当某些资源(对象、变量等)需要与其它进程共享,设计代码时就要实现这些资源的同步性,同时保证程序的线程安全。无共享需求的资源则不必进行同步操作。

        异步即每个线程都是独立运行的,彼此之间互不影响。比如web页面发起的Ajax请求一般为异步请求,待请求数据返回之后再进行加载,却丝毫不影响页面的正常操作。

public class MyObject {

	public synchronized void method1(){
		try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(4000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public void method2(){
		System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		final MyObject mo = new MyObject();
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method1();
			}
		},"t1");
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				mo.method2();
			}
		},"t2");		
		t1.start();
		t2.start();		
	}	
}
输出结果:(即时打印完毕)
t1
t2

原因:t1线程先持有object对象的Lock锁,t2线程可以以异步的方式调用对象中的非synchronized修饰的方法

        将上述代码中method2方法添加synchronize关键字修饰

public synchronized void method2(){
	System.out.println(Thread.currentThread().getName());
}

代码修改后,main方法输出结果:(先打印t1,等待4S之后,再打印t2)
t1
t2

原因: t1线程先持有object对象的Lock锁,t2线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步

3、对象锁同步异步问题的应用--解决脏读问题

public class DirtyRead {

	private String username = "qwert";
	private String password = "123";
	
	public synchronized void setValue(String username, String password){
		this.username = username;
  		//注意此处是为了测试用户名立即更新成功,而密码因某原因未及时更新的情况  					
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		
		this.password = password;
		
		System.out.println("setValue最终结果:username = " + username + " , password = " + password);
	}
	/** 若用synchronized修饰,可避免脏读 */
	public synchronized void  getValue(){
		System.out.println("getValue方法得到:username = " + this.username + " , password = " + this.password);
	}
	
	public static void main(String[] args) throws Exception{
		
		final DirtyRead dr = new DirtyRead();
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				dr.setValue("ad", "456");		
			}
		});
		t1.start();
		Thread.sleep(1000);		
		dr.getValue();
	}
}
   a. getValue方法在无synchronize关键字修饰时,程序输出如下:(等待1秒后先输出第一行,再1秒后输出第二行)
    	getValue方法得到:username = ad , password = 123
    	setValue最终结果:username = ad , password = 456
   b. getValue方法在有synchronize关键字修饰时,程序输出如下:(等待2秒后顺序输出这两行)
   	setValue最终结果:username = ad , password = 456
    	getValue方法得到:username = ad , password = 456

小tips:示例1使用两个对象展示访问synchronize锁在有无static关键字修饰时的区别;示例2则是借助同一对象访问有无synchronize关键字修饰方法时的区别,注意不要搞混啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皓月星辰_w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值