ReentrantLock,多线程并发修改一个数据,结果重复数据,锁没有起作用

来者 2019-03-12 01:27:04
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SynchronizedThread {
public void useThread() throws InterruptedException {
final Bank bank = new Bank();
NewThread new_thread = new NewThread(bank);
System.out.println("线程1");
Thread thread1 = new Thread(new_thread);
thread1.start();
System.out.println("线程2");
Thread thread2 = new Thread(new_thread);
thread2.start();

thread1.join();
thread2.join();
}

public static void main(String[] args) throws InterruptedException {
SynchronizedThread st = new SynchronizedThread();
st.useThread();
}
}

class Bank {
private int account = 100;
private static final Lock lock1 = new ReentrantLock();

public int getAccount() {
lock1.lock();
try{
return account;
}finally {
lock1.unlock();
}
}


public void save(int money) {
lock1.lock();
try {
account += money;
} finally {
lock1.unlock();
}
}

}

class NewThread implements Runnable {
private Bank bank;

public NewThread(Bank bank) {
this.bank = bank;
}

@Override
public void run() {
for (int i = 0; i < 10; i++) {
bank.save(10);
System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName());
}
}
}

====================打印的结果数据有重复数据=============
线程1
线程2
0账户余额为:120 线程名称:Thread-0
0账户余额为:120 线程名称:Thread-1

1账户余额为:130 线程名称:Thread-0
1账户余额为:140 线程名称:Thread-1
2账户余额为:150 线程名称:Thread-0
2账户余额为:160 线程名称:Thread-1
3账户余额为:170 线程名称:Thread-0
3账户余额为:180 线程名称:Thread-1
4账户余额为:190 线程名称:Thread-0
4账户余额为:200 线程名称:Thread-1
5账户余额为:210 线程名称:Thread-0
5账户余额为:220 线程名称:Thread-1
6账户余额为:230 线程名称:Thread-0
6账户余额为:240 线程名称:Thread-1
7账户余额为:250 线程名称:Thread-0
7账户余额为:260 线程名称:Thread-1
8账户余额为:270 线程名称:Thread-0
8账户余额为:280 线程名称:Thread-1
9账户余额为:290 线程名称:Thread-0
9账户余额为:300 线程名称:Thread-1
...全文
658 19 打赏 收藏 转发到动态 举报
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
zzwpublic 2020-02-16
  • 打赏
  • 举报
回复
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bank.save(10);
System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName());
}
}

实际上 金额是正确的,只不过显示 的有问题。当bank.save(10);后lock已经释放,在System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName()); 还没执行时,被别的线程抢走,所以这个输出直到下次此线程再取到cpu时间时才输出。

public void save(int money) {
//lock1.lock();
try {
account += money;
} finally {
System.out.println(account); /输出金额放到这里,加不加lock结果很明显,另外多起几个线程效果更明显
//lock1.unlock();
}
}
小短脚 2019-03-15
  • 打赏
  • 举报
回复
引用 10 楼 来者 的回复:
[quote=引用 9 楼 我自横刀香甜笑 的回复:] [quote=引用 8 楼 来者 的回复:] [quote=引用 4 楼 我自横刀香甜笑 的回复:] 把锁放在NewThread里试试看。 @Override public void run() { for (int i = 0; i < 10; i++) { lock1.lock(); try{ bank.save(10); System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName()); } finally{ lock1.unlock(); } } } 可以参看下。
假如,我要给我某一个类,比如当前这个:Bank 一些方法加锁,避免并发的问题,像这种我应该怎么做?[/quote] 你的每个方法里加锁,实际上每次释放,下一次是谁拿到这个锁都是不确定的,所以你得在一个锁定期间里把所有的事都干完了再释放,不然就可能出现争取不到锁的问题。[/quote] 假如,我做的事情,有一二三步,除了把他们放到一起做之外,还有更好的办法让修改的数据保持原子性吗?[/quote] 保证account本身操作的原子性,而不管任何调用方何时何地调用才符合开发需求的话,我也觉得如果把锁加在调用方不太妥当,并且在for中加try catch finally在做开发的时候并不符合规范,我推荐的做法不使用ReentranLock,使用AtomicInteger,在赋值的时候就返回新值,这样对account的原子操作在本身,改造如下 首先对Bank类的改造如下:

public class Bank {
    private AtomicInteger account = new AtomicInteger(100);

    public int save(int money) {
        return account.AddAndGet(money);
    }
}
NewThread类的改造:

public class NewThread implements Runnable {

    private Bank bank;

    public NewThread(Bank bank) {
        this.bank = bank;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i + "账户余额为:" + bank.save(10) + " 线程名称:" + Thread.currentThread().getName());
        }
    }
}
其他不变,不管你有多少个并发线程,对account的操作都是保证原子操作的,打印出来的信息可能不是按金额从小到大排列的,但是多个线程都往一个控制台打印肯定会有这样的情况
码农小麦 2019-03-14
  • 打赏
  • 举报
回复
你这两个方法是安全的,但是组合起来使用,不是安全的。
小短脚 2019-03-14
  • 打赏
  • 举报
