1、多线程的三大特性
- 原子性::可以理解为 CPU 执行一次简单的基本操作
- 可见性:JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线
程之间不能及时看到改变,这个就是可见性问题 - 有序性:一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
案例描述:
电影院有三个窗口,售两种票共 100 张,输出售票情况
class SellTicket implements Runnuble {
private int ticket = 50;
public void run() {
while (true) {
if (ticket > 0) {
// 模拟更加真实场景,每次出票后延迟 100 毫秒
try {
Thread.sleep(100);
} catch (InterruptException e) {
e.printStackTrace();
}
System.out.println(Thread.currrentThread().getName()+"正在出票"(ticket--)+"票数");
}
}
}
}
public class SellTIcketDemo {
public static void main(String[] args) {
//创建线程资源对象
SellTicket s = new SellTicket();
//创建三个线程对象
Thread t1 = new Thread(s,"窗口1");
Thread t2 = new Thread(s,"窗口2");
Thread t3 = new Thread(s,"窗口3");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
分析:
每次输出的结果不同,此次结果出现了两种不安全的情况:相同的票和负数票的情况
那么那些问题会引起线程的不安全?
1>是否是多线程环境
2>是否有数据共享
3>是否有多个操作于共享数据
而在以上的代码中前两条是完全符合的,问题在于是否有多个操作共享数据?
注意 ticket-- 操作;CPU并不是简单的执行了一次减一,而是进行了三步:
1> 从内存读取变量到 CPU
2> 修改变量
3> 写回内存
出现相同的票(CPU的操作原子性决定的)
CPU 的每一次执行都是原子性(最简单基本)的操作,比如 ticket–其实并不是一次操作;
首先记录tiket以前的值 50
接着执行 tiket-- 的操作
然后输出以前的值 50 t1输出,就在这之间可能 t2 就来了(并发/并行),则 t2 也输出一样的了
tiket 的值变成 49
出现负数票的情况,就是在休眠的时候(随机性和延迟导致)
t1 、t2、t3 恰好都在 100 毫秒内分别进来了,所以三个都会执行
所以最坏情况下:
正在出售第 1 张票 ticket = 0
正在出售第 0 张票 tiket = -1
正在出售第 -1 张票 tiket = -2
常见的非原子性操作:
n++;–n 从内存读取变量到CPU,修改变量、写回内存
对象的new操作 分配对象的内存、初始化对象、将对象赋值给变量
2、synchronized 实现线程同步
- 同步代码块
synchronized(对象) {
需要同步的代码块
}
//注意同步可以解决安全问题的根本原因在对象上,该对象如同锁的功能
//多个线程必须是同一把锁
//锁的对象是任意对象
1> 特点:多个线程是前提、多个线程使用的是同一个锁对象
2> 好处:解决了多线程的安全问题
3> 弊端:当线程相对较多时,每个线程都会去判断同步上的锁,这是很耗费资源的会降低程序的运行效率
- 同步方法
把同步关键字加在方法上
同步方法的锁对象:this
静态方法及锁对象问题:当前类的class文件对象
修改后线程安全
public class Sell_Ticket implements Runnable{
//定义 50 张票
private int ticket = 50;
//创建锁对象
private Object obj = new Object();
public void run() {
while (true) {
synchronized(obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + (ticket--) + "票");
}
}
}
}
}
public class Sell_TicketDemo {
public static void main(String[] args) {
//创建资源对象
Sell_Ticket my = new Sell_Ticket();
//创建线程对象
Thread t1 = new Thread(my,"窗口1");
Thread t2 = new Thread(my,"窗口2");
Thread t3 = new Thread(my,"窗口3");
t1.start();
t2.start();
t3.start();
}
}