首页 > 技术文章 > java线程同步的演示

oldoldcoder 2021-11-06 09:35 原文

线程同步

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()方法?

推荐阅读