回复
你的Bank的操作不是原子性的,两个线程中的lock1不是同一个对象,所以最简单的办法是把Bank中的两个方法中的lock1去掉,把private int account = 100改为private AtomicInteger account = new Atomicinteger(100) getAccount方法中为return account.get() save方法中为account.getAndAdd(money) 最后就是这样的: class Bank { private static AtomicInteger account = new AtomicIntege(100); public Integer getAccount(){ return account.get(); } public void save(Integer money) { account.getAndAdd(money) } 试一下你的多线程看看结果是不是对了…
pstrunner 2019-03-14
  • 打赏
  • 举报
回复
不知道楼主说重复数据是那部分重复了
  • 打赏
  • 举报
回复
引用 10 楼 来者 的回复:
[quote=引用 9 楼 我自横刀香甜笑 的回复:]
[quote=引用 8 楼 来者 的回复:]
[quote=引用 4 楼 我自横刀香甜笑 的回复:]
把锁放在NewThread里试试看。
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock1.lock();
try{
bank.save(10);
System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName());
}
finally{
lock1.unlock();
}
}
}

可以参看下。


假如,我要给我某一个类,比如当前这个:Bank 一些方法加锁,避免并发的问题,像这种我应该怎么做?[/quote]
你的每个方法里加锁,实际上每次释放,下一次是谁拿到这个锁都是不确定的,所以你得在一个锁定期间里把所有的事都干完了再释放,不然就可能出现争取不到锁的问题。[/quote]

假如,我做的事情,有一二三步,除了把他们放到一起做之外,还有更好的办法让修改的数据保持原子性吗?[/quote]
你不一起干完了那就有锁被别人抢占并处理的风险。
qq_39936465 2019-03-13
  • 打赏
  • 举报
回复

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class test10 {
	public static void main(String[] arg0) {
		Bank bank = new Bank();
		Save s = new Save(bank);
		Thread thread1 = new Thread(s);
		Thread thread2 = new Thread(s);
		thread1.start();
		thread2.start();
	}

}

class Bank {
	private int account = 100;

	public int getAccount() {
		return account;
	}

	public void setAccount(int account) {
		this.account = account;
	}

}

class Save implements Runnable {
	Bank bank = null;
	Lock lock = new ReentrantLock();

	public Save(Bank bank) {
		super();
		this.bank = bank;
	}

	public void run() {
		for (int i = 0; i < 10; i++) {
			lock.lock();
			//要做几步都在try代码块中完成
			try {
				bank.setAccount(bank.getAccount() + 10);
				System.out.println(i + "账户余额为:" + bank.getAccount() + "线程名称:" + Thread.currentThread().getName());
				Thread.sleep(1000);//不加入sleep太快了,另个线程基本抢不到
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				//最后在finally中释放锁
				lock.unlock();
			}
		}

	}
}

运行结果: 0账户余额为:110线程名称:Thread-0 0账户余额为:120线程名称:Thread-1 1账户余额为:130线程名称:Thread-0 1账户余额为:140线程名称:Thread-1 2账户余额为:150线程名称:Thread-0 2账户余额为:160线程名称:Thread-1 3账户余额为:170线程名称:Thread-0 3账户余额为:180线程名称:Thread-1 4账户余额为:190线程名称:Thread-0 4账户余额为:200线程名称:Thread-1 5账户余额为:210线程名称:Thread-0 5账户余额为:220线程名称:Thread-1 6账户余额为:230线程名称:Thread-0 6账户余额为:240线程名称:Thread-1 7账户余额为:250线程名称:Thread-0 7账户余额为:260线程名称:Thread-1 8账户余额为:270线程名称:Thread-0 9账户余额为:280线程名称:Thread-0 8账户余额为:290线程名称:Thread-1 9账户余额为:300线程名称:Thread-1 这样不知道合不合你要求
来者 2019-03-13
  • 打赏
  • 举报
回复
引用 11 楼 西门儿吹吹雪 的回复:
private int account = 100; 把account 加上volatile
加volatile之后,出现数丢失的情况,110没有了,200出现重复 0账户余额为:120 线程名称:Thread-1 1账户余额为:130 线程名称:Thread-1 2账户余额为:140 线程名称:Thread-1 3账户余额为:150 线程名称:Thread-1 4账户余额为:160 线程名称:Thread-1 5账户余额为:170 线程名称:Thread-1 6账户余额为:180 线程名称:Thread-1 7账户余额为:190 线程名称:Thread-1 8账户余额为:200 线程名称:Thread-1 0账户余额为:200 线程名称:Thread-0 9账户余额为:210 线程名称:Thread-1 1账户余额为:220 线程名称:Thread-0 2账户余额为:230 线程名称:Thread-0 3账户余额为:240 线程名称:Thread-0 4账户余额为:250 线程名称:Thread-0 5账户余额为:260 线程名称:Thread-0 6账户余额为:270 线程名称:Thread-0 7账户余额为:280 线程名称:Thread-0 8账户余额为:290 线程名称:Thread-0 9账户余额为:300 线程名称:Thread-0
凤鸣86 2019-03-13
  • 打赏
  • 举报
