GoMind Hub

java并发(含volatile,单例,线程池,消息队列,事务)

计算机知识思维导图:java并发(含volatile,单例,线程池,消息队列,事务)。网页展示前三层结构,可在线查看完整脑图并下载 GoMind 文件。

2026-08-25

计算机知识学习资料思维导图
## java并发(含volatile,单例,线程池,消息队列,事务)
### java并发(含volatile,单例,线程池,消息队列,事务)
#### 并发&锁相关
##### 锁的两大特性
##### 多线程的两种实现方法是什么?
##### 同步方式
##### 相关概念
##### volatile
##### 锁机制
##### jvm常见的锁
##### 锁场景,定义
#### 事务的并发控制
##### 锁类型有
##### 锁的三级锁定协议
##### 活锁:指程序无限期等待。解决方法是先来先服务策略
##### 死锁
##### 锁定的粒度
##### 意向锁
##### ORACLE锁
##### 解决锁争用的策略及方法
#### 分布式锁
##### 在分布式环境下,因为每一个项目实例运行在不同的虚拟机实例上,所以项目实例中的对象锁已经不起作用了(项目实例之间使用的是不同的锁)。锁。
##### 这时候就要另外再找一个节点,要求它对所有的项目实例都是同一个,由这个节点来管理
#### 扩展
##### 单例模式
##### 线程池
##### 消息队列
##### 事务控制
#### 问题
##### 在Java中Lock接口比synchronized块的优势是什么?
##### 你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
##### 在java中wait和sleep方法的不同?
##### 用Java实现阻塞队列
##### 用Java编程一个会导致死锁的程序,你将怎么解决?
##### 什么是原子操作,Java中的原子操作是什么?
##### Java中的 Volatile 关键是什么作用?怎样使用它?跟synchronized有什么不同?
##### 什么是竞争条件?你怎样发现和解决竞争?
##### 你将如何使用thread dump?你将如何分析Thread dump?
##### 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
##### 在Java中CycliBarriar和CountdownLatch有什么区别?
##### sleep() , wait() 的区别!
##### stop()与suspend() 不推荐使用的原因?
##### volatile与同步语法块,锁的区别
#### 关键字
##### 实现方式
##### 单例
##### 锁机制 或 并发的场景应对
##### 锁类型
##### 死锁概念
##### 隔离级别
##### 分布式锁
#### 参考
##### 并发/多线程
##### 事务并发控制
##### 锁类型
##### volatile(不稳定的)
##### 单例模式
##### 线程池
##### 消息队列
##### 事务

> 仅展示前三层结构;请在线查看完整脑图或下载 GoMind 文件。

java并发(含volatile,单例,线程池,消息队列,事务)

java并发(含volatile,单例,线程池,消息队列,事务)
每次都得等先拿到锁的线程把锁释放了,其他线程才有资格访问共享资源,期间线程对共享资源时独占的
互斥
一个线程修改了某个共享变量的值,其他线程能读取到最新的值,而不至于读取到脏数据
可见性
锁的两大特性
new hello("A").start();
class hello extends Thread
new Thread("threadName"){ ... }.start();
匿名Thread实现
new Thread(new helloR("线程A")).start();
class helloR implements Runnable
new Thread(new Runnable() { ... }).start();
匿名Rnunable实现
new Thread(() -> System.out.println("hello thread")).start();
Lambda
多线程的两种实现方法是什么?
sleep() 父级:thread 释放:定时 对象锁:不释放
wait() / nofity() 方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制
Java多线程之并发协作生产者消费者设计模式 - 推酷
wait() 父级:object 释放:notify,notifyAll 对象锁:释放
暂停方法
public synchronized void get(Thread thread)
同步方法
private static Object initLock = new Object();
synchronized (类.initLock)
BookCDRLog.java
 
/** 初始化锁 */
private static Object initLock = new Object();
 
synchronized (BookCDRLog.initLock)
            {
                if (!BookCDRLog.inited)
                {
                    instance.init();
                    BookCDRLog.inited = true;
                }
            }
同步代码块
private static Lock lock = new ReentrantLock()
同步锁:
ConnectionUtil.java
/** 实例锁 */
private static Lock lock = new ReentrantLock();
 
/** 连接锁[连接,释放] */
private static Lock connLock = new ReentrantLock();
 
    /**
     * 释放连接 <功能详细描述> [参数说明]
     *
     * @return void [返回类型说明]
     * @exception throws [违例类型] [违例说明]
     * @see [类、类#方法、类#成员]
     */
    public void release()
    {
        // 锁[getConnection(),release()]
        connLock.lock();
       
        try
        {
            if (null != conn && !conn.isClosed())
            {
                conn.close();
            }
        }
        catch (SQLException e)
        {
            logger.error("关闭连接错误!" + e.getMessage());
        }
        finally
        {
            // 锁-释放[getConnection(),release()]
            connLock.unlock();
        }
    }
