[Java笔记]多线程(二)——线程同步

本文探讨了Java中多线程同步的问题,包括同步代码块的使用,如何解决线程并发操作共享数据的安全隐患,以及同步方法的介绍。同时,提到了操作系统层面的进程同步方法和PV操作等概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

同步代码块

一、问题的出现

       run()方法的方法体不具有同步安全性,因为当多条语句在操作共享数据时,其中一个线程的多条语句只执行了一个部分,还没有执行完,另外一个线程参与了执行,导致共享数据结果的错误。
       例如问题——银行取钱问题

//账户类,该类封装了账户编号和余额两个实例变量
public class Account
{
	// 封装账户编号、账户余额的两个成员变量
	private String accountNo;
	private double balance;
	public Account(){}
	// 构造器
	public Account(String accountNo , double balance)
	{
		this.accountNo = accountNo;
		this.balance = balance;
	}
	// 此处省略了accountNo和balance的setter和getter方法

	// accountNo的setter和getter方法
	public void setAccountNo(String accountNo)
	{
		this.accountNo = accountNo;
	}
	public String getAccountNo()
	{
		return this.accountNo;
	}

	// balance的setter和getter方法
	public void setBalance(double balance)
	{
		this.balance = balance;
	}
	public double getBalance()
	{
		return this.balance;
	}

	// 下面两个方法根据accountNo来重写hashCode()和equals()方法
	public int hashCode()
	{
		return accountNo.hashCode();
	}
	public boolean equals(Object obj)
	{
		if(this == obj)
			return true;
		if (obj !=null
			&& obj.getClass() == Account.class)
		{
			Account target = (Account)obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
}
//取钱的线程类
public class DrawThread extends Thread
{
	// 模拟用户账户
	private Account account;
	// 当前取钱线程所希望取的钱数
	private double drawAmount;
	public DrawThread(String name , Account account
		, double drawAmount)
	{
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}
	// 当多条线程修改同一个共享数据时,将涉及数据安全问题。
	public void run()
	{
		// 账户余额大于取钱数目
		if (account.getBalance() >= drawAmount)
		{
			// 吐出钞票
			System.out.println(getName()
				+ "取钱成功!吐出钞票:" + drawAmount);
			try
			{
				Thread.sleep(1);
			}
			catch (InterruptedException ex)
			{
				ex.printStackTrace();
			}
			// 修改余额
			account.setBalance(account.getBalance() - drawAmount);
			System.out.println("\t余额为: " + account.getBalance());
		}
		else
		{
			System.out.println(getName() + "取钱失败!余额不足!");
		}
	}
}
//取钱,其中实现两个线程对账户进行取钱
public class DrawTest
{
	public static void main(String[] args)
	{
		// 创建一个账户
		Account acct = new Account("1234567" , 1000);
		// 模拟两个线程对同一个账户取钱
		new DrawThread("甲" , acct , 800).start();
		new DrawThread("乙" , acct , 800).start();
	}
}

       运行结果可能会出现-600的情况,这不是期望的结果。

二、解决方法

       引入同步监视器,通用方法是“同步代码块”,同步代码块格式如下:

synchronized(obj){
	···
	//此处的代码就是同步代码块
}

       obj就是同步监视器,线程开始执行同步代码块之前。必须先获得对同步监视器的锁定。任何时候只能有一哥线程可以获得对同步监视器的锁定,当同步代码块执行完之后,该线程会释放对该同步监视器的锁定。

public class DrawThread extends Thread
{
	// 模拟用户账户
	private Account account;
	// 当前取钱线程所希望取的钱数
	private double drawAmount;
	public DrawThread(String name , Account account
		, double drawAmount)
	{
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}
	// 当多条线程修改同一个共享数据时,将涉及数据安全问题。
	public void run()
	{
		// 使用account作为同步监视器,任何线程进入下面同步代码块之前,
		// 必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修改它
		// 这种做法符合:“加锁 → 修改 → 释放锁”的逻辑
		synchronized (account)
		{
			// 账户余额大于取钱数目
			if (account.getBalance() >= drawAmount)
			{
				// 吐出钞票
				System.out.println(getName()
					+ "取钱成功!吐出钞票:" + drawAmount);
				try
				{
					Thread.sleep(1);
				}
				catch (InterruptedException ex)
				{
					ex.printStackTrace();
				}
				// 修改余额
				account.setBalance(account.getBalance() - drawAmount);
				System.out.println("\t余额为: " + account.getBalance());
			}
			else
			{
				System.out.println(getName() + "取钱失败!余额不足!");
			}
		}
		// 同步代码块结束,该线程释放同步锁
	}
}
三、同步方法

        synchronized关键字不仅可以用于同步代码块,还可以修饰方法,该方法成为同步方法,但synchronized不能修饰构造器、成员变量等。

四、操作系统相关

(一)、进程同步的四种方法:
这里引用一位优快云作者的文章[面试]进程同步的四种方法
(二)、PV操作——同步与互斥
(三)、“加锁——修改——释放锁”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值