1.高并发及线程安全的概念
1.高并发:在某个时间点上,有多个线程同时访问某一个资源。例如:双十一,12306 , 秒杀
2.线程安全性问题:当多个线程无序的访问同一个资源(例如:同一个变量、同一数据库、同一个文件……),而且访问同一资源的代码不具有“原子性”,这时对这一资源的方法就会产生安全性问题——导致此资源最终的结果是错误。
3.高并发所产生的安全性问题主要表现:
1).可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
2).有序性:即程序执行的顺序按照代码的先后顺序执行。
3).原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
2.高并发问题一_不可见性(重点)
产生的原因:定义一个变量。一个线程修改变量的值,另一个线程由于访问频率太快,导致一直使用本线程区内的变量副本,而没有实时的到主内存中获取变量的新值。
package com.itheima.demo01visible;
/*
高并发问题一_不可见性(重点)
*/
public class MyThread extends Thread{
//定义一个静态的成员变量,供所有的线程使用
public static int a = 0;
@Override
public void run() {
System.out.println("Thread-0线程开始执行线程任务了,睡醒2秒钟,等待主线程先执行!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread-0线程2秒钟之后,睡醒了,把变量a的值修改为1");
a=1;
System.out.println("Thread-0线程执行线程任务结束了!"+a);
}
}
package com.itheima.demo01visible;
public class Demo01Visible {
public static void main(String[] args) {
//创建Thread的子类对象
MyThread mt = new MyThread();
//调用start方法,开启一个新的线程,执行run方法
mt.start();
//主线程在开启Thread-0线程之后,会继续执行main方法中的代码
System.out.println("主线程,执行一个死循环");
while (true){
if(MyThread.a==1){
System.out.println("主线程判断变量a的值==1,结束死循环!");
break;
}
}
}
}
3.Java内存模型JMM(了解)
概述:JMM(Java Memory Model)Java内存模型,是java虚拟机规范中所定义的一种内存模型。
Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。所有的共享变量都存储于主内存。这里所说的变量指的是实例变量(成员变量)和类变量(静态成员变量)。不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存完成。
4.高并发问题二_无序性
1).有序性:多行代码的编写(.java)顺序和编译(.class)顺序。
有些时候,编译器在编译代码时,为了提高效率,会对代码“重排”:
.java文件
int a = 10; //第一行
int b = 20; //第二行
int c = a * b; //第三行
在执行第三行之前,由于第一行和第二行的先后顺序无所谓,所以编译器可能会对“第一行”和“第二行”进行代码重排:
.class
int b = 20;
int a = 10;
int c = a * b;
2).但在多线程环境下,这种重排可能是我们不希望发生的,因为:重排,可能会影响另一个线程的结果,所以我们不需要代码进行重排
5.高并发问题三_非原子性(重点)
原子:不可分割 100行代码是一个原子,线程执行100行代码不可以分开执行,要么都执行,要都不执行
需求:
1.定义多线程共享的静态变量money
2.Thread-0线程把money的值增加10000
3.main线程把money的值增加10000
4.查看money的最终结果
package com.itheima.demo02atomic;
/*
高并发问题三_非原子性(重点)
需求:
1.定义多线程共享的静态变量money
2.Thread-0线程把money的值增加10000
3.main线程把money的值增加10000
4.查看money的最终结果
*/
public class MyThread extends Thread{
//1.定义多线程共享的静态变量money
public static int money=0;
@Override
public void run() {
//2.Thread-0线程把money的值增加10000
System.out.println("Thread-0线程开始执行线程任务,增加money的值!");
for (int i = 0; i < 10000; i++) {
money++;
}
System.out.println("Thread-0线程执行线程任务结束!");
}
}
package com.itheima.demo02atomic;
public class Demo01Atomic {
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
mt.start();
//主线程在开启新的线程之后,会继续执行main方法中的代码
//3.main线程把money的值增加10000
System.out.println("主线程开始增加变量money的值!");
for (int i = 0; i < 10000; i++) {
MyThread.money++;
}
System.out.println("主线程增加money值结束,睡眠2秒钟,等待Thread-0线程也执行完毕!");
Thread.sleep(2000);
System.out.println("最终两个线程都执行完毕,打印money的值:"+MyThread.money);
}
}
主线程开始增加变量money的值!
主线程增加money值结束,睡眠2秒钟,等待Thread-0线程也执行完毕!
Thread-0线程开始执行线程任务,增加money的值!
Thread-0线程执行线程任务结束!
最终两个线程都执行完毕,打印money的值:20000
主线程开始增加变量money的值!
Thread-0线程开始执行线程任务,增加money的值!
主线程增加money值结束,睡眠2秒钟,等待Thread-0线程也执行完毕!
Thread-0线程执行线程任务结束!
最终两个线程都执行完毕,打印money的值:18198
主线程开始增加变量money的值!
Thread-0线程开始执行线程任务,增加money的值!
Thread-0线程执行线程任务结束!
主线程增加money值结束,睡眠2秒钟,等待Thread-0线程也执行完毕!
最终两个线程都执行完毕,打印money的值:12060
原子性原理
每个线程访问money变量,都需要三步:
1).取money的值;
2).将money++
3).将money写回
这三步就不具有“原子性”——执行某一步时,很可能会被暂停(失去了cpu的执行权),执行另外一个线程,就会导致变量的最终结果错误!!!!