Java多线程详解之三:线程同步
一、前言
本篇文章将从什么是线程同步、为什么要线程同步、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关键字进行标注,我们可以看见我们在synchronized块中,写了一个this,因为对于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方法;
欢迎关注微信公众号,不定期分享学习笔记与资料,与Mike一起学java,谢谢~

浙公网安备 33010602011771号