10. 多线程的安全问题
大约 3 分钟
1. 数据安全问题分析
1.1 继承Thread方式
通过继承Thread父类的方式,多个子线程操作对象的成员变量不会出现数据共享的问题
package com.bobo.thread1;
public class ThreadDemo01 {
/**
* 数据安全问题
* 线程的创建方式有两种
* 继承自Thread类实现
* 实现Runable接口
* @param args
*/
public static void main(String[] args) {
MyThread01 t1 = new MyThread01();
t1.setName("A");
MyThread01 t2 = new MyThread01();
t2.setName("B");
MyThread01 t3 = new MyThread01();
t3.setName("C");
t1.start();
t2.start();
t3.start();
}
}
class MyThread01 extends Thread{
private Integer count = 0;
@Override
public void run() {
//System.out.println(Thread.currentThread().getName()+ " 执行了");
// 操作count
while(count < 10){
count++;
System.out.println(Thread.currentThread().getName()+ " 执行了" + count);
}
}
}
线程之间相互操作的是独立的数据
如果多个子线程操作的是静态属性,那么这时多个线程会共享该数据
package com.bobo.thread1;
public class ThreadDemo02{
/**
* 数据安全问题
* 线程的创建方式有两种
* 继承自Thread类实现
* 实现Runable接口
* @param args
*/
public static void main(String[] args) {
MyThread02 t1 = new MyThread02();
t1.setName("A");
MyThread02 t2 = new MyThread02();
t2.setName("B");
MyThread02 t3 = new MyThread02();
t3.setName("C");
t1.start();
t2.start();
t3.start();
}
}
class MyThread02 extends Thread{
private static Integer count = 0;
@Override
public void run() {
//System.out.println(Thread.currentThread().getName()+ " 执行了");
// 操作count
while(count < 10){
count++;
System.out.println(Thread.currentThread().getName()+ " 执行了" + count);
}
}
}
输出结果
1.2 实现Runable接口
我们通过Runable接口的方式创建的线程,如果多个子线程操作的是同一个Runable对象那么同一个对象的普通成员变量是共享的。
package com.bobo.thread1;
public class ThreadDemo03 {
public static void main(String[] args) {
Runnable runnable = new MyRunable();
Runnable runnable1 = new MyRunable();
Thread t1 = new Thread(runnable,"A");
Thread t2 = new Thread(runnable,"B");
Thread t3 = new Thread(runnable,"C");
// t4 线程操作的是另外一个Runable对象
Thread t4 = new Thread(runnable1,"D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class MyRunable implements Runnable{
private Integer count = 0;
@Override
public void run() {
while(count < 10){
count ++;
System.out.println(Thread.currentThread().getName() + " :" + count);
}
}
}
输出结果
B :2
D :1
C :3
A :2
C :5
D :2
B :4
D :3
C :7
A :6
C :9
D :4
B :8
D :5
A :10
D :6
D :7
D :8
D :9
D :10
前面三个线程共享第一个Runable对象的属性,第四个操作的是第二个Runable对象的属性
2.数据完全问题的原因
案例代码:
package com.bobo.thread1;
public class ThreadDemo04 {
/**
* 通过多线程模拟火车站售票的场景
* 有100张票 3个窗口售卖
* @param args
*/
public static void main(String[] args) {
// 火车票资料
Runnable runnable = new MyRunable04();
Thread t1 = new Thread(runnable,"窗口A");
Thread t2 = new Thread(runnable,"窗口B");
Thread t3 = new Thread(runnable,"窗口C");
t1.start();
t2.start();
t3.start();
}
}
class MyRunable04 implements Runnable{
// 火车票
private Integer ticket = 10;
@Override
public void run() {
while(ticket > 0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
}
}
}
输出结果
窗口A 卖出了第:10张票
窗口C 卖出了第:10张票
窗口B 卖出了第:10张票
窗口C 卖出了第:9张票
窗口B 卖出了第:9张票
窗口A 卖出了第:9张票
窗口B 卖出了第:8张票
窗口C 卖出了第:8张票
窗口A 卖出了第:7张票
窗口A 卖出了第:6张票
窗口B 卖出了第:6张票
窗口C 卖出了第:5张票
窗口B 卖出了第:3张票
窗口A 卖出了第:4张票
窗口C 卖出了第:4张票
窗口A 卖出了第:2张票
窗口B 卖出了第:0张票
窗口C 卖出了第:1张票
我们一共只有10张票,但是确卖出去了18张票,这个显然出现了数据完全问题。
多个子线程操作飞原子的代码,出现的问题,其实就是不能保证原子性出现的问题。
在多线程中出现数据安全问题的本质原因有三个:
- 原子性
- 可见性
- 有序性
原子性:多个操作要么同时执行要么都不执行
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );