java 多线程—— 线程同步=队列+锁
Posted 玛丽莲茼蒿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 多线程—— 线程同步=队列+锁相关的知识,希望对你有一定的参考价值。
一、线程同步 = 队列 + 锁
同步就是多个线程同时访问一个资源。
那么如何实现? 队列+锁。
想要访问同一资源的线程排成一个队列,按照排队的顺序访问。访问的时候加上一个锁(参考卫生间排队+锁门),访问完释放锁。
二、 不安全案例
2.1 不安全的抢票系统
之前我们实现过这个例子。
package Unsafe;
public class RailwayTicketSystem
public static void main(String[] args)
BuyTicket buyer = new BuyTicket();
new Thread(buyer,"黑黑").start();
new Thread(buyer,"白白").start();
new Thread(buyer,"黄牛党").start();
class BuyTicket implements Runnable
private int ticketNums = 10; //系统里有10张票
//抢票行为
@Override
public void run()
while(ticketNums>0)
try
Thread.sleep(100); //模拟延时,放大问题的发生性
catch (InterruptedException e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+"--->抢到了第"+ticketNums+"张票");
ticketNums--;
2.2 不安全的银行取钱
场景: 黑土有一张存款为100万的卡,黑土去银行柜台取钱50万,同一时刻,黑土的老婆白云也要通过网上银行从卡里取走100万。
因为取钱是用户各自取各自账户里的钱,不存在多个线程操作同一个对象(所有用户都去抢系统里的票),所以可以用extends Thread。
package Unsafe;
public class UnsafeBank
public static void main(String[] args)
//黑黑的卡里一共有100万
Account 黑土的卡 = new Account("黑土的卡",100);
//黑黑要从卡里取走50万
DrawMoney 黑土 = new DrawMoney(黑土的卡,50);
黑土.start();
//同时,白白也来到了银行,白白要从卡里取走100万
DrawMoney 白云 = new DrawMoney(黑土的卡, 100);
白云.start();
//银行卡
class Account
private String name; //持卡人
private int money ; //余额
public Account(String name, int money)
this.name = name;
this.money = money;
public String getName()
return name;
public void setName(String name)
this.name = name;
public int getMoney()
return money;
public void setMoney(int money)
this.money = money;
//银行:模拟取款
class DrawMoney extends Thread
Account account; //账户
int drawMoney; //要取多少钱
public DrawMoney(Account account,int drawMoney)
this.account = account;
this.drawMoney = drawMoney;
//取钱
@Override
public void run()
if(account.getMoney()-drawMoney<0)
System.out.println("余额已不足,【"+Thread.currentThread().getName()+"】无法取钱");
return;
//延时,放大问题的发生
try
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
//余额变动
account.setMoney(account.getMoney() - drawMoney);
System.out.println(Thread.currentThread().getName()+"取走了"+drawMoney);
//输出余额
System.out.println(account.getMoney());
2.3 不安全的集合
这里以ArraryList为例,我们知道ArraryList的底层是用数组存储的。当多个线程同时执行add方法时,会出现多个线程向数组的同一个位置存放数据的情况。
三、同步机制
由于我们可以用private关键字来保证变量只能被方法访问,所以我们只需要针对类似于getXx()方法提出一套机制,这套机制就是synchronized关键字,synchronized就能实现队列+锁机制。它包括两种用法
所以说,synchronized 锁的既是对象(的资源/成员变量)(临界资源),也是一段代码(临界区)
1. synchronized 方法
在方法前面加synchronized 关键字。
同步方法所属类所创建的每个对象,都有一把锁。
2. synchronized 块
如何使用?中括号括起来临界区,小括号内填上临界资源。
一个方法中同时存在读取和增删改的代码,但是读取不属于同时操作资源。假如一个方法有1000行,里面只有5行代码是增删改,需要同步,剩下的995行不需要同步,那么使用synchronized声明整个方法会造成线程不必要的等待,浪费时间。所以出现了synchronized块。
顾名思义就是把一个代码段声明为synchronized。
可以指定要锁定的对象,如果不指定的话默认锁的是this。
3.1 解决不安全的抢票系统
我们给将run方法声明为synchronized,发现虽然结果不会出现负数的情况。
但是票都被同一个人抢去了。
我们来看一下这是为什么。给run方法上锁,意味着所有进入run方法的对象都要把run方法执行完才能释放这个锁给下一个排队的对象用。在我们的代码中,一旦某个对象进入了run方法就要一直抢票,直到 ticketNums<0,也就是意味着一张票也没有了,才会退出run方法。所以,除了第一个被执行的线程能抢到票且抢走了所有票,其他的线程一张票都抢不到。
可是这不是我们的目的呀!
错就错在,我们想要锁的操作是“抢一张票”,而我们上面锁的是“抢完所有票”。
所以应该把抢一张票的逻辑单独写成一个方法,然后加上synchronized关键字。
package Unsafe;
public class RailwayTicketSystem
public static void main(String[] args)
BuyTicket system= new BuyTicket(); //镜像
new Thread(system,"黑黑").start(); //容器1
new Thread(system,"白白").start(); //容器2
new Thread(system,"黄牛党").start(); //容器3
class BuyTicket implements Runnable
private int ticketNums = 10; //系统里有10张票
private boolean flag = true; //系统初始化是开放的
//抢票行为
@Override
public void run()
while(flag==true)
try
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
buy();
public synchronized void buy()
if(ticketNums>0)
System.out.println(Thread.currentThread().getName()+"--->抢到了第"+ticketNums+"张票");
ticketNums--;
if(ticketNums == 0) flag = false;
注意,睡眠代码的位置也值得思考:
3.2 解决不安全的银行取钱系统
在需要同步的代码中,发生变动(增删改)的是account,而不是run方法所在的DrawMoney类。所以要指定锁的对象account,如果不指定的话默认锁的是所在类。此时我们不能给方法加synchronized了,因为方法无法指定被锁的对象。我们使用同步块:
3.3 解决不安全的集合
以上是关于java 多线程—— 线程同步=队列+锁的主要内容,如果未能解决你的问题,请参考以下文章