1、多线程安全
一张票,还剩:99
窗口1卖了一张票,还剩:99
窗口2卖了一张票,还剩:97
窗口3卖了一张票,还剩:96
窗口1卖了一张票,还剩:97
package com.hsq.thread;
/**
* 售票问题
*/
public class MyThead_安全 {
static Integer piao = 100;
static Object o = new Object();
public static void main(String[] args) {
new Thread(new MyTest(), "窗口1").start();
new Thread(new MyTest(), "窗口2").start();
new Thread(new MyTest(), "窗口3").start();
}
static class MyTest implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (piao > 0) {
piao--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩:" + piao);
} else {
// 票没了
break;
}
}
}
}
}
2、JMM内存模型
3、线程并发的三大特征
3.1、原子性
原子性就是指操作不可分割,所有操作要么全部执行完成,要么全部执行失败。
对于代码段,一次只允许一个线程执行
解决售票问题、加锁 synchronized
package com.hsq.thread;
/**
* 售票问题
*/
public class MyThead_安全 {
static Integer piao = 100;
static Object o = new Object();
public static void main(String[] args) {
new Thread(new MyTest(), "窗口1").start();
new Thread(new MyTest(), "窗口2").start();
new Thread(new MyTest(), "窗口3").start();
}
static class MyTest implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o){
if (piao > 0) {
piao--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩:" + piao);
} else {
// 票没了
break;
}
}
}
}
}
}
3.2、可见性
可见性:变量被修改后,对其他线程立马可见
package com.hsq.thread;
public class MyThread_可见性 {
static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(new test1(), "线程一").start();
Thread.sleep(1000);
new Thread(new test2(), "线程二").start();
System.out.println(flag);
}
// 线程1
static class test1 implements Runnable {
@Override
public void run() {
System.out.println("线程一执行=======" + Thread.currentThread().getName());
long sum = 0;
while (!flag) {
sum++;
}
System.out.println(sum);
}
}
// 线程2
static class test2 implements Runnable {
@Override
public void run() {
System.out.println("线程2执行===========" + Thread.currentThread().getName());
fun();
}
}
public static void fun() {
flag = true;
}
}
出现线程一,一直执行
原因:参考JMM内存
3.3、有序性
有序性:代码禁止重排序
有序性是指代码在运行期间保证和代码的顺序一致,但实际上在执行过程中会进行指令重排序。
指令重排序:为了提高CPU的运行效率,JVM可能会对编译后的代码进行顺序调换等,但会保证最终结果与调换前的结果保持一致,指令的执行顺序与代码逻辑顺序不一致,这个过程就叫做指令的重排序。
- 当然,在多线程的情况下重排序可不会保证我们代码最终结果的一致,可以通过sychronized或者volatile来确保有序性。
4、volatile
volatile关键字的作用是变量在多个线程之间可见。并且能够保证所修饰变量的有序性:
1、保证变量的可见性: 当一个被volatile关键字修饰的变量被一 个线程修改的时候,其他线程可以立刻得到修改之后的结果。当一个线程向被volatile关键字修饰的变量写入数据的时候,虚拟机会强制它被值刷新到主内存中。当-个线程用到被volatile关键字修饰的值的时候,虚拟机会强制要求它从主内存中读取。
2、屏蔽指令重排序: 指令重排序是编译器和处理器为了高效对程序进行优化的手段,它只能保证程序执行的结果时正确的,但是无法保证程序的操作顺序与代码顺序-致。这在单线程中不会构成问题,但是在多线程中就会出现问题。非常经典的例子是在单例方法中同时对字段加入volatile,就是为了防止指令重排序。