java线程同步的演示
线程同步
1.问题引出:
对于同一个银行账户在多个地点同一时间的取钱模拟
具体代码我们看下面写的:
account账户类:
package thread;
public class Account
{
//一个账户名和一个余额
private String acNo;
private int balance;
Account(String ac,int bal)
{
acNo=ac;
balance=bal;
}
//set方法和get方法
public void setAccountNo(String no)
{
acNo=no;
}
public void setBalance(int bal)
{
balance=bal;
}
public String getAccoutNo()
{
return acNo;
}
public int getBalance()
{
return balance;
}
//两个使用覆盖方法
@Override
public int hashCode() {
return acNo.hashCode();
}
@Override
public boolean equals(Object obj) {
if(obj==this)
return true;
if(obj!=null&&obj.getClass()==Account.class)
{
Account target=(Account)obj;
return target.equals(acNo);
}
return false;
}
}
取钱类:
package thread;
public class DrawAccount extends Thread
{
// 用户的账户
private Account acn;
private double drawMoney;
public DrawAccount(String name,Account ac,double reduce)
{
super(name);
acn=ac;
drawMoney=reduce;
}
public void run()
{
if(acn.getBalance()>drawMoney)
{
//取出钞票
System.out.println(this.getName()+" 取钱成功,取出了: "+drawMoney);
//增加错误率
/*try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//减去钞票
acn.setBalance((int) (acn.getBalance()-drawMoney));
//打印余额
System.out.println("余额为: "+acn.getBalance());
}
else {
System.out.println("取钱失败,余额是 "+acn.getBalance());
}
}
}
ps:值得注意的是那段注释的代码,这段代码可以增加失误率
测试类:
package thread;
public class DrawTest
{
public static void main(String[]__)
{
Account he=new Account("123", 1600);
new DrawAccount("取钱机器1", he, 1000).start();
new DrawAccount("取钱机器2", he, 1000).start();
}
}
多次运行之后会看到下面 情况:
我们这里稍微做一下解释:
- 如果我们之前了解过线程同步的问题,应该会对这两个概念比较明确:原子性和可见性,对于这里的取钱操作,判断,扣费这两个动作不是原子操作,因此存在当一个线程已经判断成功之后,CPU的使用权被另外一个线程夺取,进行判断,这个时候账户还没有扣费,所以就造成了出现账户余额为负数的情况。
2.同步做法
为了解决上述问题,java的多线程使用了同步监视器来解决,同步代码块的语法格式:
synchronized(obj)
{
//代码块
}
括号中的obj就是同步监视器,上面代码的含义就是在线程开始执行之前,必须现获得同步监视器的锁定,任何一个线程在运行同步代码块前一定要先获得锁,流程:"加锁"->"修改锁"->"释放锁"
obj这个锁一般使用共享的变量来当锁,当程序运行到synchronize的时候,会先判断里面的obj锁是否已经被别人获取。
另外还有同步方法
public synchronized void run
{
//代码块
}
这个时候相当于使用this作为obj来充当锁
可变类的线程安全是通过降低效率来获得的,为了减少负面影响:
- 不要对所有的资源进行同步,只需要对于竞争资源(共享资源加锁)
- 可变类有两种运行环境:单线程和多线程的时候,应该提供两种版本的可变类,单线程效率性和多线程安全性。
3.关于锁的释放
下面这几种情况下,会释放锁:
- 当前线程的同步方法,同步代码块执行结束,当前线程释放锁
- 遇到break,return语句释放锁
- 遇到了未处理的ERROR和EXCEPTION就会释放锁
- 线程执行同步代码块的时候,程序执行了同步监视器对象的wait()方法,则当前的线程暂停,并释放同步监视器(ps:wait()方法是继承object类的方法)
下面这几种情况,不会释放锁,可能造成死锁:
- 程序调用sleep()方法和yield()方法不会释放锁
- 线程调用这个线程的suspend()方法将该线程挂起,这个线程不会释放同步监视器
补充昨天学了就忘记了join()方法:阻塞调用线程的线程,等待被调用的线程执行结束之后,调用线程的线程才会继续执行,底层使用了wait()方法?