一、前言
本篇文章将从什么是线程同步、为什么要线程同步、Java线程同步的方法三个部分向大家介绍线程同步,如果对线程同步概念很了解的同学可以跳过第二部分和第三部分,直接看第四部分的内容~~~
二、什么是线程同步
通俗一点的说,线程同步就好比我们食堂排队打饭,每个人都想吃饭,最天然的解决办法就算是:排队,一个个来。
处理多线程问题时,如果多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
三、为什么要线程同步
我们可以举两个例子,如下图:
左图中,如果一班列车仅剩最后一张票,这时候有三个人在某订票软件上都看见了这张票,他们同时抢这张票,如果不加以限制,每个人都会获得一张票,但是车上只剩最后一张座位了,该怎么办呢?
右图中,如果一个账户中有100万,夫妻双方在各自的手机账户上看到的余额都是100万,这时候丈夫想取50万买辆车,妻子想取100万买套房,如果不加以限制可以同时操作,那么他们都能取到钱,二人一共取出来150万,那银行岂不是血亏!
下面我们来使用一段代码,模拟一下小明小黄小刚三人是怎样抢票的:
public class UnsafeBuyT {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"小明").start();
new Thread(station,"小刚").start();
new Thread(station,"小黄").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNum = 10;
private boolean flag = true;
@Override
public void run() {
//买票
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
if (ticketNum <= 0) {
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "拿到" + ticketNum--);
}
}
因为对于synchronized块,我们需要指定对象,本例中我们需要锁定的是BuyTicket对象,所以我们使用this即可。
2、Lock
从JDK5.0开始,Java提供了更强大的线程同步机制-通过显示定义同步锁对象来实现同步,同步锁使用Lock对象;
Lock接口时控制对各线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只有一个线程对Lock对象加锁,线程开始访问共享资源之前应获得Lock对象;
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常见的是ReentrantLocj,可以显式加锁、释放锁;
修改后的BuyTicket类:
class BuyTicket implements Runnable{
//票
private int ticketNum = 10;
private boolean flag = true;
//1.创建锁对象
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
//买票
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
//加锁
lock.lock();
if (ticketNum <= 0) {
flag = false;
return;
}
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName() + "拿到" + ticketNum--);
//解锁
lock.unlock();
}
}
我们可以看见,Lock锁是一个显式锁,即显式的加锁lock(),以及显示的解锁unlock()。
注:Lock手动开启和关闭锁,开启后不要忘记关闭锁,否则该线程会一直占有此对象的锁,导致其他线程无法访问。
3、Synchronized与Lock的对比
- Lock是显式锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放;
- Lock只有代码块锁,synchronized有代码块锁和方法锁;
- 使用Lock锁,性能更好,并且具有更好的扩展性(提供更多的子类);
- 优先使用顺序:Lock > synchronized块 > synchronized方法;