非线程安全
public class SafeThread {
public static void main(String[] args) {
Safe12306 t=new Safe12306(100);
Thread thread1=new Thread(t,"小红");
Thread thread2=new Thread(t,"小白");
Thread thread3=new Thread(t,"小黑");
thread1.start();
thread2.start();
thread3.start();
}
}
class Safe12306 implements Runnable
{
private Integer tickets;
private boolean flag=true;
public Safe12306(Integer tickets) {
this.tickets = tickets;
}
@Override
public void run() {
while (flag)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
BuyTicket();
}
}
private void BuyTicket() {
if (tickets<=0)
{
flag=false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" --> Ticket"+tickets--);
}
}
模拟了延时之后,可能会出现多个人买到同一张票或者有人买到了不存在的票的情况。
结果
结果
这就是线程不安全的情况。
为什么会出现这两种情况,首先看有人买到不存在的票的情况,当只有最后一张票的时候,小黑小红小白三人都进入了BuyTicket方法此时TICKET=1,此时执行sleep方法,模拟网络延迟,小黑先进入先sleep,到了小红小红也sleep,然后小白也sleep,然后小黑重新被分配CPU获得最后一票,而小红和小白只能获得不存在的票。
多人买到同一张票的情况,线程的机制是从主存中复制数据到自己的工作区,然后再把自己更改过的内容覆盖到主存,多人买到同一张票应该是小黑复制到了自己的工作区还没有覆盖主存,小红和小白就已经复制主存中的内容到自己的工作区。
线程安全
- 实现线程同步
1.同步方法
给BuyTicket方法加上synchronized关键字。
private synchronized void BuyTicket() {
try {
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tickets<=0)
{
flag=false;
return;
}
System.out.println(Thread.currentThread().getName()+" --> Ticket"+tickets--);
}
synchronized看似锁的是方法实际是当前对象(this)。更改了哪个对象的内容就要锁哪个对象,锁方法的开销较大,一般不推荐锁方法。
2.同步代码块
public class SafeFrame {
public static void main(String[] args) {
HashMap <String ,Integer> map=new HashMap<>();
ConcurrentHashMap<String ,Integer>cmap=new ConcurrentHashMap<>();
LinkedList<String> list=new LinkedList<>();
for (int i=0;i<10000;i++)
{
int finalI = i;
new Thread(()->{
list.add(Thread.currentThread().getName());
synchronized (map){
map.put(Thread.currentThread().getName(), finalI);
}
cmap.put(Thread.currentThread().getName(),finalI);
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("list==>"+list.size());
System.out.println("hashmap==>"+map.size());
System.out.println("concurrenthashmap==>"+cmap.size());
}
}
结果
syncchronized锁了map,结果输出和本来就是ConcurrentHashMap一样,但是List输出结果不正确。
取钱操作
public class SafeBank {
public static void main(String[] args) {
Account tt=new Account("TT",100);
Draw xx=new Draw(tt,90,"xx");
Draw t=new Draw(tt,30,"tt");
Thread thread1=new Thread(t);
Thread thread2=new Thread(xx);
thread1.start();
thread2.start();
}
}
class Account
{
String name;
Integer money;
public Account(String name, Integer money) {
this.name = name;
this.money = money;
}
public void setMoney(Integer money) {
this.money = money;
}
}
class Draw implements Runnable
{
Account account;
Integer pocketmoney;
String pocketName;
public Draw(Account account, Integer pocketmoney,String pocketName) {
this.account = account;
this.pocketmoney = pocketmoney;
this.pocketName=pocketName;
}
@Override
public void run() {
if(account.money-pocketmoney<=0)
{
System.out.println("没钱");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setMoney(account.money-pocketmoney);
System.out.println(pocketName+"取走了"+account.name+pocketmoney+"元,还剩"+account.money+"元");
}
}
如果不加同步就会出现余额为负数的情况
结果
这时候需要加Synchronized,但是要注意,此时需要操作的对象是Account而不是this,所以不能在run方法上加锁,应该使用synchronized代码块。
public void run() {
synchronized (account)
{
if(account.money-pocketmoney<=0)
{
System.out.println("没钱");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setMoney(account.money-pocketmoney);
System.out.println(pocketName+"取走了"+account.name+pocketmoney+"元,还剩"+account.money+"元");
}
}
结果