回复
private int account = 100; 把account 加上volatile
来者 2019-03-13
  • 打赏
  • 举报
回复
引用 9 楼 我自横刀香甜笑 的回复:
[quote=引用 8 楼 来者 的回复:] [quote=引用 4 楼 我自横刀香甜笑 的回复:] 把锁放在NewThread里试试看。 @Override public void run() { for (int i = 0; i < 10; i++) { lock1.lock(); try{ bank.save(10); System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName()); } finally{ lock1.unlock(); } } } 可以参看下。
假如,我要给我某一个类,比如当前这个:Bank 一些方法加锁,避免并发的问题,像这种我应该怎么做?[/quote] 你的每个方法里加锁,实际上每次释放,下一次是谁拿到这个锁都是不确定的,所以你得在一个锁定期间里把所有的事都干完了再释放,不然就可能出现争取不到锁的问题。[/quote] 假如,我做的事情,有一二三步,除了把他们放到一起做之外,还有更好的办法让修改的数据保持原子性吗?
  • 打赏
  • 举报
回复
引用 8 楼 来者 的回复:
[quote=引用 4 楼 我自横刀香甜笑 的回复:]
把锁放在NewThread里试试看。
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock1.lock();
try{
bank.save(10);
System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName());
}
finally{
lock1.unlock();
}
}
}

可以参看下。


假如,我要给我某一个类,比如当前这个:Bank 一些方法加锁,避免并发的问题,像这种我应该怎么做?[/quote]
你的每个方法里加锁,实际上每次释放,下一次是谁拿到这个锁都是不确定的,所以你得在一个锁定期间里把所有的事都干完了再释放,不然就可能出现争取不到锁的问题。
来者 2019-03-13
  • 打赏
  • 举报
回复
引用 4 楼 我自横刀香甜笑 的回复:
把锁放在NewThread里试试看。 @Override public void run() { for (int i = 0; i < 10; i++) { lock1.lock(); try{ bank.save(10); System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName()); } finally{ lock1.unlock(); } } } 可以参看下。
假如,我要给我某一个类,比如当前这个:Bank 一些方法加锁,避免并发的问题,像这种我应该怎么做?
  • 打赏
  • 举报
回复


引用 5 楼 来者 的回复:
[quote=引用 1 楼 企鹅爱吃方便面 的回复:]
System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName());
应该是这一句的同步问题,虽然你在useThread里面加了join保持同步,但是Thread1和Thread0是异步的,输出重复数据只能说明在两个线程里面读到了旧的数据或者更新的数据。


我去掉了,还是无法进行同步[/quote]

并不是让你去掉,我提到的这块代码并不是原子性的代码,你只需要加个同步块就好了
来者 2019-03-13
  • 打赏
  • 举报
回复
引用 2 楼 stacksoverflow 的回复:
你的bank.save(10);和bank.getAccount()加一起不是原子的,所以会出现打印重复。 比如 线程1:save(10) 线程2:save(10) 线程1:getAccount() 输出120 线程2:getAccount() 输出120
我这个代码怎么修改,才能做到我想要的结果呢
来者 2019-03-13
  • 打赏
  • 举报
回复
引用 1 楼 企鹅爱吃方便面 的回复:
System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName()); 应该是这一句的同步问题,虽然你在useThread里面加了join保持同步,但是Thread1和Thread0是异步的,输出重复数据只能说明在两个线程里面读到了旧的数据或者更新的数据。
我去掉了,还是无法进行同步
  • 打赏
  • 举报
回复
把锁放在NewThread里试试看。
@Override
public void run() {
for (int i = 0; i < 10; i++) {
lock1.lock();
try{
bank.save(10);
System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName());
}
finally{
lock1.unlock();
}
}
}

可以参看下。
qq_39936465 2019-03-12
  • 打赏
  • 举报
回复
亲你设置了2次lock,当线程0抢到锁后 执行save,然后释放锁,在执行get时还要再次抢锁,但是被线程1抢到了,线程1执行了save,然后释放锁,这时又被线程0抢到锁后打印出来,后释放锁后又被线程1抢到锁后打印出来。

我觉得应该把锁放在run的循环内比较合适,不知道符不符合你的要求

public void run() {
for (int i = 0; i < 10; i++) {
lock1.lock();
try{
bank.save(10);
System.out.println(i + "账户余额为:"
+bank.getAccount()+" 线程名称:"+Thread.currentThread().getName());
}finally {
lock1.unlock();
}
}
}









stacksoverflow 2019-03-12
  • 打赏
  • 举报
回复
你的bank.save(10);和bank.getAccount()加一起不是原子的,所以会出现打印重复。 比如 线程1:save(10) 线程2:save(10) 线程1:getAccount() 输出120 线程2:getAccount() 输出120
  • 打赏
  • 举报
回复
System.out.println(i + "账户余额为:" +bank.getAccount()+" 线程名称:"+Thread.currentThread().getName());
应该是这一句的同步问题,虽然你在useThread里面加了join保持同步,但是Thread1和Thread0是异步的,输出重复数据只能说明在两个线程里面读到了旧的数据或者更新的数据。

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