分布式、多线程、锁
是什么 - 为什么 - 怎么用
什么是分布式 为了解决单个物理服务器容量和性能瓶颈问题而采用的优化手段。 水平扩展,垂直拆分。
什么是线程?线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位
线程和进程有什么区别? 线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。每个线程都拥有单独的栈内存用来存储本地数据。
如何在Java中实现线程 继承java.lang.Thread 类或者直接调用Runnable接口来重写run()方法实现线程,Callable和Future创建线程 util包下。
什么是线程池? 为什么要使用它?
创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。
为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。
join方法 把指定的线程加入到当前线程中
主线程启动子线程,如果子线程中要进行大量的耗时运算,主线程会早于子线程结束,这时候主线程如果想等待子线程完成之后再运行,就需要join()方法
多线程 与 JUC

JAVA 线程实现/创建方式
继承 Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。start()方法是一个 native 方法,它将启动一个新线程,并执行 run()方法。
1 | public class MyThread extends Thread { |
实现 Runnable 接口
1 | public class MyThread extends OtherClass implements Runnable { |
ExecutorService、 Callable
有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行
Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务
返回的 Object 了,再结合线程池接口 ExecutorService 就可以实现传说中有返回结果的多线程了。
1 | //创建一个线程池 |
juc汇总
- Executors:具有Runnable任务的执行者。
- ExecutorService:一个线程池管理者,其实现类有多种,我会介绍一部分,我们能把Runnable,Callable提交到池中让其调度。
- Semaphore:一个计数信号量。
- ReentrantLock:一个可重入的互斥锁定Lock,功能类似synchronized,但要强大的多。
- Future:是与Runnable,Callable进行交互的接口,比如一个线程执行结束后取返回的结果等,还提供了cancel终止线程。
- BlockingQueue:阻塞队列。
- CompletionService:ExecutorService的扩展,可以获得线程执行结果的。
- CountDownLatch:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
- CyclicBarrier:一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。
- Future:表示异步计算的结果。
- ScheduldExecutorService:一个ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
多线程操作(比如异步通知,或者一些操作的后续处理,为了不影响相应时间),我们是启动的时候new了队列,BlockingQueue LinkedBlockingQueue
1. 线程与进程
进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
区别
a,地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
b,资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资
c,线程是处理器调度的基本单位,但进程不是.
d,二者均可并发执行.
2.守护线程
在Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thread)。
守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
3.java thread状态
- NEW 状态是指线程刚创建, 尚未启动
- RUNNABLE 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等
- BLOCKED 这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 也就是这里是线程在等待进入临界区
- WAITING 这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束
- TIMED_WAITING 这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态
- TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)

