线程不安全的几种表现形式以及解决方式
案例一:买票问题
实现思想run()方法中不断地在买票直到票被买完为止先假设有三中人同时间买票分别是 黄牛,乘客,小白
首先我们先做一个买票方法,模拟乘客买票
private int buy() throws InterruptedException {
if (ticks<=0){
flag=false;
return 0;
}
Thread.sleep(10);
//防止cpu执行速度太快还没等其它人买票就将票买完了
System.out.println(Thread.currentThread().getName()+"==>"+ticks--);
return 0;
}
然后将run() 方法进行重写
private int ticks=10;//定义当前票数
boolean flag=true; //循环终止flag
@Override
public void run() {
while (true){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
最后在开启线程进行测试
public static void main(String[] args) {
buyTicket buyTicket=new buyTicket();
//将三个角色丢进同一根线程中
new Thread(buyTicket,"黄牛").start();
new Thread(buyTicket,"乘客").start();
new Thread(buyTicket,"小白").start();
}
首先我们会以为票卖完的时候线程就会自动终止,但是并非如此
黄牛可真是猛啊居然买到了-1张票,匪夷所思。好吧然我们画一张图看一下。
都是假象惹的祸
现在让我们给buy方法加一把锁
private synchronized int buy()
运行代码
现在的买票1正常了
案例二:银行取钱模拟
我们想的是假如我从银行中取走了50元现在账户中信息只有50元,所以女朋友不会取到钱,或者女朋友直接从银行中取走了100元,而我没有取到钱。所以我根据这个思路写一个方法来模拟。
public void run() {
if (account.money-drawingMoney<0){
System.out.println("钱不够取不了");
return;
}
try {
Thread.sleep(100);//模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money=account.money-drawingMoney;
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"余额为"+account.money);
System.out.println(this.getName()+"手里面的钱"+nowMoney);
}
好了现在我们让我和女朋友同时取钱
public static void main(String[] args) {
Account account=new Account(100,"家森的私房钱");
Drawing you=new Drawing(account,50,"你");
Drawing girlfriend=new Drawing(account,100,"girlfriend");
you.start();
girlfriend.start();
}
有点惊讶,我和女朋友都有钱,但是好像成了贷款。。。这是明显的线程不安全。
解决办法:给run方法加一把锁 ?
看来加锁后还是不行那我们得换一种方法。
同步块 :synchronized(obj) { }
Obj称之为同步监视器:
- Obj可以是任何对象,推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法中得监视器就是this,对象本身,或者反射中得class;
同步监视器得执行过程:
3. 第一个线程访问,锁定同步监视器,执行其中代码
4. 第二个线程访问 ,发现同步监视器锁定,无法进行访问
5. 当第一个线程访问结束时,解锁同步监视器
6. 第二个线程再次访问发现没有锁,锁定并访问
我们发现Account是共享资源所以我们将account作为同步监视器
synchronized (account){
if (account.money-drawingMoney<0){
System.out.println("钱不够取不了");
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money=account.money-drawingMoney;
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"余额为"+account.money);
System.out.println(this.getName()+"手里面的钱"+nowMoney);
}
案例三:不安全线程
我们都知道ArrayList集合是线程不安全的,但是他是如何表现得不安全的呐?
List<String> list =new ArrayList<String>();
for (int i = 0; i <= 99999; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();//每进行一次循环进入一个线程
}
Thread.sleep(3000);
System.out.println(list.size());
理论上当打印出list.size()应该为100000。
线程少了,不安全事件发生了,但是怎么解决呐?可以给list加一个同步监视器
问题又解决了。
好了这就是本文章的全部内容了,,,感谢阅读。。。。。。