connLock.lock();
connLock.unlock();
同步锁
synchronized不可主动中断,Lock可以
synchronized不可读写分离,Lock可以
synchronized不可获取当前线程是否获得锁,Lock可以
synchronized 的局限性 与 Lock 的优点
同步方式
已在锁线程中调用一个带锁的方法情况下,是否需要重新申请锁
由于synchronized和Lock都具备可重入性
可重入锁
synchronized就不是可中断锁
Lock是可中断锁
可中断锁
先近先出即公平
synchronized就是非公平锁
Lock锁: ReentrantLock 和 ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁
公平锁
队列锁就是将线程组织成一个队列,让每个线程在不同的存储单元上旋转,从而降低cache一致性流量
MCS队列锁
相关概念
读写主存中的数据没有CPU中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了CPU高速缓存。CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关
CPU高速缓存虽然解决了效率问题,但是它会带来一个新的问题:数据一致性
在程序运行中,会将运行所需要的数据复制一份到CPU高速缓存中,在进行运算时CPU不再也主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后才会将数据刷新到主存中
包含了三个操作:读取i值、i + 1 、将+1结果赋值给i;
非原子操作
volatile关键字只能保证每次读取的是最新的值,但是没办法保证对变量的操作的原子性
两个线程同时获得主内存中变量值1,两线程各自加1写会主内存结构将是2
i++
然后线程A执行+1操作并将结果写入高速缓存中,最后写入主存中,此时主存i==2,
线程B做同样的操作,主存中的i仍然=2。所以最终结果为2并不是3。这种现象就是缓存一致性问题。
两个线程从主存中读取i的值(1)到各自的高速缓存中,
例子
缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的
volatile
当某个CPU在写数据时,如果发现操作的变量是共享变量,则会通知其他CPU告知该变量的缓存行是无效的,因此其他CPU在读取该变量时,发现其无效会重新从主存中加载数据
通过缓存一致性协议
总线加LOCK#锁的话,只能有一个CPU能够运行,其他CPU都得阻塞,效率较为低下
通过在总线加LOCK#锁的方式
Untitled node
解决缓存一致性方案有两种
由来
?即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
1—在Java中,对基本数据类型的变量和赋值操作都是原子性操作;
i = 0;
2—包含了两个操作:读取i,将i值赋值给j
j = i ;
3—包含了三个操作:读取i值、i + 1 、将+1结果赋值给i;
i++;
4—同三一样
i = j + 1;
单线程环境下我们可以认为整个步骤都是原子性操作
在多线程环境下则不同,Java只保证了基本数据类型的变量和赋值操作才是原子性的( 注:在32位的JDK环境下,对64位数据的读取不是原子性操作*,如long、double )
要想在多线程环境下保证原子性,则可以通过锁、synchronized来确保
场景
同理:非原子性的操作,无法用volatile来保障同步
volatile是无法保证复合操作的原子性
原子性
如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫原子性?
原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分。将整个操作视作一个整体是原子性的核心特征。
原子是构成物质的基本单位(当然电子等暂且不论),所以原子的意思代表着——“不可分”
原子性理解
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。
当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,当其他线程读取共享变量时,它会直接从主内存中读取。
Java提供了volatile来保证可见性
当然,synchronize和锁都可以保证可见性。
可见性
控制指令重排序
即程序执行的顺序按照代码的先后顺序执行
volatile关键字能够通过提供内存屏障,来保证某些指令顺序处理器不能够优化重排,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
最著名的例子就是单例模式里面的DCL(双重检查锁)
Java提供volatile来保证一定的有序性
有序性
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的
总述
volatile相对于synchronized稍微轻量些,在某些场合它可以替代synchronized,但是又不能完全取代synchronized
对变量的写操作不依赖当前值;
该变量没有包含在具有其他变量的不变式中
只有在某些场合才能够使用volatile。使用它必须满足如下两个条件:
volatile经常用于两个两个场景:状态标记、double check
需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。比如:状态标记量,双重校验
使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。
作用/使用
volatile
定义:很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制
适合:同时刻,低频改写 (多读少写)
优点:吞吐量大,处理业务多
缺点:如在更新时发现已经被更新,需要业务重做,而重做依然可能被修改。也就是乐观锁也可能完成一个业务需要N次请求。
特点:更新带比对条件(版本号,时间戳)锁交付数据库管理
乐观锁
定义:很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁
适合:同时刻,高频改写(多写少读)
优点:一次请求完成业务(但在锁的前提下)
缺点:每次请求都存在锁,吞吐量受限(实际并发情况下,并不会每次都冲突,所以部分锁是浪费了)
特点:全程锁[锁由程序控制]
悲观所
定义:也叫短事务;一次业务过程只和服务器通信一次。
意图:业务要求
场景:多数业务属于这种场景。
在线
定义:也叫长事务;一次业务过程需要多次的和服务器通信。
意图:业务要求。
场景:修改审批流程的过程会持续多个请求,多个请求必须作为一个长事务对待。
离线
在线、离线
概念:A和B依次读取了数据,先读取的执行修改会成功,后读取的执行修改回失败。
思路:事务隔离级别或数据库锁。
在线悲观锁
概念:A打开了编辑界面,B就不能打开编辑界面了。
思路:自己实现锁。
离线悲观锁
概念:A和B依次读取了数据,先执行修改的会成功,后执行修改的会失败。
思路:版本字段。
在线乐观锁
概念:A和B依次打开了编辑界面,先执行修改的会成功,后执行修改的会失败。
思路:版本字段
离线乐观锁
在线、离线 & 乐观、悲观
锁机制
偏向锁是JDK1.6提出来的一种锁优化的机制
偏向锁
如果偏向锁失败,Java虚拟机就会让线程申请轻量级锁
轻量级锁
当轻量级锁失败,虚拟机就会使用重量级锁
重量级锁
自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环)
自旋锁
jvm常见的锁
若有多条线程,其中一条线程需要等到其他 所有 线程准备完所需的资源后才能运行,这样的情况可以使用闭锁
闭锁:CountDownLatch
若有多条线程,他们到达屏障时将会被阻塞,只有当所有线程都到达屏障时才能打开屏障,所有线程同时执行,若有这样的需求可以使用同步屏障。此外,当屏障打开的同时还能指定执行的任务。
同步屏障:CyclicBarrier
若有m个资源,但有n条线程(n>m),因此同一时刻只能允许m条线程访问资源,此时可以使用Semaphore控制访问该资源的线程数量。
信号量:Semaphore
闭锁只会阻塞一条线程,目的是为了让该条任务线程满足条件后执行;
而同步屏障会阻塞所有线程,目的是为了让所有线程同时执行(实际上并不会同时执行,而是尽量把线程启动的时间间隔降为最少)
闭锁 与 同步屏障 的区别
问题
锁场景,定义
并发&锁相关
排他锁(eXclusive Lock)
共享锁(Share Lock)
锁类型有
一级锁定协议是指事务T在修改对象之前,必须先对该对象进行加X锁,并直到事务结束时才释放该X锁,如果事务T仅仅是读取对象就,则不需进行任何加锁
一级锁定协议避免了数据修改丢失,但是无法避免脏读、不可重复读。
一级:修改前后
二级锁定协议是指在一级协议的基础上,加上事务T在读取对象之前必须加S锁,读完后立即释放S锁。
二级锁定协议避免了数据修改丢失、脏读。但是避免不了不可重复读。
二级:?
三级锁定协议是指在一级锁定协议的基础上,加上事务T在读取对象之间必须加S锁,直到事务结束后才释放S锁
三级锁定协议避免了数据修改丢失、脏读、不可重复读
三级:读写前后
锁的三级锁定协议
活锁:指程序无限期等待。解决方法是先来先服务策略
死锁:是指两个或多个事务都锁定了一些数据库对象,然后又需要锁定对方的数据库对象而且失败需要等待造成的。即相互等待对方锁定的资源释放。
预防死锁的方法有:一次锁定、顺序锁定(这是操作系统的解决方法,但不适用数据库)。
即如果事务的等待时间超过了规定的时限,就认为发生了死锁
缺点是:容易误判死锁;时限不好决定;
超时法
事务的等待图是一个有向图,如果事务T1等待事务T2,则T1和T2节点之间就有一条从T1指向T2的有向边。如果T1和T2之间产生回路就说明发生了死锁
等待图法
诊断
手动地选择一个处理死锁的代价最小的事务,将其撤销,使其释放所有已锁定的数据库对象。
解决死锁的常用方法
死锁
锁定对象的大小被称为锁定的粒度。
锁定的粒度
如果对一个节点加某种意向锁,则会对该节点的所有下级节点加这种锁(X锁、S锁)
如果对一个节点加某种锁(X锁、S锁),则必须先对该节点的所有上级节点加这种意向锁
如果对一个节点加IS锁,则表示对它的所有下级节点有加S锁的意向;
如果对一个节点加S锁,则必须先对该节点的各个上级节点加IS锁。
IS锁(Intended Share Lock即意向共享锁)
如果对一个节点加IX锁,则表示对它的所有下级节点有加X锁的意向;
如果对一个节点加X锁,则必须先对该节点的各个上级节点加IX锁。
IX锁(Intended eXclusive Lock即意向排他锁)
如果对一个节点加SIX锁,则表示对它加S锁,然后再加IX锁,即SIX=S+IX
SIX锁(Share Intented eXclusive Lock即共享意向排他锁)
意向锁分三种
意向锁
DDL锁(字典锁)、DML锁(数据锁)
ORACLE锁的分类
数据库级别锁、表级别锁、行级别锁。不支持列级别锁。
ORACLE锁的级别
将数据库设置成受限制方式
将数据库更改为只读方式
锁定数据库的两种方式
ORACLE锁
1、 不应该运行上事务,在操作时应该及时地使用COMMIT和ROLLBACK语句。
2、 避免使用表锁,而是使用ORACLE默认的加锁机制。
3、 在更改数据之前,可以使用 SELECT … FOR UPDATE NOWAIT语句试探性的加锁,通过返回的提示了解具体情况,避免莫名的等待。
4、 非高峰时期使用DDL语句。
及时检测系统中是否存在锁,调查锁存在的原因,适当时机使用 ALTER SYSTEM KILL SESSION‘sid,serial#’杀死锁。
解决锁争用的策略及方法
事务的并发控制
在分布式环境下,因为每一个项目实例运行在不同的虚拟机实例上,所以项目实例中的对象锁已经不起作用了(项目实例之间使用的是不同的锁)。锁。
比如可以由redis服务器来管理锁。
这时候就要另外再找一个节点,要求它对所有的项目实例都是同一个,由这个节点来管理
分布式锁
缺点:提前new出来实例了,并不是在第一次调用get方法时才实例化,没有进行延迟加载。
jvm
/**   
 * 实现单例访问Kerrigan的第五次尝试   
 */    