4.请说出与线程同步以及线程调度相关的方法
- wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
5.进程调度算法
实时系统:FIFO(First Input First Output,先进先出算法),SJF(Shortest Job First,最短作业优先算法),SRTF(Shortest Remaining Time First,最短剩余时间优先算法)。
交互式系统:RR(Round Robin,时间片轮转算法),HPF(Highest Priority First,最高优先级算法),多级队列,最短进程优先,保证调度,彩票调度,公平分享调度。
时间片轮转法
6.wait()和sleep()的区别
- sleep来自Thread类,和wait来自Object类
- 调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
- sleep睡眠后不出让系统资源,wait让出系统资源其他线程可以占用CPU
- sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒
8.Synchronized 与Lock
ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
总体的结论先摆出来:
synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronized,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
9.线程池
线程池的作用:
在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。
常用线程池:ExecutorService 是主要的实现类,其中常用的有
Executors.newSingleThreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。
线程池的4个组成部分
线程池管理器:用于创建并管理线程池;
工作线程:线程池中的线程;
任务接口:每个任务必须实现的接口,用于工作线程调度其运行;
任务队列:用于存放待处理的任务,提供一种缓冲机制。
ThreadPoolExecutor
1 | public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue) { |
10.Executor拒绝策略
- AbortPolicy:为java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute需要try
catch,否则程序会直接退出. - DiscardPolicy:直接抛弃,任务不执行,空方法
- DiscardOldestPolicy:从队列里面抛弃head的一个任务,并再次execute 此task。
- CallerRunsPolicy:不停重复执行提交,直到插入(好凶残)
- 用户自定义拒绝策略:实现RejectedExecutionHandler,并自己定义策略模式
11.4种线程池 - CachedThreadPool 、 FixedThreadPool、SingleThreadPool(固定长度、缺少创建多余删除、延时执行、单例)
- newSingleThreadExecutor :创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务, 保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
适用场景:任务少 ,并且不需要并发执行 - newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.
线程没有任务要执行时,便处于空闲状态,处于空闲状态的线程并不会被立即销毁(会被缓存住),只有当空闲时间超出一段时间(默认为60s)后,线程池才会销毁该线程(相当于清除过时的缓存)。新任务到达后,线程池首先会让被缓存住的线程(空闲状态)去执行任务,如果没有可用线程(无空闲线程),便会创建新的线程。
适用场景:处理任务速度 > 提交任务速度,耗时少的任务(避免无限新增线程) - newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行
12.Java里的阻塞队列
7个阻塞队列
记:数组有界阻塞队列、支持优先级排序无界阻塞、支持优先级队列无界阻塞、不存数据阻塞、链表(有界、无界、双向)阻塞
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
按照先进先出(FIFO)原则排序默认情况下不保证访问者公平的访问队列,因为公平访问降低吞吐量。
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列(两个独立锁提高并发)。
基于链表的阻塞队列,按照先进先出(FIFO)原则排序,其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,能够处理并发数据。
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列(compareTo 排序实现优先)。
采取自然顺序升序排列,可自定义实现 compareTo()。
DelayQueue:一个使用优先级队列实现的无界阻塞队列。(缓存失效、定时任务)
支持延时获取元素的无界阻塞队列。队列使用 PriorityQueue实现
缓存系统的设计:可以用 DelayQueue 保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从 DelayQueue 中获取元素时,表示缓存有效期到了。
定时任务调度: 使 用 DelayQueue 保 存 当 天 将 会 执 行 的 任 务 和 执 行 时 间 , 一 旦 从DelayQueue 中获取到任务就开始执行,从比如TimerQueue 就是使用 DelayQueue 实现的。
SynchronousQueue:一个不存储元素的阻塞队列。(不存储数据、可用于传递数据)
是一个不存储元素的阻塞队列。每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。(传递器)
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。
多了 tryTransfer(试探下有没传入数据,没有就返回false,有没有数据都立即返回结果)、transfer 方法(去取数据,没数据就等,直到有数据才返回)
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
双向队列指的你可以从队列的两端插入和移出元素。
添加元素
Java中的阻塞队列接口BlockingQueue继承自Queue接口。BlockingQueue接口提供了3个添加元素方法。
add:添加元素到队列里,添加成功返回true,由于容量满了添加失败会抛出IllegalStateException异常
offer:添加元素到队列里,添加成功返回true,添加失败返回false
put:添加元素到队列里,如果容量满了会阻塞直到容量不满
删除方法
3个删除方法
poll:删除队列头部元素,如果队列为空,返回null。否则返回元素。
remove:基于对象找到对应的元素,并删除。删除成功返回true,否则返回false
take:删除队列头部元素,如果队列为空,一直阻塞到队列有元素并删除
13.DelayQueue
队列中每个元素都有个过期时间,并且队列是个优先级队列,当从队列获取元素时候,只有过期元素才会出队列。
14.原子操作类
在java.util.concurrent.atomic包下,可以分为四种类型的原子更新类:原子更新基本类型、原子更新数组类型、原子更新引用和原子更新属性。
原子更新基本类型
使用原子方式更新基本类型,共包括3个类:
AtomicBoolean:原子更新布尔变量
AtomicInteger:原子更新整型变量
AtomicLong:原子更新长整型变量
atomic实现原理:通过CAS(比较并替换 - 乐观锁)的方式实现的,代价是可能循环时间会太长
原子更新数组
通过原子更新数组里的某个元素,共有3个类:
AtomicIntegerArray:原子更新整型数组的某个元素
AtomicLongArray:原子更新长整型数组的某个元素
AtomicReferenceArray:原子更新引用类型数组的某个元素
AtomicIntegerArray常用的方法有:
int addAndSet(int i, int delta):以原子方式将输入值与数组中索引为i的元素相加
boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式更新数组中索引为i的值为update值
原子更新引用类型
AtomicReference:原子更新引用类型
AtomicReferenceFieldUpdater:原子更新引用类型里的字段
AtomicMarkableReference:原子更新带有标记位的引用类型。
原子更新字段类
如果需要原子更新某个类的某个字段,就需要用到原子更新字段类,可以使用以下几个类:
AtomicIntegerFieldUpdater:原子更新整型字段
AtomicLongFieldUpdater:原子更新长整型字段
AtomicStampedReference:原子更新带有版本号的引用类型。
要想原子更新字段,需要两个步骤:
每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段
更新类的字段(属性)必须为public volatile
15.死锁,以及解决死锁
死锁产生的四个必要条件
互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。
不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。
请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。
循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
解决死锁
一是死锁预防,就是不让上面的四个条件同时成立。
二是,合理分配资源。
三是使用银行家算法,如果该进程请求的资源操作系统剩余量可以满足,那么就分配。
16.进程间的通信方式
- 管道( pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
- 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
17.怎么解决 hash冲突
1.开放地址法 2.再哈希法 3.链地址法(hashmap) 4.建公共溢出区
juc - 详细名称 concurrent 用过那个,知道为什么这么用 LinkedBlockingQueue (几个子类的区别) 需要记得有哪些子类 - 各有什么区别 。
18.ThreadLocal
hreadLocal为每个线程维护一个本地变量。
采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。
ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。
4个方法:get/set/remove/ initialValue
可以用于解决SimpleDateFormat的线程安全
1 | public class SimpleDateFormatDemo { |
源码分析
ThreadLocalMap
ThreadLocalMap内部是利用Entry来进行key-value的存储的。
1 | static class Entry extends WeakReference<ThreadLocal> { |
上面源码中key就是ThreadLocal,value就是值,Entry继承WeakReference,所以Entry对应key的引用(ThreadLocal实例)是一个弱引用。
set(ThreadLocal key, Object value)
1 | /** |
这个set操作和集合Map解决散列冲突的方法不同,集合Map采用的是链地址法,这里采用的是开放定址法(线性探测)。set()方法中的replaceStaleEntry()和cleanSomeSlots(),这两个方法可以清除掉key ==null的实例,防止内存泄漏。
getEntry()
1 | private Entry getEntry(ThreadLocal key) { |
由于采用了开放定址法,当前key的散列值和元素在数组中的索引并不是一一对应的,首先取一个猜测数(key的散列值),如果所对应的key是我们要找的元素,那么直接返回,否则调用getEntryAfterMiss
1 | private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) { |
这里一直在探测寻找下一个元素,知道找的元素的key是我们要找的。这里当key==null时,调用expungeStaleEntry有利于GC的回收,用于防止内存泄漏。
ThreadLocal为什么会内存泄漏
ThreadLocalMap的key为ThreadLocal实例,他是一个弱引用,我们知道弱引用有利于GC的回收,当key == null时,GC就会回收这部分空间,但value不一定能被回收,因为他和Current Thread之间还存在一个强引用的关系。由于这个强引用的关系,会导致value无法回收,如果线程对象不消除这个强引用的关系,就可能会出现OOM。有些时候,我们调用ThreadLocalMap的remove()方法进行显式处理。
ThreadLocal总结
ThreadLocal不是用来解决共享变量的问题,也不是协调线程同步,他是为了方便各线程管理自己的状态而引用的一个机制。
每个ThreadLocal内部都有一个ThreadLocalMap,他保存的key是ThreadLocal的实例,他的值是当前线程的局部变量的副本的值。
19.Atomic - Integer
一个提供原子操作的 Integer 的类,同理的还有几个都在atomic包下。在多线程程序中,诸如++i 或 i++等运算不具有原子性,是不安全的线程操作之一。通常我们会使用 synchronized 将该操作变成一个原子操作,但 JVM 为此类操作特意提供了一些同步类,使得使用更方便,且使程序运行效率变得更高。
锁
锁-单机锁如何实现 - sys 的好像就是同步和互斥锁.
同步锁和互斥锁的区别 同步就是一起用,互斥就是谁拿到了就是谁的,其他人睡着.
锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。 锁可以升级,但不可降级。
偏向锁:目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护
阻塞锁:让线程到阻塞状态等待,得到通知醒来开始竞争.不占cpu资源,但是恢复慢.竞争激烈使用阻塞锁,不激烈使用自旋锁.
乐观锁与悲观锁(为什么? 优势? )
悲观锁 - 假设最差情况- 每次拿别人都在改- 所以每次拿数据都上锁,比如行锁,表锁,读锁,写锁.等 synchronized和ReentrantLock等独占锁就是悲观锁思想的实现
乐观锁(CAS)假设都是最好情况,别人不会改,所以不上锁,就判断这期间有没有人改数据,所以通过version判定,适用于多读情况 . 可以提高吞吐量.
乐观锁适用于写比较少的情况下(多读场景)但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
**乐观锁实现 : **1 version. 2 CAS算法 - 比较需要读写的值.与原先相同就修改,不同就不操作.一般情况下是自旋操作,即不断重试.
共享锁与独占锁:共享锁是一种乐观锁 ,独占锁是一种悲观锁
什么是可重入锁(ReentrantLock)?(递归锁)
可重入锁指的是在同一个线程中外层函数获取锁后,内层函数仍然有获取该锁的代码,却不受影响。在java中,ReentrantLock与 synchronized 都是可重入锁。(避免死锁)
https://blog.csdn.net/qq_36299025/article/details/89883989
公平锁与非公平锁
公平锁(Fair):加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
非公平锁(Nonfair):加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。
非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护一个队列;Java 中的 synchronized 是非公平锁, ReentrantLock 默认的 lock()方法采用的是非公平锁。
分拆锁(lock splitting)与分离锁(lock striping)
如果一个锁守护多个相互独立的状态变量,你可能能够通过拆分锁,使每个锁守护不同的变量,从而改进伸缩性.这样可以使锁的请求频率都变小.分拆锁对于中等竞争强度锁,能够转化为非竞争锁. 分拆锁的扩展,分为若干加锁块集合,并且他们属于相互独立对象,这个情况就是分离锁.如:ConcurrentHashMap就实现了16个锁数组,各自守护1/16. - (原文在深入分布式缓存中)
读写锁
1)多个读者可以同时进行读
2)写者必须互斥(只允许一个写者写,也不能读者、写者同时进行)
3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
Java中读写锁有个接口java.util.concurrent.locks.ReadWriteLock也有具体的实现ReentrantReadWriteLock。
锁优化
**减少锁持有时间 **:只用在有线程安全要求的程序上加锁
减小锁粒度:将大对象拆分为小对象,降低锁竞争,可以提升偏向锁、轻量锁的成功率。典型案例还是ConcurrentHashMap
锁分离:读写锁就是锁分离。 LinkedBlockingQueue 从头部取出,从尾部放数据。
锁粗化:在某些情况下,不停的请求、同步、释放,本身会消耗系统资源,不利于性能优化(与减小粒度相反)。
锁消除:编译器层面优化。详情
使用双锁分离就得注意一点,那就是防止线程夯死。生产线程要唤醒生产线程,消费线程也要唤醒生产线程,消费线程唤醒消费线程,消费线程也要唤醒生产线程。
分布式锁 保证分布式领域中共享数据安全问题
1、数据库实现(效率低,不推荐)
2、redis实现(使用redission实现,但是需要考虑思索,释放问题。繁琐一些)
3、Zookeeper实现 (使用临时节点,效率高,失效时间可以控制)
4、Spring Cloud 实现全局锁(内置的)
https://www.cnblogs.com/wangjintao-0623/p/9727272.html
线程同步方式
synchronized 同步代码块时,一个时间内只能有一个线程得到执行.其他线程需要等待(加法计数器用到 - 计数器+机器码 生成订单号)
同步锁(lock)
1 | //Lock是控制多个线程对共享资源进行访问的工具.通常,锁提供了对共享资源的独占访问,每次只能有一个,线程对Lock对象加锁,线程开始访问共享资源之前,应该先获得Lock对象。 |
ConcurrentHashMap并发
Lost Wake-Up 问题
多线程情况下,一个消费者线程,一个生产者线程。
生产者
1 | count+1; |
消费者
1 | while(count<=0) |
如果两个步骤同时执行

如何解决 - 通过同步的方式:只能有一个执行,就可以解决这个问题。(java有强制要求)