在这里现有三个线程不安全的实例代码
一、线程不安全的体现
1.模拟抢票
package com.lzy.syn;
/**
* 线程不安全,有负数,重复
* @author Administration
*
*/
public class aUnsafeTest01 implements Runnable {
private int ticketNums=10; //票数
private boolean flag=true;
@Override
public void run() {
while(flag) {
test();
}
}
public void test() {
while(true) {
if(ticketNums<=0) {
break;
}
try {
Thread.sleep(200);//模拟延迟
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
public static void main(String[] args) {
//一份资源
aUnsafeTest01 web=new aUnsafeTest01();
System.out.println(Thread.currentThread().getName());
//多个代理
new Thread(web,"栗").start();
new Thread(web,"李").start();
new Thread(web,"力").start();
}
}
运行结果:
main
力–>10
李–>9
栗–>9
栗–>8
力–>7
李–>6
栗–>5
力–>4
李–>3
栗–>2
力–>1
李–>0
栗–>-1
可以看出在模拟抢票过程中出现-1,以及同一张票被两个人都抢到,这显然是不合常理的。
出现-1的原因是当票数是1的时候力进入test()方法中拿到了1,但由于延迟等原因,还没有运行到
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
这一段代码时,也就是说虽然最后一张票数已经有归属了,但是代码中ticketNums还是等于1,这时候”李“和“栗”也进入Test()方法,所以就会出现负数,
出现相同的票数是因为
如图所示,在票数为9时 李拿走了9,但是在还没有返还票数8的时候,栗也把9拿走了,所以说出现了票数重复
2.模拟取钱
package com.lzy.syn;
/**
* 线程不安全:取钱
* @author Administration
*
*/
public class aUnSafeTrest02 {
public static void main(String[] args) {
//账户
Account account=new Account(100,"礼金");
UnSafeDrawing you=new UnSafeDrawing(account,80,"自己");
UnSafeDrawing wife=new UnSafeDrawing(account,90,"妻子");
you.start();
wife.start();
}
}
//账户
class Account{
int money; //金额
String name;//取钱的人名称
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//模拟取款
class UnSafeDrawing extends Thread{
Account account;//取钱账户
int drawingMoney;//取得钱数
int packetTotal;//放入口袋的总钱数
public UnSafeDrawing(Account account, int drawingMoney,String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
public void run() {
if(account.money-drawingMoney<0) {
return;
}
try {
Thread.sleep(1000);//模拟延迟
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
account.money-=drawingMoney; //账户余额=账户余额-取得钱数
packetTotal+=drawingMoney; //口袋里的钱=口袋里的钱+取得钱数
System.out.println(this.getName()+"-->账户余额为:"+account.money);
System.out.println(this.getName()+"-->口袋的钱为:"+packetTotal);
}
}
运行结果:
自己–>账户余额为:-70
自己–>口袋的钱为:80
妻子–>账户余额为:-70
妻子–>口袋的钱为:90
这里模拟你和你的妻子同时取一张银行卡上的钱。你取80,妻子取90,在运行结果中,账户余额为-70,显然也是不符合常理,这也是线程不安全的表现
3.创建10000个list对象
package com.lzy.syn;
import java.util.ArrayList;
import java.util.List;
/**
* 线程不安全:取钱
* @author Administration
*
*/
public class aUnsafeTrst03 {
public static void main(String[] args) {
List<String>list=new ArrayList<String>();
for(int i=0;i<10000;i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}) .start();
}
System.out.println(list.size());
}
}
运行结果
9982
显然,运行结果并非达到我们的预期,创建10000个,这也是线程不安全。
二、如何解决以上线程不安全问题
解决以上问题,需要线程同步,它包括两种用法:synchronized方法和synchronized块
缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
1.
package com.lzy.syn;
/**
* 线程安全,在并发时帮正数据的正确性、效率尽可能高
* synchronized
* 1.同步方法
* 2.同步块
* @author Administration
*
*/
public class bSafeTest01 {
public static void main(String[] args) {
//一份资源
SafeWeb123061 web=new SafeWeb123061();
//多个代理
new Thread(web,"栗").start();
new Thread(web,"李").start();
new Thread(web,"力").start();
}
}
class SafeWeb123061 implements Runnable{
private int ticketNums=10;
private boolean flag=true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(200);//模拟延迟
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
test();
}
}
//线程安全 同步
public synchronized void test() {
if(ticketNums<=0) {
flag=true;
return;
}
//模拟延时
try {
Thread.sleep(200);//模拟延迟
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
运行结果:
栗–>10
李–>9
力–>8
李–>7
栗–>6
李–>5
力–>4
李–>3
栗–>2
李–>1
在test()方法加入synchronized后,假如"栗"进入Test()方法中,开始取票,Test()方法将会被锁定,其他两个代理只能等”栗“完成后,才能轮到他们,避免同时进去导致票数出现问题。
注意:这里锁得不是test()方法,而是锁的对象web,要知道test()中操作的flag,ticketNums都是属于SafeWeb123061类的。
2.模拟取钱
package com.lzy.syn;
/**
* 线程不安全:取钱
* @author Administration
*
*/
public class bSafeTrst02 {
public static void main(String[] args) {
//账户
Account account=new Account(100,"礼金");
Drawing you=new Drawing(account,80,"自己");
Drawing wife=new Drawing(account,90,"妻子");
you.start();
wife.start();
}
}
class Account{
int money; //金额
String name;//名称
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//模拟取款
class Drawing extends Thread{
Account account;//取钱账户
int drawingMoney;//取得钱数
int packetTotal;//口袋的总数
public Drawing(Account account, int drawingMoney,String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
public void run() {
test();
}
public void test() {
if(account.money==0) {
return; //提高性能
}
synchronized(account) {
if(account.money-drawingMoney<0) {
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
account.money-=drawingMoney;
packetTotal+=drawingMoney;
System.out.println(this.getName()+"-->账户余额为:"+account.money);
System.out.println(this.getName()+"-->口袋的钱为:"+packetTotal);
}
}
}
运行结果:
自己–>账户余额为:20
自己–>口袋的钱为:80
由于你取了80,卡里省20,所以妻子取90判定失败
如果模拟抢票的代码懂了。在这你可能认为只要在模拟取钱的Test()前加上synchronized就可以解决问题了,但这是错误的,因为锁资源没有锁对,test()中的account.money,drawingMone,packetTotal都是属于Drawing类的, Drawing相当于取款机,你锁一个取款机显然是不对的,锁的应该是你的账户account,所以这就用到”同步块“
同步块:synchronized(obj){ },obj称之为同步监视器
1.obj可以是任何对象,但是推荐使用共享资源作为同步监视器
2.同步方法中无需指定同步监视器,因为同步方法的同步监视器时this即该对象本身,或class即类的模子
3.创建对象
package com.lzy.syn;
import java.util.ArrayList;
import java.util.List;
/**
* 线程安全,容器
* @author Administration
*
*/
public class bSafeTrst03 {
public static void main(String[] args) {
List<String>list=new ArrayList<String>();
for(int i=0;i<10000;i++) {
new Thread(()->{
synchronized(list) {
list.add(Thread.currentThread().getName());
}
}) .start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(list.size());
}
}
这里用到的依然是同步块
三、提高性能
package com.lzy.syn;
import java.util.ArrayList;
import java.util.List;
/**
* 线程安全:在并发时保证数据的正确性、效率尽可能高
* @author Administration
*
*/
public class bSynBlockTest04 {
public static void main(String[] args) {
//一份资源
SynWebd12306 web =new SynWebd12306();
//多个代理
new Thread(web,"栗").start();
new Thread(web,"李").start();
new Thread(web,"力").start();;
}
}
class SynWebd12306 implements Runnable{
private int ticketNums=10;
private boolean flag=true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
test5();
}
}
//线程安全:尽可能锁定合理的范围(不是指代码 指数据的完整性)
//double checking
public void test5() {
if(ticketNums<=0) {//考虑的是没有票的情况
flag = false;
return ;
}
synchronized(this) {
if(ticketNums<=0) {//考虑最后的1张票
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程不安全 范围太小锁不住
public void test4() {
synchronized(this) {
if(ticketNums<=0) {
flag = false;
return ;
}
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
//线程不安全 ticketNums对象在变
public void test3() {
synchronized((Integer)ticketNums) {
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程安全 范围太大 -->效率低下
public void test2() {
synchronized(this) {
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}
//线程安全 同步
public synchronized void test1() {
if(ticketNums<=0) {
flag = false;
return ;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}