共计 3452 个字符,预计需要花费 9 分钟才能阅读完成。
一、前言
<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,有须要的能够去下载。