老生常谈的一个问题,就bank的那个例子,同一张卡同时取钱,并发2个,每个取800,但卡里只有1000,不能让2个人同时操作.这里就需要线程同步.比如下面的DEMO.
首先定义一个账户类:
package org.credo.thread.tongbu;
public class Account {
private String account;
private double money;
public Account(String account,double money){
this.account=account;
this.money=money;
}
//根据account重写hashcode和equals方法.
public int hasCode(){
return account.hashCode();
}
public boolean equals(Object obj){
if(this==obj){
return true;
}
if(obj!=null && obj.getClass()==Account.class){
Account target=(Account)obj;
return target.getAccount().equals(account);
}
return false;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
再就是业务类:
package org.credo.thread.tongbu;
public class Draw extends Thread{
private Account account;
private double hopeGetMoney;
public Draw(String name,Account account,double hopeGetMoney){
super(name);
this.account=account;
this.hopeGetMoney=hopeGetMoney;
}
//当多个线程修改同一个共享数据的时候,将会引发数据问题.
public void run(){
if(account.getMoney() >= hopeGetMoney){
System.out.println(account.getAccount()+"---"+getName()+"取钱成功,取出金额为:"+hopeGetMoney);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
//修改金额
account.setMoney(account.getMoney()-hopeGetMoney);
System.out.println("余额为:"+account.getMoney());
}else{
System.out.println("余额不足,取款失败!");
}
}
}
测试类:
package org.credo.thread.tongbu;
public class NoSynchronizedTest {
public static void main(String[] args) {
//create a new account
Account account=new Account("印度人", 1000);
new Draw("印度人A", account, 800).start();
new Draw("印度人B", account, 800).start();
}
}
结果是:
印度人---印度人B取钱成功,取出金额为:800.0
印度人---印度人A取钱成功,取出金额为:800.0
余额为:200.0
余额为:-600.0
自然是错的,毕竟这不是信用卡.
在这种场景下,account这个账户必然要是一定时间内只能一个人操作的,是需要线程的同步的.
1.同步代码块
如下code:在业务类的run方法总加入synchronized代码块.
public void run(){
//使用account作为同步监视器,任何线程进入下面同步代码块之前必须先获得对account账户的锁定.
//其他线程无法获得锁,也就无法修改
//这种做法符合, 加锁---修改---释放锁 的逻辑.
synchronized (account) {
if(account.getMoney() >= hopeGetMoney){
System.out.println(account.getAccount()+"---"+getName()+"取钱成功,取出金额为:"+hopeGetMoney);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
//修改金额
account.setMoney(account.getMoney()-hopeGetMoney);
System.out.println("余额为:"+account.getMoney());
}else{
System.out.println("余额不足,取款失败!");
}
}
//结束,释放同步锁.
}
印度人---印度人A取钱成功,取出金额为:800.0
余额为:200.0
余额不足,取款失败!
synchronized(obj){
//此处代码就是同步代码块
}
括号里的obj就是同步监视器,上面的含义就是,线程开始执行同步代码前,必须先获得对同步监视器的锁定.当完成同步代码块后会释放锁定.
Java允许任何对象充当同步监视器,但实际上,我们必须使用可能被并发访问的贡献资源充当同步监视器.如上面代码的 account.
2.同步方法
与同步代码块对应的,java的多线程的安全支持还提供了同步方法,同步方法就是使用synchronized关键字去修饰某个方法.对于同步方法而言,其同步监视器就是this,就是该对象本身.
用过使用同步方法可以很方便的实现线程安全的类,线程安全的类具有如下特征:
- 该类的对象可以被多个线程安全的访问
- 每个线程调用该对象的任意方法都会得到正确的结果.
- 每个线程调用该对象任意方法后,该对象状态依然保持合理状态.
synchronized关键字可以修饰方法,代码块,但不能修改构造器,属性等.
在Account类中加入下面代码:
public synchronized void process(double hopeGetMoney){
if(money >= hopeGetMoney){
System.out.println(Thread.currentThread().getName()+"取钱成功,取出金额为:"+hopeGetMoney);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
//修改金额
this.money=money-hopeGetMoney;
System.out.println("余额为:"+money);
}else{
System.out.println("余额不足,取款失败!");
}
}
修改业务类中的run()方法为:
public void run(){
account.process(hopeGetMoney);
}
印度人A取钱成功,取出金额为:800.0
余额为:200.0
余额不足,取款失败!
比如StringBuffer是多线程安全的,但性能不好.StringBuilder有好的性能,但线程是不安全的.
3.释放同步监视器的锁定
释放:
- 1.当前线程的同步方法,同步代码块正常运行结束.
- 2.当前线程在同步代码块,同步方法中遇到break,return什么的,
- 3发生异常,错误.
- 4.程序执行了同步监视器对象的wait()方法,线程暂停并释放同步监视器.
不释放:
- 1.在期内执行了Thread.sleep,thread.yield方法.不会放开.
- 2.用了suspend挂起,当然不应该用suspend和resume方法.
从JAVA5开始.有了Lock.
先贴代码,上一章最后的代码,不变,只修改account代码如下:
public class Account {
private String account;
private double money;
private final ReentrantLock lock=new ReentrantLock();
public Account(String account,double money){
this.account=account;
this.money=money;
}
public void process(double hopeGetMoney){
try {
lock.lock();
if(money >= hopeGetMoney){
System.out.println(Thread.currentThread().getName()+"取钱成功,取出金额为:"+hopeGetMoney);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
//修改金额
this.money=money-hopeGetMoney;
System.out.println("余额为:"+money);
}else{
System.out.println("余额不足,取款失败!");
}
} finally {
lock.unlock();
}
}
//根据account重写hashcode和equals方法.
使用的是lock.输出为:
印度人A取钱成功,取出金额为:800.0
余额为:200.0
余额不足,取款失败!
Lock是控制多个线程对共享资源进行访问的工具.通常,锁提供了对共享资源的独占访问,每次只能有一个线程对lock对象加锁,线程开始访问共享资源之前应获得Lock对象.
同步锁里面需要理解的东西也很多,这里........尼玛快过年了,实在没心情搞了.
===============死锁==================
死锁就是两个线程互相等待对方释放同步监视器时就回发生死锁.Java虚拟机没有检测,也没有采取措施来处理死锁情况,所以多线程编程时要注意这个情况.发生死锁的时候,整个程序没错误没异常,没提示,只是说有线程都处于阻塞状态,无法继续.