Java多线程-线程安全

多个线程竞争同一资源时,会出现线程安全问题,
例如计数

    private static class Sequence{

        private static Integer i=0;

        //public static synchronized int getNext(){
        public static int getNext(){
            return ++i;
        }
    }

    @Test
    public void test01() throws InterruptedException {
        //启动10个线程发号
        for (int i = 0; i <10 ; i++) {
            new Thread(new Runnable(){
                @Override
                public void run() {
                    for (int j = 0; j < 5; j++) {
                        //最后输出一行不是50
                        System.out.println(Thread.currentThread().getName()+" i=" +Sequence.getNext());
                    }
                }
            }).start();
        }
        while (Thread.activeCount()>2){
            Thread.sleep(1000);
        }
    }

例如银行取钱场景。

public class Account {
	
	private String accountNo;
	
	private Integer balance;

	public Account(String accountNo, Integer balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}
	//省略get、set方法
}

取钱操作:

public class DrawThread extends Thread {

	private Account account;

	private Integer drawAmount;

	public DrawThread(String name, Account account, Integer drawAmount) {
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}

	@Override
	public void run() {
			if (account.getBalance() >= this.drawAmount) {
				// 余额足够
				System.out.println("吐出钞票");
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 修改余额
				account.setBalance(account.getBalance() - this.drawAmount);
				System.out.println("余额为" + account.getBalance());
			} else {
				// 余额不够
				System.out.println("余额不足");
			}
	}
}

起两个线程,模拟并发取钱操作。

public class TextMain {
	public static void main(String[] args) {
		Account account = new Account("张三", 1000);
		new DrawThread("甲", account, 800).start();
		new DrawThread("已", account, 800).start();
	}
}

这里写图片描述
如图中,账户余额为负数,出现该情况的原因为第一个线程开始取钱,进入后当前线程睡眠,但是没有修改该账户的余额,导致第二个取钱线程能够进入到能取钱的操作,第一个线程执行减少余额操作,第二个线程也执行减少余额操作。导致余额出现了负数。
1 解决方案–同步代码块:
解决该问题的思路为在进入取钱操作时添加同步监视器,线程在执行之前必须获得对同步监视器的锁定。
阻止两个线程对同一共享资源的并发访问。实现加锁-》修改-》释放锁的逻辑。
改进后的取钱操作:

@Override
	public void run() {
		synchronized (this.account) {
			if (account.getBalance() >= this.drawAmount) {
				// 余额足够
				System.out.println("吐出钞票");
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 修改余额
				account.setBalance(account.getBalance() - this.drawAmount);
				System.out.println("余额为" + account.getBalance());
			} else {
				// 余额不够
				System.out.println("余额不足");
			}
		}
	}

修改为同步代码块后,监视account对象,保证当前只有一个线程能对account对象执行修改操作。

2 取钱操作设置为同步方法。
使用synchronize关键字,无需显式的指定同步监视器,同步方法的同步监视器即为this,即吊用该方法的对象。
同一场景,使用同步方法。

public class Account {

	private String accountNo;

	private Integer balance;

	public Account(String accountNo, Integer balance) {
		super();
		this.accountNo = accountNo;
		this.balance = balance;
	}
	//同步方法
	public synchronized void draw(Integer drawAmount) {
		if (this.balance >= drawAmount) {
			// 余额足够
			System.out.println("吐出钞票");
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// 修改余额
			this.balance = this.balance - drawAmount;
			System.out.println("余额为" + this.balance);
		} else {
			// 余额不够
			System.out.println("余额不足");
		}
	}
}

取钱线程:

public class DrawThread extends Thread {

	private Account account;

	private Integer drawAmount;

	public DrawThread(String name, Account account, Integer drawAmount) {
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}

	@Override
	public void run() {
		account.draw(800);
	}
}

测试方法:

public class TextMain {

	public static void main(String[] args) {
		Account account = new Account("张三", 1000);

		new DrawThread("甲", account, 800).start();
		new DrawThread("已", account, 800).start();
	}
}

参考《疯狂java讲义》。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值