public class SingletonKerriganE {     
      
    /**   
     * 单例对象实例   
     */    
    private static SingletonKerriganE instance = new SingletonKerriganE();     
      
    public static SingletonKerriganE getInstance() {     
        return instance;     
    }     
}
饿汉式的创建方式在一些场景中将无法使用:譬如Kerrigan实例的创建是依赖参数或者配置文件的,在getInstance()之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了
不管你用不用,一开始就创建单例对象
饿汉式
多线程情况,容易实例化多次
不控制同步
/**   
 * 实现单例访问Kerrigan的第一次尝试   
 */    
public class SingletonKerriganA {     
      
    /**   
     * 单例对象实例   
     */    
    private static SingletonKerriganA instance = null;     
      
    public static SingletonKerriganA getInstance() {     
        if (instance == null) {                              //line A     
            instance = new SingletonKerriganA();          //line B     
        }     
        return instance;     
    }     
}
指令重排序风险,次线程在使用单例时有可能对象不为null(先指向了内存地址),但也找不到实例。导致程序异常
doubleCheck(DCL)
/**   
 * 实现单例访问Kerrigan的第四次尝试   
 */    
public class SingletonKerriganD {     
      
    /**   
     * 单例对象实例   
     */    
    private static SingletonKerriganD instance = null;     
      
