今天学习了高并发和线程安全,以及怎么解决这些安全问题代码量相对较少,重要的是理解
时间: 2022/4/24
高并发和线程安全
高并发就是在同一时间段内,有大量的线程访问
比如: 双十一 12306春运购票
线程安全问题就是线程和线程之间出现了不该出现的相互影响
多线程内存运行机制
多线程在内存中和之前单线程不太一样
多个线程在内存中共享同一块堆空间和方法区
但是每个线程会自己有自己的一个栈空间
可见性
在多个线程共同操作一个变量的时候,一个线程可能看不到另一个线程对变量的修改,这个叫可见性问题
public class AAAA1 extends Thread{
static int a=0;
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
a=10;
System.out.println("AAAA1的a = " + a);
}
}
public class AAAA2 extends Thread{
@Override
public void run() {
while (true) {
if (AAAA1.a==10) {
System.out.println("AAAA2知道了a=10");
break;
}
}
}
}
public class Test1 {
public static void main(String[] args) {
AAAA1 aaaa1 = new AAAA1();
aaaa1.start();
AAAA2 aaaa2 = new AAAA2();
aaaa2.start();
}
}
结果:
原因:
有序性
程序在编译期间,有可能会把没有逻辑关系的代码打乱顺序,这个现象叫代码重排,重排对单线程不会有影响,但是在多线程情况下就有可能对别的线程造成影响。
这个现象是随机的有出现的可能性,我们没有办法使用代码直接演示…
原子性
正常理解原子是不可分割的最小单位
但是java中在多线程的情况下,两个原子代码会互相产生影响,这个影响叫原子性问题
public class AAAA3 extends Thread{
static int a = 0;
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
a++;
}
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException {
AAAA3 aaaa1 = new AAAA3();
aaaa1.start();
AAAA3 aaaa2 = new AAAA3();
aaaa2.start();
Thread.sleep(1000);
System.out.println(AAAA3.a);
}
}
原因:
解决以上安全问题的办法
volatile关键字
volatile关键字可以作用于成员变量
作用:
可以解决可见性和有序性问题
volatile static int a = 0;
AtomicInteger关键字
对于整数int,java中有一个原子类整数叫AtomicInteger
AtomicInteger可以解决前面三种问题
//static int a = 0;
static AtomicInteger a = new AtomicInteger(0);
AtomicIntegerArray关键字
普通数组比如int[] 在多线程的情况下也会出现原子性问题
我们可以使用原子类数组AtomicIntegerArray解决问题
//定义静态数组
//static int[] arr = new int[100];
//多线程可以使用原子类的数组解决
static AtomicIntegerArray arr = new AtomicIntegerArray(100);
多行代码的线程安全问题
用卖票问题来演示线程安全
public class MyThread extends Thread {
//定义静态变量
static int ticket = 100;
@Override
public void run() {
while(true){
if(ticket<=0){
break;
}
System.out.println(getName() + "卖出了" + ticket + "号票");
ticket--;
}
}
}
public class Demo {
public static void main(String[] args) {
//开启线程
MyThread mt = new MyThread();
mt.setName("窗口一");
mt.start();
//开启线程
MyThread mt2 = new MyThread();
mt2.setName("窗口二");
mt2.start();
}
}
synchronized同步代码块
格式:
synchronized(锁对象){
代码
}
流程:
同步代码块有一个锁对象,必须获取到锁才能进入代码块内部,只能有一个线程进入代码块,在线程出来之前,别的线程无法进入代码块。
锁对象:
java中任意类型的对象都可以放在同步代码块里面当做锁对象,只要是一个对象就可以
要求多个线程用的锁对象必须是同一个对象,如果不是同一个对象起不到效果
public class MyThread extends Thread {
//定义静态变量
static int ticket = 100;
@Override
public void run() {
while(true){
//同步代码块
//锁对象两个要求:1.可以是任意类型 2.多个线程用的是同一个对象
synchronized ("abc") {
if (ticket <= 0) {
break;
}
System.out.println(getName() + "卖出了" + ticket + "号票");
ticket--;
}
}
}
}
//----------------------------------------------
public class Demo {
public static void main(String[] args) {
//开启线程
MyThread mt = new MyThread();
mt.setName("窗口一");
mt.start();
//开启线程
MyThread mt2 = new MyThread();
mt2.setName("窗口二");
mt2.start();
}
}
synchronized同步方法
同步方法锁对象是系统默认的,不用我们手动写…
格式:
定义一个同步方法:
public synchronized void method(){
}
同步方法的锁对象叫this
定义一个静态同步方法:
public static synchronized void method(){
}
静态同步方法的锁对象叫 类的字节码文件对象
(一个类在第一次被使用时会被加载到方法区,一个类只会被加载一次 XXX.class)
Lock类型锁
Lock是一个锁类型,有两个方法
lock():加锁
unlock():解锁
public class MyThread extends Thread {
//定义静态变量
static int ticket = 100;
//创建Lock对象
static Lock c = new ReentrantLock();
@Override
public void run() {
while(true){
//加锁
c.lock();
if(ticket<=0){
//解锁
c.unlock();
break;
}
System.out.println(getName() + "卖出了" + ticket + "号票");
ticket--;
//解锁
c.unlock();
}
}
}
-------------------------------------------------------------
public class Demo {
public static void main(String[] args) {
//开启线程
MyThread mt = new MyThread();
mt.setName("窗口一");
mt.start();
//开启线程
MyThread mt2 = new MyThread();
mt2.setName("窗口二");
mt2.start();
}
}
悲观锁和乐观锁
同步写法叫 悲观锁 ,不允许多个线程一起执行,解决了所有线程问题,但是效率低
CAS机制叫 乐观锁 , 允许多个线程一起执行,能解决原子性问题,效率比较高
开发中多线程如何避免安全问题
多线程问题出现需要条件:
1.要有多个线程
2.要有共享变量
3.多个线程在同时操作共享变量
今天我们学习是故意用了多线程操作共享数据,以后写代码我们尽量不要定义多个线程的共享变量,就不会出现问题…
并发包
之前我们的集合其实在多线程情况下也都有可能出问题
如果在多线程情况下需要使用共享集合,就需要使用并发包中的集合类型
ArrayList
ArrayList可能出现线程安全问题
CopyOnWriteArrayList可以解决线程安全问题
//定义集合
//static ArrayList<Integer> list = new ArrayList<>();
static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
HashSet
HashSet可能出现线程安全问题
CopyOnWriteArraySet可以解决线程安全问题
//定义集合
//static HashSet<Integer> set = new HashSet<>();
static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
HashMap
HashMap可能出现线程安全问题
Hashtable和ConcurrentHashMap都可以解决线程安全问题
//定义集合
//static HashMap<Integer,Integer> map = new HashMap<>();
//static Hashtable<Integer,Integer> map = new Hashtable<>();
static ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();
并发包的类执行效率更高
Hashtable其实是一个比较老的解决线程安全的类,他的方法是同步方法,
一个线程在执行的时候,其他线程只能等待,所以效率很低
ConcurrentHashMap是并发包的类,他是同步代码块(同步了方法内的一部分代码)+CAS机制,也能解决线程安全问题,效率比较高