Java_Juc
依赖改变
1 |
|
前置知识
进程和线程
进程就是: 一个软件实例就是一个进程
线程就是: 进程的子集
并发和并行
并发(concurrent)就是, 一个核心迅速在多个任务之间切换
并行(parallel)就是, 多个核心同时在多个任务运行。
异步和同步
同步: 不需要等待结果的返回
异步: 需要等待结果的返回’
运行效率提升
创建和运行线程
直接使用Thread
1 | Thread t = new Thread() { |
Runnable 配合Thread
1 | Runnable runnable = new Runnable() { |
可以试着使用lambda表达式把上诉代码精简
Thread 和 Runnable之间的关系
上诉的两个方法中
- 方法一: 是把线程和任务合并在一起了
- 方法二: 是把线程和任务分开了
并且使用 Runnable 可以更加的和线程池等高级api 配合。
FutureTask 配合Thread
可以获取任务的执行结果, 他和Runnable 有关系
1 | // 创建任务对象 |
线程运行
查看进程线程的方法
JConsole
- 打开cmd
- 输入JConsole
- 然后就可以连接你想要连接的Java服务
对于你想要监听的 类, 你需要做如下操作
1 | java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote - |
栈与栈帧
略(很简单)
多线程的栈与栈帧
每个线程都有自己独立的栈内存, 线程之间的栈互相不干扰
线程的上下文切换(Thread context Switch)
从使用cpu到不使用cpu的原因
常见方法
看pdf
start
getState 获取线程的状态
在未start之前, 调用线程的getState方法会得到一个NEW, 在调用start 之后, 调用线程的getState 方法会得到一个 RUNNABLE
- 连续调用两次start 方法
sleep
使得线程的状态由Running 变成 Timed Waiting 状态
线程打断
1 | Thread t1 = new Thread("t1") { |
报错信息如下
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at cn.itcast.test.Test7$1.run(Test7.java:14)
- 同时我们还需要注意一个睡眠结束的线程未必会立即执行
sleep方法更新
1 | // 原来 |
yield
使得线程从 running 变成 runnable 状态, 然后调用其他的线程, 如果没有其他的线程要执行会把机会继续让该线程进行
线程优先级(setPriority)
提示任务调度器去优先调度这个线程, 但是这只是个提示,不一定会执行
当cpu 繁忙的时候,优先级高的就会被分配尽可能多的服务
在Runnable 构造器中, 使用Thread.yield()可以实现让的效果
而setPriority 需要对线程对象 进行操作。
sleep的小应用
略
join
等待线程的结束, 拥塞主线程
- 如果下面这段代码不加上 join 方法, 那么就会出现r = 0 . 但是又了join之后就会等到这个线程结束之后再去调用后面的方法
1 | static int r = 0; |
- 带参数的join
设置等待的最大时间, 如果超过这个时间就不等待
interrupt
介绍不同阻塞状态被打断的会发生的事件
打断sleep join wait
- sleep 会抛出异常, 同时打断的那一刻会有一个打断标记, 但是会立刻清空,把它变成false
- isInterrupted方法, 获取线程的打断标签(如果被正常的打断是为true)
如果在一个线程里面, 我们想要获得这个线程的实例
那么我们可以使用这个方法
Thread.currentThread()
获取打断标签 Thread.currentThread().isInterrupted()
两个阶段终止模式
线程T1 终止 线程 T2, 这里的终止, 不会立刻终止, 而是会等T2处理好一切之后再终止
错误思路
强制终止, stop 方法真正杀死线程。
使用System.exit(int) 方法停止线程。
正确思路
重新设置打断标记, 可以使得原本为false的标记变成true
1 | import lombok.extern.slf4j.Slf4j; |
打断park
park 可以让进程暂时停止
LockSupport.park()
通过对线程使用interrupt方法, 可以让他停止暂停
但是如果你打断了它一次, 再打断它就没用了(但是可以通过调用Thread.interrupted使得打断标记重置为假的)
调用LockSupport.park(),开始打断
不推荐使用的方法 stop(使用两阶段终止模式来停止)、 suspend resume 。
守护线程
- 用例: 垃圾回收器就是一个守护线程
- Tomcat中的Acceptor 和 Poller 都是守护请求, 当它接受到shutdown命令之后,就会关闭掉这两个线程
线程的状态
五种状态
初始状态
可运行状态
运行状态
终止状态
阻塞状态
这是腿上面这几种状态的描述
六种状态
- synchroniz 是一个锁操作,
统筹规划习题
- 泡茶问题
方法一 : 使用join 方法
共享模型 之 管程
- 问题引入
两个 线程同时加加减减, 然后停止, 但是结果和应该的结果不符合
上面这个是 java的 自增和自减 运算符的 操作过程
临界区
多个线程访问共享资源
多个线程读写共享资源时发送了指令交错, 就会出现错误
在一段代码块如果存在对一个共享资源的多线程读写操作, 这个代码块就是一个临界区
可以理解为 一个代码块里面
1 | static void increment { |
竞态条件
多个线程在临界区内执行, 由于代码块的执行顺序不同而导致结果没有办法预测,称作竞态条件
synchronized(对象锁)
语法
1 | synchronized(对象) { |
1 | public class Test17{ |
1 | class Test{ |
线程八锁
- 类对象和 实例对象的区别
线程安全的判断
- 出现错误的情况
- 如果将list切换为局部变量的时候就不会有事情
- 但是如果局部变量暴露到外部就会有问题
当创建了子类之后, 并且方法为public ,子类通过添加线程,就有可能出现问题
如果你不想子类重写方法, 请在方法前面加上final修饰符
这就是这些修饰符的作用所在哦 ,这就是开闭原则中的闭原则
常见的线程安全类
可变的线程安全类
虽然这些线程类中的每一个方法都实现了线程安全, 但是方法之间的组合不一定满足线程安全
参考如下示意图, 当然我们可以通过在外层在添加锁来解决这个问题
不可变的线程安全类
获取你有疑问, 为什么replace ,substring也对字符串会有修改效果, 但是还是可以保证线程安全呢?
因为它并没有改变字符串里面的属性,他只是创建了一个新的字符串
线程安全的例题
例题1
spring没有特别注释, 都是单例模式,这时候会出现线程安全.,
我们可以使用环绕通知, 将操作的线程变量变成局部变量
例题2
没有成员变量,且都是正常的局部变量, 所以是安全的
例题3(我们要避免外星方法)
总结
加了final不一定可以保证线程安全
需要使用上面两种类型才会安全
案例
买票问题
- 这里暂时找不到源代码
转账问题
这里非常坑爹 ,这里的this 只会锁住自己的money, 不会锁住别人的钱
实际上它的解决方法是将锁指定为 相同的class, 但是它的性能是不高的.(Account类对所有的对象都是共享的.我就拿出这些共享的东西来作为锁)
Monitor
java通过Klass Word 找到它的类对象
之前我们说的锁就是Monitor, 也就是监视器或者管程
- 上锁的原理
- 这是上锁的流程
下面是从**==字节码角度==**理解锁的竞争
过于难理解 ,不贴了
synchronize优化原理
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁_偏向锁升级为重量级锁-CSDN博客
轻量级锁
如果一个对象虽然有多个线程访问,但多线程访问的时间是错开的, 那么就可以升级为轻量锁
语法仍然是synchronized, 和重量级锁相似
cas表示交换操作, 是原子性
cas 操作失败会分为两种
第二种是 自己的线程 给自己加锁, 就会产生锁重入, 如下面这种程序
1 | static final obj = new Object(); |
锁膨胀
自旋优化
- 适合多核CPU下面才有意义
java 7 之后就不能控制是否开启自旋
偏向锁
- 没有使用偏向锁
- 使用了偏向锁
偏向状态
偏向锁会延迟生效, 所以需要过一会才会从正常状态变成偏向锁状态
我们可以通过在jvm 加上配置参数-XX:BiasedLockingStartupDelay=0来禁止延迟
当一个可以偏向的锁,使用hashcode之后, 就会变成无锁状态
如果存在一个其他线程也需要使用偏向锁对象的时候, 偏向锁也会变成轻量锁
当调用wait/notify 的时候就会撤销偏向锁和轻量锁给撤销, 变成重量级锁
批量重偏向
- 当撤销出现20次
如果有一个锁, 一开始由线程a掌控, 后面转变为 线程b掌控, 那么在线程b咋锁重入多次之后, 就会变成 偏向锁
批量撤销
- 当撤销出现40次
锁消除
利用 JIT(即使编译器, 会对java的字节码进行进一步的优化) 会优化锁(反正是一些 jvm的骚操作就对了)
wait
API 介绍
- 我们可以往 wait ( )里面添加一个时间, 这样他会在过了这个时间之后就唤醒
wait 和 sleep 的区别
sleep 睡眠的时候, 不会释放锁, 但是wait 会释放锁
sleep 不需要和 synchronize 配合使用, 但是wait 需要
sleep 是Thread 方法, wait 是Object 的方法
他们的共同点是 : 它们的状态都是TIMED_WAITING
实验总结
我们可以设置一个 唤醒的条件为 xxx, 如果确实是对应线程的唤醒就把xxx 改成true, 然后跳出while 循环。
- 唤醒条件。
- 唤醒线程
模式
同步模式
保护模式
- 保护模式大体上是这样实现的
使用join 就需要一直等待, 但是使用这个模式可以, 继续干别的
我们还可以给这个模式添加一些功能
增添超时时间
但是这里会有点问题, 如果出现虚假唤醒的时候, 你每次又得 重新等2秒, 所以修改为下面这种方式
join 原理
略(类似于上面超时的保护模式)
保护模式-拓展2
有点懵逼(到时候可以在看看)
异步模式 - 生产者消费者
- 测试代码
- 产生的结果为
park & Unpark
- park 就是 等待, 当调用unpark的时候就唤醒park的地方
- 如果先调用unpark的时候, park 就不会等待
差异
原理
- 但是多次unpark只会补充一次备用干粮
重新理解线程状态
多把锁
引入
就是把原本一个锁换成多个锁, 缩小锁粒度
死锁
略
a锁想要b锁
b锁想要a锁
定位死锁
1 | // 在terminal中输入,查看线程信息 |
哲学家就餐
每个人拿一个筷子, 但是在等别人的筷子