赞
踩
分发:
Java SDK 并发包里的 Executor、Fork/Join、Future 本质上都是一种分工方法
同步:
一个线程执行完了一个任务,如何通知执行后续任务的线程开工,线程之间相互协作,而解决协作的核心技术就是管程
互斥:
互斥用于解决线程安全问题,保障同一时间只允许有一个线程访问共享变量,实现互斥的核心技术就是锁
在CPU多核场景下,两个线程同时操作内存中的值,会将内存值加载到cpu的缓存中,进行计算,最后再写回内存,两个cpu的缓存是不可见,导致最终的结果与预期是存在差距的
count+1的指令可以被拆分为以下三个步骤
1、count加载进cpu寄存器
2、count+1
3、count写回内存
cpu保证的原子性是指令级别的,而不是高级操作符(count+1 可以)
//如果使用Bank对象作为锁,会导致所有操作都是串行,性能效率差 //用不同的锁对受保护资源进行精细化管理,能够提升性能。这种锁叫细粒度锁。
- public class Bank {
- private int balance;
- private String password;
- //钱锁
- private final Object balanceLock = new Object();
- //密码锁
- private final Object passwordLock = new Object();
-
- private void saveMoney() {
- synchronized (balanceLock) {
- balance = +1;
- }
- }
-
- private int getMoney() {
- synchronized (balanceLock) {
- return balance;
- }
- }
-
- private void updatePass(String newPss) {
- synchronized (passwordLock) {
- password = newPss;
- }
- }
- }

- public class Account {
- private int balance;
- //Account在java加载过程中就已经被创建了,是所有account实例对象所共享的,
- // 在jvm中是唯一的,因此可以作为唯一共享的锁
- private void transfer(Account target, int mount) {
- synchronized (Account.class) {
- target.balance += mount;
- this.balance -= mount;
- }
- }
- }
优化 (但是会存在死锁问题)
死锁原因
账户A调用transfer,执行synchronized (target),此时账户A获取A的锁,等待B锁释放
账户B也调用了transfer,执行synchronized (target),此时账户B获取B的锁,等待A锁释放
此时账户A,B陷入无限等待的死循环,出现了死锁
- private void transfer(Account target, int mount) {
- synchronized (this) {
- synchronized (target) {
- this.balance = balance - mount;
- target.balance += mount;
- }
- }
- }
以账户的例子来说,当线程进行转账,需要同时获取A,B两个锁,才能进行操作,此时需要一个管理者来分配和释放资源
- class Allocator {
- private List<Object> als =
- new ArrayList<>();
-
- // 一次性申请所有资源
- synchronized boolean apply(
- Object from, Object to) {
- if (als.contains(from) ||
- als.contains(to)) {
- return false;
- } else {
- als.add(from);
- als.add(to);
- }
- return true;
- }
-
- // 归还资源
- synchronized void free(
- Object from, Object to) {
- als.remove(from);
- als.remove(to);
- }
- }
-
- class Account{
- private Allocator actr;
- private int balance;
- private void transfer2(Account target, int amt) {
- // 一次性申请转出账户和转入账户,直到成功
- while (!actr.apply(this, target)) {
- try {
- // 锁定转出账户
- synchronized (this) {
- // 锁定转入账户
- synchronized (target) {
- if (this.balance > amt) {
- this.balance -= amt;
- target.balance += amt;
- }
- }
- }
- } finally {
- actr.free(this, target);
- }
- }
-
- }
- }

获取锁的顺序按锁的ID从小到大排序
- class Account{
- private int balance
- private int id;
- private void transfer3(Account target, int amt) {
- //按锁的ID从小到大排,先获取的锁一定是最小的锁
- Account front = this;
- Account latter = target;
- if(front.id>latter.id){
- front = target;
- latter = this;
- }
- synchronized (front){
- synchronized (latter){
- if (this.balance > amt) {
- this.balance -= amt;
- target.balance += amt;
- }
- }
- }
- }
- }

创建多少线程是最合适的?
进行多线程开发,无疑是为了提高吞吐与降低延迟,设置最合适的线程,就是如果最大化提供内存和cpu的利用率
从两个场景出发: (可以通过Visual VM查看线程执行情况)
CPU密集型任务(即计算性任务),也非绝对,因为有些cpu核数本就比较少,也有任务开到几倍以上的核数(这块我也是很迷茫)
线程的数量 = CPU核数(lscpu查看cpu核数),一般在工程上可以设置为线程数=CPU核数+1
IO密集型任务
最佳线程数 =1 +(I/O 耗时 / CPU 耗时)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。