# java-concurrent-examples **Repository Path**: MrMacro/java-concurrent-examples ## Basic Information - **Project Name**: java-concurrent-examples - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-09-20 - **Last Updated**: 2021-12-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ##Java 并发包相关实例. 使用线程要点: 1.Java应用程序的main 函数是一个线程,是被JVM启动的时候调用,线程的名字叫main 2.实现一个线程,必须创建Thread实例,override run 方法,并且调用start 方法 3.在JVM启动后,实际上有多个线程,但是至少有一个非守护线程 4.当你调用一个线程start 方法,此时至少有两个线程,一个是调用你的线程,还有一个是运行run 方法的线程 5.线程的生命周期分为:new runnable running blocked terminated 线程的生命周期 1.new -> new Thread() 还不是一个线程 2.runnable -> start() 可执行状态 3.Running -> 拥有cpu执行权,调用执行 4.blocked -> lock 5.terminated -> 结束 线程创建规则 1.创建线程对象Thread ,默认有一个线程名,以Thread-开头,从0开始计数 构造函数 Thread() 2.如果在构造Thread 的时候没有传递Runnable 或者没有复写Thread 的run 方法,则该Thread 将不会调用任务东西 如果传递了Runnable 接口的实例,或者复写了Thread 的run方法,则执行该方法的逻辑单元(逻辑代码) 3.如果构造线程对象时未传入ThreadGroup ,Thread 会默认获取父线程的ThreadGroup 作为该线程的Thread 的Group, 此时子线程和父线程在同一个ThreadGroup join 等待当前线程执行完成 线程中断的几方式: 1.使用退出标志,使线程正常退出,也就是当 run() 方法完成后线程中止。 2.使用 stop() 方法强行终止线程,但是不推荐使用这个方法,该方法已被弃用。 3.使用 interrupt 方法中断线程。 死锁: A线程持有A锁,想要去获取B锁 B线程持有B锁,想要去获取A锁 这时候就会发生死锁现象 Java提供查看命令: 1.jps 查看当前系统所有Java进程pid 2.jstack pid 查看对应Java进程 线程状态 sleep 和wait 区别: 1.sleep 是Thread 类的方法 ,wait 是所有Object类方法 2.sleep 不会释放锁,wait 会释放锁,并且会把它加入到Object wait queue 3.使用sleep 不需要在synchronized中,而wait 需要。 4.使用wait 需要主动唤醒(wait(time)除外),而sleep 只需要等待对应休眠时间 volatile详解: i = 1; i = i + 1; 线程一: main memory -> 1 -> cache i+1 -> cache (2) -> main memory (2) 线程二: main memory -> 1 -> cache i+1 -> cache (2) -> main memory (2) 注:这里有个很严重的问题,就是缓存的可见性,当多个线程操作时,后面的线程对共享变量的改变是不可见的 解决方式: 1.给数据总线加锁 总线(数据总线、地址总线、控制总线) 2.CPU高速缓存一致性协议 缓存一致性协议(MESI) 核心思想: 1.当CPU写入数据的时候,如果发现该变量被共享(也就是说,在其他CPU中也存在该变量的副本), 会发出一个信号,通知其他CPU 该变量的缓存无效 2.当其他CPU 访问该变量的时候,重新到主内存中去获取 volatile 关键字: 一旦一个共享变量被volatile 修饰,就具备两层语义: 1.保证了不同线程间的可见性 2.禁止对其进行重排序,也就是保证了有序性 3.并未保证原子性 volatile 的使用场景: 1.状态两标记 2.保证前后的一致性 并发编程中三个比较重要的概念: 1.原子性 一个操作或多个操作,要么都成功,要么都失败,中间不能由于任何的因素中断 对基本数据类型的变量读取和赋值是保证了原子性的,要么都成功,要么都失败,这些操作不可被中断 a = 10 ; 原子性 b = a ; 不满足 ,1=> read a ;2=> assign b ; c++ ; 不满足,1=> read c ;2=> add ; 3=> assign c; c=c+1; 不满足,1=> read c ;2=> add ; 3=> assign c; 2.可见性 多线线程访问共享变量可见最新数据 使用 volatile 关键字保证可见性,当一个变量被标记为volatile,当他发生变化,会立马更新到主存中, 其他线程读取的时候直接去主存中拿数据 3.有序性 重排序只保证最终一致性,在单线程下没有问题,但在多线程下会有问题. happens-before 规则: 3.1 程序的顺序性规则 按照程序的顺序性,前面的操作Happens-before 于后面的任意操作,也就是说前面对某个变量的修改一定对后面的操作时可见的 3.2 volatile 规则 对一个volatile 变量的写操作,happens-before 后面对这个变量的读操作,也就是说一个volatile 变量的写操作对于后续 对这个volatile 变量的读操作可见 3.3 传递性 如果A Happens-before B,B happens-before C ,那么 A happens-before . 3.4 管程中锁的规则 对于一个锁的解锁 happens-before 对这个锁的加锁 3.5 线程start() 规则 3.6 线程join() 规则 3.7 线程中断规则 3.8 对象终结规则 如何将非线程安全的容器变成线程安全的容器? 实现思路是: 只需要把非线程安全的容器封装在对象内部,然后控制好访问路径. 并发容器及其注意事项: java 在1.5 版本之前所谓的线程安全的容器,主要指的是同步容器 使用Collections.synchronized... () 获取到线程安全的内部包装容器. java 在1.5 版本之后提供了性能更高的容器,我们一般称为并发容器 并发容器四大类: List - CopyOnWriteArrayList 顾名思义就是 写的时候会将共享的变量新复制一份出来,这样做的好处就是读操作完全无锁. 适用场景: 适用于写操作非常少的场景,而且能容忍读写的短暂不一致 因为写是基于新数组,读是基于原数组. 注:CopyOnWriteArrayList 迭代器是只读的,不支持增删改查,因为迭代器遍历的仅仅是一个快照,对快照进行增删改查没有意义. Map -ConcurrentHashMap -ConcurrentSkipListMap 从应用的角度来看,主要区别在于ConcurrentHashMap 的key 是无序的,而ConcurrentSkipListMap 的key 是有序的 ConcurrentSkipListMap 里面的SkipList 本身就是一种数据结构(跳表),跳表的插入、删除、查询操作的平均时间复杂度 是O(n) ,所以在高并发场景下若你对ConcurrentHashMap 的性能不满意,可以使用ConcurrentSkipListMap. 注:1.两者的key 和value 都不能为空,否则会抛出NullPointerException 运行时异常. Set -CopyOnWriteArraySet -ConcurrentSkipListSet Queue 根据一下两个维度来分类: 1.阻塞与非阻塞 2.单端与双端 阻塞队列使用 Blocking 关键字标识 单端队列使用 Queue 关键字标识 双端队列使用 Deque 关键字标识 这两个组合,可以将Queue 细分为四大类: 1.单端阻塞队列 -ArrayBlockingQueue (数组) -LinkBlockingQueue (链表) -SynchronousQueue (不持有队列) -LinkedTransferQueue (融合 LinkedBlockingQueue 和 SynchronousQueue 的功能,性能更好) -PriorityBlockingQueue (支持按照优先级出队) -DelayQueue (支持延时出队) 2.双端阻塞队列 -LinkedBlockingDeque 3.单端非阻塞队列 -ConcurrentLinkedQueue 4.双端非阻塞队列 -ConcurrentLinkedDeque 注:使用队列时,需要格外注意队列是否支持有界(所谓有界指的是内部队列是否有容量限制), 实际工作中,一般都不建议使用无界的队列,因为数据量大了就很容易导致OOM。 上面我们提到的这些Queue 中,只有 ArrayBlockingQueue 和 LinkedBlockingQueue 支持有界. 所以,我们使用其他无界队列时,一定要充分考虑是否存在导致OOM 的隐患.