一、前言

  本篇文章将从什么是线程同步为什么要线程同步、Java线程同步的方法三个部分向大家介绍线程同步,如果对线程同步概念很了解的同学可以跳过第二部分和第三部分,直接看第四部分的内容~~~  

 

二、什么是线程同步

  通俗一点的说,线程同步就好比我们食堂排队打饭,每个人都想吃饭,最天然的解决办法就算是:排队,一个个来。

  处理多线程问题时,如果多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

 

三、为什么要线程同步

  我们可以举两个例子,如下图:

java 变量 线程同步 java线程同步的原理_加锁

                                  

java 变量 线程同步 java线程同步的原理_加锁_02

  

  左图中,如果一班列车仅剩最后一张票,这时候有三个人在某订票软件上都看见了这张票,他们同时抢这张票,如果不加以限制,每个人都会获得一张票,但是车上只剩最后一张座位了,该怎么办呢?

  右图中,如果一个账户中有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--);
    }
}

java 变量 线程同步 java线程同步的原理_加锁_03

因为对于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();

    }
}

java 变量 线程同步 java线程同步的原理_加锁_03

  我们可以看见,Lock锁是一个显式锁,即显式的加锁lock(),以及显示的解锁unlock()。

注:Lock手动开启和关闭锁,开启后不要忘记关闭锁,否则该线程会一直占有此对象的锁,导致其他线程无法访问

 

3、Synchronized与Lock的对比

  • Lock是显式锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放;
  • Lock只有代码块锁,synchronized有代码块锁和方法锁;
  • 使用Lock锁,性能更好,并且具有更好的扩展性(提供更多的子类);
  • 优先使用顺序:Lock > synchronized块 > synchronized方法