一、前言
<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 引入了线程同步机制。次要有以下三种形式:
- <font face=黑体>同步代码块;
- <font face=黑体>同步办法;
- <font face=黑体>锁机制;
2.2.1、同步代码块
<font face=黑体>synchronized 关键字能够用于办法中的某个区块中,示意只对这个区块的资源履行互斥拜访。
<font face=黑体>格局:
synchronized(同步锁){ 须要同步操作的代码}
<font face=黑体>留神:
- <font face=黑体>代码块中的锁对象能够是任意对象;
- <font face=黑体>然而必须保障多个线程应用的锁对象是同一个;
- <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 锁也称同步锁,加锁与开释锁办法化了,如下:
- <font face=黑体>public void lock() : 获取锁;
- <font face=黑体>public void lock() : 开释同步锁;
<font face=黑体>Lock 锁应用步骤:
- <font face=黑体>在成员地位创立一个 ReentrantLock 对象;
- <font face=黑体>在可能呈现平安问题的代码前调用 Lock 接口中的办法lock();
- <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,有须要的能够去下载。