前言——不进则退啊,博客几天没写,排名第一次下降了,得勤奋更新,不能偷懒。。
欢迎转载,转载请注明来处。
目录
五.使用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("本次余额不足");
}
}
}