当多个线程同时访问同一个临界资源时,原子操作可能被破坏,会导致数据丢失, 就会触发线程安全问题
临界资源: 被多个线程同时访问的对象
原子操作: 线程访问临界资源的过程中不可更改和缺失的操作
每个对象都默认拥有互斥锁, 该锁默认不开启.
当开启互斥锁之后,线程想要访问对象,则在需要拥有时间片的基础上也拥有锁标记,锁标记只能被一个线程拥有,拥有时间片和锁标记的线程才能执行自身内容,在此期间,其他线程只能等正在执行的线程执行结束释放锁标记和时间片之后才能进入就绪状态
synchronized: 开启互斥锁的关键字
思路: 在被线程同时访问的方法上加锁
访问修饰符 synchronized 返回值类型 方法名(参数列表){ ? ? }
package com.by.util;
?
import java.util.ArrayList;
import java.util.List;
?
/**
* 工具类-操作集合属性
*/
public class MyList {
? ?private List<Integer> list = new ArrayList<>();
?
? ?/**
? ? * 给集合属性添加元素
? ? * @param n 添加的元素值
? ? synchronized: 同步方法
? ? */
? ?public synchronized void insert(int n){
? ? ? ?list.add(n);
? }
? ?/**
? ? * 查看集合内容
? ? */
? ?public void query(){
? ? ? ?System.out.println("集合长度: " + list.size());
? ? ? ?for (int i = 0; i < list.size(); i++) {
? ? ? ? ? ?System.out.print(list.get(i)+" ");
? ? ? }
? }
?
}
思路: 让参与临界资源对象访问的线程自身加锁
synchronized(临界资源对象){ ? ?//需要被认定为原子操作的代码 }
使用: 所有访问同一临界资源的线程都需要同时添加同步代码块
package com.by.test2;
?
import com.by.util.MyList;
?
public class TestMyList {
? ?public static void main(String[] args)throws Exception {
? ? ? ?//创建两个线程,同时操作工具类,线程1负责往集合中添加元素1-5,线程2负责往集合中添加元素6-10
? ? ? ?//添加结束之后查看集合内容
? ? ? ?//创建工具类对象
? ? ? ?MyList m = new MyList();
? ? ? ?Thread t1=new Thread(()->{
? ? ? ? ? ?for (int i = 1; i <=5 ; i++) {
? ? ? ? ? ? ? ?synchronized (m) {
? ? ? ? ? ? ? ? ? ?m.insert(i);
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? });
?
? ? ? ?Thread t2=new Thread(()->{
? ? ? ? ? ?for (int i = 6; i <=10 ; i++) {
? ? ? ? ? ? ? ?synchronized (m) {
? ? ? ? ? ? ? ? ? ?m.insert(i);
? ? ? ? ? ? ? }
? ? ? ? ? }
? ? ? });
?
? ? ? ?t1.start();
? ? ? ?t2.start();
? ? ? ?//使t1和t2线程先进行添加操作
? ? ? ?t1.join();
? ? ? ?t2.join();
?
? ? ? ?//查看集合元素
? ? ? ?m.query();
? }
}
?
? /* * 张三上厕所 * 李四上厕所 * * 原子操作: 脱裤子-->蹲下来-->上厕所-->擦屁股-->穿裤子-->冲水-->走人 * *临界资源: 厕所-坑位 * *解决方式1:给厕所大门加锁 *解决方式2:自己给坑位加锁 * * * */
同步方法: 线程执行需要同时争抢时间片和锁标记,写法简单但效率较慢
同步代码块: 线程只需要争抢时间片, 开启互斥锁的线程默认拥有锁标记, 效率较快但写法相对繁琐
悲观锁: 悲观的认为集合一定会出现线程安全问题,所以直接加锁
乐观锁: 乐观的认为集合一定不会出现线程安全问题,如果安全问题发生,再利用算法解决问题(无锁机制)
JDK5.0,发布了一批无锁机制的线程安全的集合类
都来自于java.util.concurrent包
ConcurrentHashMap: CAS算法
compare and swap: 比较并交换
原有值,预期值,结果值: 当原有值与预期值相等时才会将结果值放入内存
int i=1;
i++;
原有值: 1 预期值: 1 结果值:2
CopyOnWriteArrayList:
当集合进行写(增删改)操作时,会先复制出一个副本,在副本中进行写操作,如果过程中出现线程安全问题,则舍弃当前副本,重新复制新的副本重复操作,直至副本中无异常,再将集合引用地址转换向副本地址,一次确保原集合中一定不会发生安全问题
特点: 舍弃写的效率提高读的效率,适用于读操作远多于写操作时
CopyOnWriteArraySet:
原理与CopyOnWriteArrayList一致, 在写时会对元素进行去重
什么是线程安全问题?
如何解决线程安全问题
同步方法和同步代码块的区别
线程安全的集合类及原理