volatile和synchronized分别是怎么实现数据同步的?
volatile是一个轻量级的锁,保证了共享变量的“可见性”, volatile修饰的变量线程不会单独存取他的副本, 每次需要使用都是从主内存中读取,并且修改后立即刷新到主内存,保证线程同步 synchronized则是使用内置锁来保证数据的同步的 1、加在普通同步方法,锁是当前实例对象 2、加在静态同步方法,锁是当前类的class对象 3、加在同步方法块,锁是括号里面的对象乐观锁和悲观锁
悲观锁是指如果一个线程占用了一个锁,而导致其他所有需要这个锁的线程进入等待, 一直到该锁被释放,也就是说,锁被独占,典型的synchronized; 乐观锁是指操作并不加锁,而是抱着尝试的态度去执行某项操作, 如果操作失败或者操作冲突,那么继续重试,直到执行成功如何实现乐观锁?如何避免ABA问题
CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时, 只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起, 而是被告知这次竞争中失败,并可以再次尝试。 CAS操作中包含三个操作数 需要读写的内存位置(V)、进行比较的预期原值(A)、拟写入的新值(B)。 如果内存位置V的值与预期原值A匹配,那么处理器会自动将该位置值更新为新值B 否则不做处理,无论何种情况,它都会在CAS指令之前返回该位置的值 可以使用只增补减的版本号来避免ABA的问题如果让你设计一个高可用系统,你需要考虑哪些方面
1、数据库读写分离,分库分表,加索引 2、加缓存,二级缓存 本地缓存+Redis 3、MQ 解耦 异步通知 削峰(针对并发写的情况) 4、业务拆分 不同业务独立部署 5、增加ES分布式查询系统,分担搜索业务流量多线程(valatile和synchronize)
volatile解决可见性问题,基于lock指令 hsdis插件可以看到java程序运行的汇编指令 1、把当前处理器缓存行的数据写回到系统内存 2、写回内存的操作会使其他CPU里缓存了该内存地址的地址无效 引出缓存一致性的问题 缓存锁MESI 原子性synchronize 性能问题? 粒度(对象锁、类锁) 锁的优化:无锁-轻量锁-重量锁为何不推荐使用stop()和suspend()方法
stop():线程不安全。会解除由线程获取的所有锁定 suspend():容易发生死锁。线程停下来,但仍持有在这之前获取的锁同步集合与并发集合有什么区别
都支持线程安全,主要区别体现在性能和可扩展性,还有如何实现线程安全 同步(HashMap,Hashtable,HashSet,Vector,ArrayList)相比并发(ConcurrentHashMap,CopyOnWriteArrayList)会慢很多。 慢的原因主要是锁,同步集合会把整个Map或List锁起来,而并发集合不会。 并发集合实现线程安全是通过使用先进的和成熟的技术像锁剥离 比如ConcurrentHashMap会把整个Map划分成几个片段, 只对几个片段上锁,同时允许多线程访问其他未上锁的片段 读多写少适合使用并发集合线程池的优点
可以管理线程 保证系统不会因为大量的并发而因为资源不足挂掉如何避免死锁
死锁的产生的四个条件 互斥条件:一个资源只被一个进程使用 请求与保持条件:请求资源时,对已获得的资源不放手 不剥夺条件:进程获得的资源,在用完前,不能被占用 循环等待条件:若干线程之间形成头尾相接的循环等待资源管理 避免死锁的技术 加锁顺序 加锁时限 死锁检测读写锁可以用于什么应用场景
读写锁用于"多读少写"的场景 支持多个读操作并发执行,写操作只能由一个线程来操作 性能的提高受制于 1、数据被读取的频率与被修改的频率相比较的结果 2、读取和写入的实际 3、有多少线程竞争 4、是否在多处理机器上运行BIO NIO AIO的区别
BIO 同步阻塞,实现模式:一个连接一个线程 客户端有连接请求时服务端就需要启动一个线程进行处理 如果这个连接不做任何事情就会造成浪费,可以通过线程池改善 NIO 同步非阻塞,实现模式:一个请求一个线程 客户端发送的连接请求都会注册到多路复用器上, 多路复用器轮询到连接有I/O请求时才启动一个线程进行处理 AIO 异步非阻塞,实现模式:一个有效请求一个线程 客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理 NIO比BIO的改善之处: 把无效的连接挡在了启动线程之前,减少了这部分资源的浪费 AIO比NIO的改善之处 将暂时可能无效的请求挡在了启动线程之前 当一个请求来的话,开启线程进行处理,但这个请求所需要的资源还没就绪,此时必须等待 使用场景 BIO适用于连接数目比较小且固定的架构,对服务器资源要求比较高 NIO适用于连接数目比较多且连接比较短的架构,比如聊天服务器 AIO用于连接数据多且连接比较长得架构,比如相册服务器堆和栈
栈是一种具有先进后出的数据结构 堆是经过排序的树形数据结构,每个结点都有一个值。 堆的数据结构指的是二叉堆 堆的特点是根结点的值最小(或最大),且根结点的两个子树也会一个堆。 由于堆的特性,常用来实现优先队列,堆的存取是随意的 为什么要划分堆和栈 1、从软件设计的角度看,栈代表了处理逻辑,而堆代表了数据。 2、堆和栈的分离,使堆的内容可以被多个栈共享。 一方面这种共享提供了一种有效的数据交互方式(如:共享内存) 一方面,堆的共享常量和缓存可以被所有栈访问,节省了空间 3、栈因为运行时的需要,比如保存系统运行的上下文,需要进行地址段的划分 由于栈只能向上增长,因此会限制住栈存储内容的能力。 而堆的对象时根据需要动态增长的 因此栈和堆的拆分,使得动态增长成为可能,相应栈中只记录堆中的一个地址即可 4、体现了java面向对象的特点为什么要用线程池
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合, 然后在需要执行新的任务时重用这些线程而不是新建一个线程 线程池的好处 1、线程池改进了一个应用程序的响应时间。 由于线程池中的线程已经准备好且等待被分配任务,应用程序直接拿不用创建。 2、线程池节省了CLR为每个短生存周期任务创建一个完整的线程的开销并可以在任务完成后回收资源 3、线程池根据当前在系统中运行的进程来优化线程时间片。 4、线程池允许我们开启多个任务而不用为每个线程设置属性 5、线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用 6、线程池可以用来解决处理一个特定请求最大线程数量限制问题