一、前言

<font face=黑体> 多线程的实现形式咱们曾经讲完了,明天咱们来讲线程平安

二、线程平安

2.1、线程平安概述

<font face=黑体>如果有多个线程在同时运行,而这些线程可能会同时拜访某一共享变量,这样就会产生线程平安问题。

<font face=黑体>咱们通过一个卖票案例来演示线程平安问题:

public class RunnableImpl implements Runnable {    // 定义一个多线程共享的票源    private int ticket = 100;    // 卖票    @Override    public void run() {        while (true) {            if (ticket > 0) {                System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");                ticket--;            }        }    }}

<font face=黑体>测试类:

public class TestSellTicket {    // 创立三个线程,同时开启,对共享的票进行发售    public static void main(String[] args) {        // 创立Runnable接口的实现类        RunnableImpl runnable = new RunnableImpl();        // 创立Thread        Thread t0 = new Thread(runnable);        Thread t1 = new Thread(runnable);        Thread t2 = new Thread(runnable);        t0.start();        t1.start();        t2.start();    }}

<font face=黑体>测试后果如下所示:

<font face=黑体>能够看到运行后果中呈现了反复的票,这就是线程不平安。线程平安问题都是由全局变量及动态变量引起的。若每个线程中对全局变量、动态变量只有读操作,而无写操作,一般来说,这个全局变量是线程平安的;若有多个线程同时执行写操作,个别都须要思考线程同步,否则的话就可能呈现线程平安的问题。

2.2、线程同步

<font face=黑体>当咱们应用多个线程拜访同一资源的时候,且多个线程中对资源有写的操作,就容易呈现线程平安问题。

<font face=黑体>要解决上述多线程并发拜访一个资源的安全性问题:也就是解决反复票与不存在票问题,Java 中提供了同步机制 (synchronized) 来解决。

<font face=黑体>根据上述卖票案例简述:

窗口 1 线程进入操作的时候,窗口 2 和窗口 3 线程只能在外等着,窗口 1 操作完结,窗口 1 和窗口 2 和窗口 3 才有机会进入代码去执行。也就是说在某个线程批改共享资源的时候,其余线程不能批改该资源,期待批改结束同步之后,能力去争夺 CPU 资源,实现对应的操作,保障了数据的同步性,解决了线程不平安的景象。

<font face=黑体>为了保障每个线程都能失常执行原子操作,Java 引入了线程同步机制。次要有以下三种形式:

  1. <font face=黑体>同步代码块;
  2. <font face=黑体>同步办法;
  3. <font face=黑体>锁机制;

2.2.1、同步代码块

<font face=黑体>synchronized 关键字能够用于办法中的某个区块中,示意只对这个区块的资源履行互斥拜访。

<font face=黑体>格局:

synchronized(同步锁){    须要同步操作的代码}

<font face=黑体>留神:

  1. <font face=黑体>代码块中的锁对象能够是任意对象;
  2. <font face=黑体>然而必须保障多个线程应用的锁对象是同一个;
  3. <font face=黑体>锁对象的作用就是将同步代码块锁住,只容许一个线程在同步代码块中执行;

<font face=黑体>利用同步代码块实现线程平安的具体代码实现如下所示:

public class RunnableImpl implements Runnable {    // 定义一个多线程共享的票源    private int ticket = 100;    // 创立一个锁对象    Object object = new Object();    // 卖票    @Override    public void run() {        while (true) {            // 创立同步代码块            synchronized (object) {                if (ticket > 0) {                    // 模仿卖票工夫                    try {                        Thread.sleep(10);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");                    ticket--;                }            }        }    }}

<font face=黑体>同步技术的原理:

<font face=黑体>同步中的线程,没有执行结束不会开释锁,同步外的线程没有锁进不去同步,所以就能够保障同步中始终只有一个线程在执行,保障了线程平安。然而程序会频换的判断锁、获取锁和开释锁导致程序的效率升高。

2.2.2、同步办法

<font face=黑体>应用 synchronized 润饰的办法,就叫做同步办法,保障A线程执行该办法的时候,其余线程只能在办法外等着。

<font face=黑体>格局:

public synchronized void method(){    可能会产生线程平安问题的代码}

<font face=黑体>利用同步办法实现线程平安的具体代码实现如下所示:

public class RunnableImpl implements Runnable {    // 定义一个多线程共享的票源    private int ticket = 100;    // 卖票    @Override    public void run() {        while (true) {            sellTicket();        }    }    /**     * 定义一个同步办法     */    public synchronized void sellTicket() {        if (ticket > 0) {            try {                Thread.sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");            ticket--;        }    }}

2.2.3、Lock 锁

<font face=黑体>java.util.concurrent.locks.Lock 机制提供了比 synchronized 代码块和 synchronized 办法更宽泛的锁定操作,同步代码块/同步办法具备的性能 Lock 都有,除此之外更弱小,更体现面向对象。

<font face=黑体>Lock 锁也称同步锁,加锁与开释锁办法化了,如下:

  1. <font face=黑体>public void lock() : 获取锁;
  2. <font face=黑体>public void lock() : 开释同步锁;

<font face=黑体>Lock 锁应用步骤:

  1. <font face=黑体>在成员地位创立一个 ReentrantLock 对象;
  2. <font face=黑体>在可能呈现平安问题的代码前调用 Lock 接口中的办法lock();
  3. <font face=黑体>在可能呈现平安问题的代码前调用 Lock 接口中的办法unLock();

<font face=黑体>利用 Lock 锁实现线程平安的具体代码实现如下所示:

public class RunnableImpl implements Runnable {    // 定义一个多线程共享的票源    private int ticket = 100;    // 在成员地位创立一个 ReentrantLock 对象;    Lock l = new ReentrantLock();     // 卖票    @Override    public void run() {        while (true) {            l.lock();            if (ticket > 0) {                try {                    Thread.sleep(10);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");                ticket--;            }            l.unlock();        }    }}

三、小结

<font face=黑体>多线程的线程平安咱们曾经讲完了,下一节咱们来讲线程状态

四、源码

<font face=黑体>文章中用到的所有源码已上传至 github,有须要的能够去下载。