多线程之间实现同步
学习目标
有明确的学习目标,学起来会更有趣哦:
- 理解线程安全
- synchronized用法
- 死锁
- 多线程创建方式
- 练习题
- 线程生命周期
- 面试总结
一、什么是线程安全?
为什么有线程安全问题?
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。
但是做读操作是不会发生数据冲突问题。
案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。
代码:
/**
*多线程之买火车票案例-展示线程不安全
*/
class ThreadTrain1 implements Runnable {
// 这是货票总票数,多个线程会同时共享资源
private int trainCount = 100;
@Override
public void run() {
while (trainCount > 0) {// 循环是指线程不停的去卖票
try {
// 等待100毫秒
Thread.sleep(10);
} catch (InterruptedException e) {
}
sale();
}
}
/**
* 出售火车票
*/
public void sale() {
if (trainCount > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--; }
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
ThreadTrain1 threadTrain = new ThreadTrain1(); // 定义 一个实例
Thread thread1 = new Thread(threadTrain, "一号窗口");
Thread thread2 = new Thread(threadTrain, "二号窗口");
thread1.start();
thread2.start();
}
}
运行结果:
注意:一号窗口和二号窗口同时出售火车第一张和第七张,部分火车票会重复出售。
结论:当多个线程共享同一个全局成员变量时,做写的操作可能会发生数据冲突问题。
二、线程安全解决办法
问:如何解决多线程之间线程安全问题?
答:使用多线程之间同步或使用锁(lock)。
问:为什么使用线程同步或使用锁能解决线程安全问题呢?
答:当使用线程同步或使用锁时,只能让当前一个线程进行执行;代码执行完成后释放锁,然后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
问:什么是多线程之间同步?
答:当多个线程共享同一个资源,不会受到其他线程的干扰。
使用同步代码块
问:什么是同步代码块?
答:就是将可能会发生线程安全问题的代码,给包括起来。
synchronized(同一个数据){
可能会发生线程冲突问题
}
代码示例:
private Object mutex = new Object();// 自定义多线程同步锁
public void sale() {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--; }
}
}
同步函数
问:什么是同步函数?
答:在方法上修饰 synchronized 称为同步函数
synchronized(同一个数据){
可能会发生线程冲突问题
}
代码示例:
public synchronized void sale() {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
}
}
问:同步函数用的是什么锁?
答:同步函数使用this锁。
证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。
代码 :
class ThreadTrain5 implements Runnable {
// 这是货票总票数,多个线程会同时共享资源
private int trainCount = 100;
public boolean flag = true;
private Object mutex = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
}
}
}
} else {
while (true) {
sale();
}
}
}
public synchronized void sale() {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
}
}
}
public class ThreadDemo5 {
public static void main(String[] args) throws InterruptedException {
ThreadTrain5 threadTrain = new ThreadTrain5(); // 定义 一个实例
Thread thread1 = new Thread(threadTrain, "一号窗口");
Thread thread2 = new Thread(threadTrain, "二号窗口");
thread1.start();
Thread.sleep(40);
threadTrain.flag = false;
thread2.start();
}
}
静态同步函数
问:什么是静态同步函数?
答:方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
静态的同步函数使用的锁是 该函数所属字节码文件对象 ,
可以用 getClass方法获取,也可以用当前 类名.class 表示。
代码示例:
//使用ThreadTrain.class锁
synchronized (ThreadTrain.class) {
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
try {
Thread.sleep(100);
} catch (Exception e) {
}
}
总结
synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件。
三、多线程死锁
什么是多线程死锁?
答:同步中嵌套同步,导致锁无法释放!
代码:
class ThreadTrain6 implements Runnable {
// 这是货票总票数,多个线程会同时共享资源
private int trainCount = 100;
public boolean flag = true;
private Object mutex = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized (mutex) {
// 锁(同步代码块)在什么时候释放? 代码执行完, 自动释放锁.
// 如果flag为true 先拿到 obj锁,再拿到this 锁、 才能执行。
// 如果flag为false先拿到this,再拿到obj锁,才能执行。
// 死锁解决办法:不要在同步中嵌套同步。
sale();
}
}
} else {
while (true) {
sale();
}
}
}
/**
*出售火车票
*/
public synchronized void sale() {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
}
}
}
}
public class DeadlockThread {
public static void main(String[] args) throws InterruptedException {
ThreadTrain6 threadTrain = new ThreadTrain6(); // 定义 一个实例
Thread thread1 = new Thread(threadTrain, "一号窗口");
Thread thread2 = new Thread(threadTrain, "二号窗口");
thread1.start();
Thread.sleep(40);
threadTrain.flag = false;
thread2.start();
}
}
四、面试题
1、什么是多线程安全?
答:当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。做读操作是不会发生数据冲突问题。
2、如何解决多线程之间线程安全问题?
答:使用多线程之间同步或使用锁(lock)。
3、为什么使用线程同步或使用锁能解决线程安全问题呢?
答:当使用线程同步或使用锁时,只能让当前一个线程进行执行;代码执行完成后释放锁,然后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
4、什么是多线程之间同步?
答:当多个线程共享同一个资源,不会受到其他线程的干扰。
5、什么是同步代码块?
答:就是将可能会发生线程安全问题的代码,给包括起来。只能让当前一个线程进行执行,被包裹的代码执行完成之后才能释放所,然后才能让其他线程进行执行!
synchronized(同一个数据){
可能会发生线程冲突问题
}
6、如何使用同步函数?
答:在方法上修饰synchronized 称为同步函数。
7、如何使用静态同步函数?
答:方法上加上static关键字,使用synchronized 关键字修饰 为静态同步函数,静态的同步函数使用的锁是 该函数所属字节码文件对象。
8、同步代码块与同步函数区别?
答:
1、同步代码块使用自定义锁(明锁)
2、同步函数使用this锁
9、同步函数与静态同步函数区别?
注意:有些面试会这样问:例如现在一个静态方法和一个非静态静态怎么实现同步?
答:
1、同步函数使用this锁
2、静态同步函数使用字节码文件,也就是类.class
**
10、什么是多线程死锁?
答:同步中嵌套同步
解决办法:同步中尽量不要嵌套同步