Java线程之synchronized同步————Java学习之路(17)

前言——不进则退啊,博客几天没写,排名第一次下降了,得勤奋更新,不能偷懒。。

欢迎转载,转载请注明来处。

目录

一.演示同步问题

二.分析同步问题产生的原因

三.解决思路

四.synchroniazed关键字

五.使用synchronized同步对象 解决同步问题,防止出现"脏数据"

六.使用synchronized方法 解决同步问题,防止出现"脏数据"


一.演示同步问题

此前,我们用的多线程都是较为简单的例子,都没有涉及到多个线程共享同一个对象或者资源的情况。倘若多线程共享资源的情况下,可能会产生一些“脏数据”。试着考虑下面银行存取款程序:

class Account{
	
	
	String holderName;
	float amount;
	
	public Account() {
		
	}
	public Account(String n ,float x) {
		
		holderName = n;
		amount = x;
	}
	
	public void deposit(float x) {
		
		amount += x;
	}
	
	public void withdraw(float x) {
		
		amount -= x;
	}
	
	public float getAmount() {
		
		return amount;
	}
}
class drawThread implements Runnable{ //用于取300元的线程类
	
	Account  account;
	
	public drawThread(Account x) {
		
		account = x;
	}
	public void run() {
		
		if(account.getAmount() >=300) { //语句1
			
	        account.withdraw(300);               //语句2
			System.out.println("本次取了300后,余额为:" + account.getAmount() );//语句3
	
		}else {
			
			System.out.println("本次余额不足");//语句4
		}
	}
}

我们创建一个账户,初始amount为500元,再创建两个线程,  线程T1用来取款300元,线程T2也用来取款300元,模拟两个人同时取300的情况:

package JavaObjcect;

public class TestAccount {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Account myAccount = new Account("Jian",500);
		
		drawThread  myDraw = new drawThread(myAccount);
		
		Thread T1 = new Thread(myDraw);
		Thread T2 = new Thread(myDraw);
		
		T1.start();
		T2.start();
		

	}

}
运行结果:
本次取了300后,余额为:200.0
本次取了300后,余额为:-100.0

 

二.分析同步问题产生的原因

上面的这个运行结果有多种组合,但上面的这种结果显然是错误的,一个500元的账号是无法两次都分别取出300的。错误的原因是什么呢? 这是由于T1和T2获得CPU的时机是随机的:

T1获得了CPU,判断了语句1,满足了余额>=300元

T2获得了CPU,判断了语句1,也满足了余额>=300元

T1获得了CPU,执行了语句2和语句3,扣了300,余额为200.

T2获得了CPU,执行了语句4,扣了300,余额位-100.

三.解决思路

总体的解决思路:在T1线程访问myAccount这个账户时,禁止T2线程访问myAccount.

T1获得了CPU,判断了语句1,满足了余额>=300元

此时T2获得了CPU,也要访问myAccount,但是T1前面没有访问结束,所以不允许T2访问

T1获得了CPU,执行了语句2和语句3,完成了取钱,余额还剩200. 结束了对myAccount的访问。

T2获得了CPU,此时T1已经访问完了,因此可以访问myAccount了,判断语句1, 余额<300,执行语句4

这样结果就能正确了。

四.synchroniazed关键字

解决问题之前,我们要先介绍一下synchroniazed关键字:

Java语言中的一个关键字,可用来给对象和方法或者代码块加锁,无论锁的是方法还是代码块,本质上锁的都是这个方法或者代码块所属的对象

1.synchronized 同步代码块

synchronized(Object  someObject){

}

someObject对象叫做同步对象,由于Object是所有类的基类,因此任何对象都可以做作为同步对象。一个线程如果要执行同步代码块的内容,必须要先占有同步对象。而someObject在同一时间,只能被一个线程占有,因此间接地同步代码块在同一个时间,只能被同一个进程占有,其他进程如果尝试访问,就会进入等待,直到其他进程释放同步对象。

此时这个someObject对象其实相当于一把钥匙,类似于一个标记,有了这个标记,我才能去执行同步代码块的内容。

2.synchronized方法

public synchronized 返回类型 方法名() {

}

其实本质是锁对象:

public 返回类型 方法名() {

     synchronized(this){ //锁定的是当前方法的对象

         代码语句

    }

}

 

五.使用synchronized同步对象 解决同步问题,防止出现"脏数据"

1.用Object类对象作为同步对象。

package JavaObjcect;

public class TestAccount {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Account myAccount = new Account("Jian",500);
		Object object1 = new Object(); //注意看这句
		
		drawThread  myDraw = new drawThread(myAccount);
		myDraw.object = object1;
		
		Thread T1 = new Thread(myDraw);
		Thread T2 = new Thread(myDraw);
		
		
		T1.start();
		T2.start();
		
		
		

	}

}

class drawThread implements Runnable{ //用于取300元的线程类
	
	Account  account;
	Object   object;
	
	public drawThread(Account x) {
		
		account = x;
	}
	public void run() {
		
	  synchronized(object) {  //任何线程要取300,都要先占有object
		  
		     if(account.getAmount() >=300) {
			
	        	
	        	account.withdraw(300);
	        
			System.out.println("本次取了300后,余额为:" + account.getAmount() );
	
		   }else {
			
			System.out.println("本次余额不足");
		   }
	  }
	}
}


 

2.用Account类对象作为同步对象。

package JavaObjcect;

public class TestAccount {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Account myAccount = new Account("Jian",500);
		
		drawThread  myDraw = new drawThread(myAccount);

		
		Thread T1 = new Thread(myDraw);
		Thread T2 = new Thread(myDraw);
		
		
		T1.start();
		T2.start();
		
		
		

	}

}


class drawThread implements Runnable{ //用于取300元的线程类
	
	Account  account;
	
	public drawThread(Account x) {
		
		account = x;
	}
	public void run() {
		
	  synchronized(account) {  //任何线程要取300,都要先占有当前的account
		  
		     if(account.getAmount() >=300) {
			
	        	
	        	account.withdraw(300);
	        
			System.out.println("本次取了300后,余额为:" + account.getAmount() );
	
		   }else {
			
			System.out.println("本次余额不足");
		   }
	  }
	}
}


 

六.使用synchronized方法 解决同步问题,防止出现"脏数据"

在run()方法前面加上synchronized

package JavaObjcect;

public class TestAccount {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		Account myAccount = new Account("Jian",500);
		
		drawThread  myDraw = new drawThread(myAccount);

		
		Thread T1 = new Thread(myDraw);
		Thread T2 = new Thread(myDraw);
		
		
		T1.start();
		T2.start();
		
		
		

	}

}


class drawThread implements Runnable{ //用于取300元的线程类
	
	Account  account;
	
	public drawThread(Account x) {
		
		account = x;
	}
	public synchronized void run() { //当一个线程正在执行drawThread对象的run方法,另一个线程 
                                      无法执行run方法
	
		  
		     if(account.getAmount() >=300) {
			
	        	
	        	account.withdraw(300);
	        
			System.out.println("本次取了300后,余额为:" + account.getAmount() );
	
		   }else {
			
			System.out.println("本次余额不足");
		   }
	  
	}
}


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值