    public static SingletonKerriganD getInstance() {     
        if (instance == null) {     
            synchronized (SingletonKerriganD.class) {     
                if (instance == null) {     
                    instance = new SingletonKerriganD();     
                }     
            }     
        }     
        return instance;     
    }     
}
private volatile static SingletonKerriganD instance = null;
可以保证每次都去instance都从主内存读取,就可以使用DCL的写法来完成单例模式
Volatile & doubleCheck(DCL)
jvm
/**   
 * 实现单例访问Kerrigan的第六次尝试   
 */    
public class SingletonKerriganF {     
      
    private static class SingletonHolder {     
        /**   
         * 单例对象实例   
         */    
        static final SingletonKerriganF INSTANCE = new SingletonKerriganF();     
    }     
      
    public static SingletonKerriganF getInstance() {     
        return SingletonHolder.INSTANCE;     
    }     
}
是在你真正用到的时候才去实例化单例对象
懒汉式
1.为对象分配内存
2.初始化实例对象
3.把引用instance指向分配的内存空间
java内存模型(jmm)并不限制处理器重排序,在执行instance=new Singleton();时,并不是原子语句,实际是包括了下面三大步骤
优化重排后执行顺序为:1,3,2, 这样在线程1执行到3时,instance已经不为null了,线程2此时判断instance!=null,则直接返回instance引用,但现在实例对象还没有初始化完毕,此时线程2使用instance可能会造成程序崩溃
现在要解决的问题就是怎样限制处理器进行指令优化重排。
指令重排序:这个三个步骤并不能保证按序执行,处理器会进行指令重排序优化,存在这样的情况:
优化重排后执行顺序为:1,3,2, 这样在线程1执行到3时,instance已经不为null了,线程2此时判断instance!=null,则直接返回instance引用,但现在实例对象还没有初始化完毕,此时线程2使用instance可能会造成程序崩溃。
Volatile
现在要解决的问题就是怎样限制处理器进行指令优化重排。
Volatile
安全管理器(SecurityManager)的checkPermission方法来限制这种突破
通过反射构造单例对象
如果单例对象有必要实现Serializable接口(很少出现),则应当同时实现readResolve()方法来保证反序列化的时候得到原来的对象
通过序列化构造单例对象
破坏单例/防御
单例模式
线程池作用就是限制系统中执行线程的数量
可以自动或手动设置线程数量,达到运行的最佳效果;
少了浪费了系统资源,多了造成系统拥挤效率不高
用线程池控制线程数量,其他线程排队等候
机制
线程池的作用
减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)
为什么要用线程池
能和Timer/TimerTask类似,解决那些需要任务重复执行的问题
ScheduledExecutorService
ExecutorService的默认实现
ThreadPoolExecutor
周期性任务调度的类实现
ScheduledThreadPoolExecutor
ExecutorService
Executor
实现类
一个单线程的线程池
此线程池保证所有任务的执行顺序按照任务的提交顺序执行
newSingleThreadExecutor
固定大小的线程池
每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小
newFixedThreadPool
一个可缓存的线程池
回收部分空闲(60秒不执行任务)的线程
此线程池不会对线程池大小做限制
newCachedThreadPool
大小无限的线程池
此线程池支持定时以及周期性执行任务的需求
newScheduledThreadPool
Executors类里面提供了一些静态工厂,生成一些常用的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, threadNum, 300,TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(3), new ThreadPoolExecutor.CallerRunsPolicy());
executor.execute(new PrintStringThread(*));
自定义实现
是指线程处于拥塞的时候形成的调度队列
队列是先进先出
栈是后进先出
Queue
“阻塞队列”:可以提供阻塞功能的队列
BlockingQueue
线程队列
工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们
直接提交
无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待
无界队列
使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽
有界队列
BlockingQueue:排队有三种通用策略/阻塞队列
线程池
消息队列能存放消息,也能发送接收消息,是各个系统之间相互通讯的中间组件,利用系统之间的解耦从微观山来说,消息队列只是一种存放数据的数据结构,提供消息的存放及获取接口
线程池是一个线程资源的管理方式,管理线程的启停及再利用
消息队列和线程池区别
基于发布订阅模型,分布式应用异步解耦,可以增加应用的水平扩展性,增加前端应用快速客户反应能力。
一对多,多对多异步解耦
大促等流量洪流突然来袭时,MQ可以缓冲突发流量,避免整个系统崩溃。
流量削峰
做为重要日志的监控通信管道,将应用日志监控对系统性能影响降到最低。
日志监控
应用解耦
消息通讯
通用场景
社交应用和物联网应用,需要大规模设备或终端持续的长连接,进行点对点推送,一对多广播式推送。
消息推送
发送金融报文,实现金融准实时的报文传输,可靠安全。
金融报文
将电信信令封装成消息,传递到各个控制终端,实现准实时控制和信息传递。
电信信令
行业应用
概述_快速入门_消息队列 MQ-阿里云
订阅消息_快速入门_消息队列 MQ-阿里云
接入指导
生产者发送一条消息到queue,一个queue可以有很多消费者,但是一个消息只能被一个消费者接受
Queue实现了一个可靠的负载均衡
点对点:Queue,不可重复消费
发布者发送到topic的消息,只有订阅了topic的订阅者才会收到消息
当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到这个消息的拷贝
发布/订阅:Topic,可以重复消费
两种模式
本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已
其主要用途:不同进程Process/线程Thread之间通信。
什么是消息队列
系统解耦:项目开始时,无法确定最终需求,不同进程间,添加一层,实现解耦,方便今后的扩展。
消息缓存:系统中,不同进程处理消息速度不同,MQ,可以实现不同Process之间的缓冲,即,写入MQ的速度可以尽可能地快,而处理消息的速度可以适当调整(或快、或慢)
为什么要用
冗余
可恢复
可伸缩
提升系统可靠性
调整模块
提升系统可扩展性
单次送达
排序保证
异步通信
数据流的阶段性能定位
其他
消息队列的要求
kafka、JMS就属于这个流派,生产者会发送 key 和 数据 到Broker,由Broker比较key之后决定给那个消费者
重Topic流
这种的代表是RabbitMQ(或者说是AMQP)。生产者发送 key 和 数据 ,消费者定义订阅的 队列 ,Broker收到数据之后会通过 一定的逻辑计算出key对应的队列,然后把数据交给队列
注意到了吗?这种模式下解耦了key和queue,在这种架构中queue是非常轻量级的(在RabbitMQ中它的上限取决于你的内存),消费者关心的只是自己的queue;生产者不必关心数据最终给谁只要指定key就行了,中间的那层映射在AMQP中叫exchange(交换机)
轻Topic流
有broker(中间商)
此门派是AMQP的“叛徒”,某位道友嫌弃AMQP太“重”(那是他没看到用Erlang实现的时候是多么的行云流水) 所以设计了zeromq
MQ是更高级的Socket,它是解决通讯问题的
ZeroMQ做的事情就是封装出一套类似于scoket的API可以完成发送数据,读取数据
顿悟了吗?Actor模型,ZeroMQ其实就是一个 跨语言的、重量级的Actor模型邮箱库
无broker
如果你用kafka做通讯总线那绝对的不会快只能更慢;
kafka在乎的是性能,速度
你想要rabbitmq实现分布式,那真的是难为它
rabbitmq追求的是灵活
如果你拿zeromq来做大数据量的传输功能,不是生产者的内存“爆掉”就是消费者被“压死”
zeromq追求的是轻量级、分布式
总结
MQ的流派
消息队列
事务是访问数据库的一个操作序列,数据库应用系统通过事务集来完成对数据库的存取。事务的正确执行使得数据库从一种状态转换成另一种状态
什么是事务
即不可分割性,事务要么全部被执行,要么就全部不被执行。
如果事务的所有子事务全部提交成功,则所有的数据库操作被提交,数据库状态发生转换;
如果有子事务失败,则其他子事务的数据库操作被回滚,即数据库回到事务执行前的状态,不会发生状态转换。
同成同败
原子性(atomicity)
事务的执行使得数据库从一种正确状态转换成另一种正确状态。
状态正常切换
一致性(consistency)
在事务正确提交之前,不允许把该事务对数据的任何改变提供给任何其他事务,即在事务正确提交之前,它可能的结果不应显示给任何其他事务。
自己用,别人不能用
隔离性(isolation)
事务正确提交后,其结果将永久保存在数据库中,即使在事务提交后有了其他故障,事务的处理结果也会得到保存。
物理提交,磁盘改变
持久性(durability)
事务必须服从ISO/IEC所制定的ACID原则。
每个SQL语句都是一个独立的事务,当数据库系统执行完一个SQL语句后,会自动提交事务
自动提交模式
必须由数据库客户程序显示指定事务开始边界和结束边界
手动提交模式
INNODB
BDB
其中MyISAM不支持数据库事务
MySQL中create table 语句默认为MyISAM类型
MyISAM
MySQL中数据库表分为3种类型
注
数据库系统支持两种事务模式
Untitled node
第一类丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖。
Untitled node
脏读:一个事务读到另一个事务未提交的[更新]数据。
Untitled node
虚读/幻读:一个事务读到另一个事务已提交的[新插入]的数据。
Untitled node
不可重复读:一个事务读到另一个事务已提交的[更新]数据。
Untitled node
第二类丢失更新:这是不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据。
相对参考事务的提交前,提交后的可能操作(重新读取,提交覆盖,撤销覆盖)出现的问题
总结
并发问题可归纳为以下几类
行共享锁定
行独占锁定
表共享锁定
表共享行独占
表独占
Oracle数据库常用的5种锁定
当数据库系统采用read Commited隔离级别时,会导致不可重复读和第二类丢失更新的并发问题,可以在应用程序中采用悲观锁或乐观锁来避免这类问题
避免所有事务的并发问题,但性能影响最严重
Serializable(串行化):一个事务在执行过程中完全看不到其他事务对数据库所做的更新。
mysql默认
Repeatable Read(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他事务对已有记录的更新。
oracle默认
Read Commited(读已提交数据):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且能看到其他事务已经提交的对已有记录的更新
Read Uncomitted(读未提交数据):一个事务在执行过程中可以拷打其他事务没有提交的新插入的记录,而且能看到其他事务没有提交的对已有记录的更新。
隔离级别分级
上文“锁的三级锁定协议”
锁定级别对应的具体做法
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大
优先考虑把数据库系统的隔离级别设为Read Commited,它能够避免脏读,而且具有较好的并发性能
尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制
数据库事务的四大特性以及事务的隔离级别 - fjdingsd - 博客园
参考
隔离级别
SQL语句:select ... for update
在Hibernate中使用get,load时如session.get(Account.class,new Long(1),LockMode,UPGRADE)
A.在应用程序中显示指定采用数据库系统的独占所来锁定数据资源
B.在数据库表中增加一个表明记录状态的LOCK字段,当它取值为“Y”时,表示该记录已经被某个事务锁定,如果为“N”,表明该记录处于空闲状态,事务可以访问它。增加锁标记字段就可以实现。
悲观锁有两种实现方式
A.悲观锁:指在应用程序中显示的为数据资源加锁。尽管能防止丢失更新和不可重复读这类并发问题,但是它会影响并发性能,因此应该谨慎地使用。
乐观锁是由程序提供的一种机制,这种机制既能保证多个事务并发访问数据,又能防止第二类丢失更新问题
在应用程序中可以利用Hibernate提供的版本控制功能来视线乐观锁,OR映射文件中的<version>元素和<timestamp>都具有版本控制的功能,一般推荐采用<version>
利用Hibernate的版本控制来实现乐观锁
B.乐观锁:乐观锁假定当前事务操作数据资源时,不回有其他事务同时访问该数据资源,因此完全依靠数据库的隔离级别来自动管理锁的工作。应用程序采用版本控制手段来避免可能出现的并发问题。
锁可以分为以下几类
JDBC事务控制的局限性在一个数据库连接内,但是其使用简单
JTA事务的功能强大,事务可以跨越多个数据库或多个DAO,使用也比较复杂
容器事务,主要指的是J2EE应用服务器提供的事务管理,局限于EJB应用使用
三种事务差异
支持严格的ACID属性
可靠
高效
状态可以只在资源管理器中维护
应用编程模型简单
本地事务的优点
不具备分布式事务处理能力
隔离的最小单位由资源管理器决定,如数据库中的一条记录
本地事务的局限
本地事务
本地事务无法解决分布式场景中的事务问题
两个账户大多数情况下会被切分到不同的数据库上
两个操作会是两次服务调用
这两个操作要求做到要么同时成功、要么同时失败。因此引入了分布式事务问题
转账
交易后台会进行库存检查、下单、减库存、更新订单状态等一连串的服务调用,每一个操作对应一个独立的服务,服务一般会有独立的数据库,因此会产生分布式事务问题
下单
典型的分布式事务场景
在上面的场景中会出现分布式事务问题,那么全局事务就是一个标准的分布式事务
Untitled node
全局事务的定义
全局事务,作为一种标准的分布式事务解决方案,他解决了本地事务无法满足分布式场景中数据的ACID的要求。
采用2PC进行事务控制的全局事务也必然存在效率低的问题。这也是全局事务最致命的缺点,在提倡微服务的今天,这是不能容忍的
全局事务的优缺点
全局事务
事务管理的角度
事务控制
扩展
在Java中Lock接口比synchronized块的优势是什么?
你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行
在java中wait和sleep方法的不同?
用Java实现阻塞队列
现场定因:thread dump
用Java编程一个会导致死锁的程序,你将怎么解决?
什么是原子操作,Java中的原子操作是什么?
自从Java 5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性、顺序性和一致性
Java中的 Volatile 关键是什么作用?怎样使用它?跟synchronized有什么不同?
什么是竞争条件?你怎样发现和解决竞争?
你将如何使用thread dump?你将如何分析Thread dump?
start()方法时你将创建新的线程,并且执行在run()方法里的代码
为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
在Java中CycliBarriar和CountdownLatch有什么区别?
sleep() 父级:thread 释放:定时 对象锁:不释放
wait() 父级:object 释放:notify,notifyAll 对象锁:释放
sleep() , wait() 的区别!
stop(): 不完全,会解除线程中所有的锁定.不方便定位问题.
suspend():容易死锁。建议使用标记结合wait(),notify()使用.
stop()与suspend() 不推荐使用的原因?
volatile的特点是当线程对变量修改后马上会与内存保证可见性,同时禁止重排序保证程序执行的有序性。
由于它操作的是副本并不会对主内存加锁,所以并不具体同步语法块以及锁的特点即可一时刻同一变量只允许一个线程操作。
不锁线程
保证可见性,有序性 (针对CPU或JVM)
volatile关键字只能保证每次读取的是最新的值,但是没办法保证对变量的操作的原子性
两个线程同时获得主内存中变量值1,两线程各自加1写会主内存结构将是2,而不是3
如非原子性操作: i++
无法保证原子性
volatile
同步语法块 synchronized
synchronized不可主动中断,Lock可以
synchronized不可读写分离,Lock可以
synchronized不可获取当前线程是否获得锁,Lock可以
锁Lock
锁线程
volatile与同步语法块,锁的区别
问题
sycn,lock 差别
实现方式
方式:饿汉式(开始就要),懒汉(用时给你)
if (myThread == null) { // 校验:已知实例化情况,减少`synchronized`同步消耗
synchronized (sycnBlock_obj) {
if (myThread == null) {// 同步校验,未实例化再实例化
1.为对象分配内存
2.初始化实例对象
3.把引用instance指向分配的内存空间
正常逻辑顺序
1.为对象分配内存
3.把引用instance指向分配的内存空间
2.初始化实例对象
异常的指令优化顺序
总结:先分配了内存, 会导致myThread == null为false,而return的myThread可能还未实例对象
// 没加Volatile可能在指令重排优化试出错
myThread_NonVolatile = new MyThread();
例
指令重排序风险,次线程在使用单例时有可能对象不为null(先指向了内存地址),但也找不到实例。导致程序异常
Volatile
风险
doubleCheck(DCL)
/**   
 * 实现单例访问Kerrigan的第四次尝试   
 */    
public class SingletonKerriganD {     
      
    /**   
     * 单例对象实例   
     */    
    private static SingletonKerriganD instance = null;     
      
    public static SingletonKerriganD getInstance() {     
        if (instance == null) {     
            synchronized (SingletonKerriganD.class) {     
                if (instance == null) {     
                    instance = new SingletonKerriganD();     
                }     
            }     
        }     
        return instance;     
    }     
}
CPU指令重排
volatile
破坏单例/防御
单例
在线、离线 & 乐观、悲观
锁机制 或 并发的场景应对
排它,共享
锁类型
程序诊断方法:超时与轨迹回路
死锁概念
不同隔离级别会出现的问题
锁实现的控制范围反应出对应的隔离级别
隔离级别
后置提交的多数据库事务
两段式事务(预备,确认)
TCC(尝试,确认,取消)
实现:radis,Consul
分布式锁
关键字
Java 并发开发:Lock 框架详解
Java线程并发中常见的锁 - 推酷
15个Java多线程面试题及答案 - 推酷
提升开发效率的Java并发神器——闭锁、同步屏障、信号量 - 推酷
Java多线程之并发协作生产者消费者设计模式 - 推酷
Java核心技术点之多线程 - 简书
使用分布式锁解决并发问题
并发/多线程
事务的并发控制 - Oracle数据库栏目 - 红黑联盟
事务并发控制和锁机制 - Coding小飞侠的专栏 - 博客频道 - CSDN.NET
事务并发控制
乐观离线锁
悲观离线锁
J2EE事务并发控制策略总结
在线
离线
.NET:在线悲观锁、在线乐观锁、离线悲观锁、离线乐观锁代码示例
在线悲观锁
离线悲观锁
在线乐观锁
离线乐观锁
.NET:再谈在线悲观锁、离线悲观锁、在线乐观锁和离线乐观锁
锁类型
理解volatile - 推酷
【死磕Java并发】-----深入分析volatile的实现原理 - 推酷
Java多线程并发编程之volatile关键字解析 - 推酷
volatile(不稳定的)
单例模式、双检测锁定DCL、volatile(转) - 一个普通的程序员 - ITeye技术网站
探究java多线程中正确的单例模式 volatile关键字 - gloryzyf - 博客频道 - CSDN.NET
单例模式分类之懒汉式与饿汉式 - 紫羽风的博客 - 博客频道 - CSDN.NET
单例模式
java自带线程池和队列详细讲解 - 开源中国社区
聊聊并发(三)——JAVA线程池的分析和使用
Java四种线程池的使用
Java ExecutorService四种线程池的例子与说明 - 德州仪器 - 博客频道 - CSDN.NET
java多线程总结五:线程池的原理及实现 - 王爵的技术博客 - 博客园
Spring线程池使用方法
Java多线程和线程池 - ImportNew 之 Spring线程池配置
线程池
消息队列_云消息_订阅管理监控_分布式应用-阿里云
消息队列的两种模式 - - 博客频道 - CSDN.NET
消息队列(Message Queue)基本概念_知识库_博客园
消息队列的流派之争 - 推酷
Java消息队列--JMS概述 - 推酷
Redis实现简单消息队列 - 简书
Spring Boot 揭秘与实战(六) 消息队列篇 - RabbitMQ - 梁桂钊 - 掘金专栏
消息队列
JAVA事务的概念 - kristain - 博客园
[Java]Spring数据库事务基础知识 - 推酷
Java基础——事务 - 推酷
Java中的事务——全局事务与本地事务 - 推酷
单数据库事务
基于后置提交的多数据库事务
prepare预提交与commit确认提交
两段式事务也就是著名的XA事务
XA是由X/Open组织提出的分布式事务的规范
两段式事务
TCC事务的全称为:Try-Confirm/Cancel,翻译成中文即:尝试、确定、取消
TCC事务是一种编程模式,如果SOA接口的提供者与调用者都遵从TCC编程模式,那么就能最大限度的保证数据一致性
非TCC模式的扣除金币操作,接口提供者只需要提供一个SOA接口即可,接口的作用就是扣除金币
扣除金币Try接口,尝试扣除金币
扣除金币Confirm接口,确定扣除金币
扣除金币Cancel接口,取消扣除金币
而TCC模式的扣除金币操作,接口提供者针对扣除金币这一操作需要提供三个SOA接口
差异
TCC事务
以交易系统为例,看分布式事务架构的五大演进
事务
参考