文章目录
1.Synchroized
前提:八股看了一遍又一遍,每次看这个Synchroized都有点不同,这次把整体总结一下
- 用处:同步代码块、同步方法
- 对于非静态的一般上锁就是针对当前的对象实例;而对于静态的则针对的当前类的所有对象,因为对于类的信息我们是存在方法区的,JVM中只有一份。
- 对象:一个对象在存储中,包含了对象头、实例数据、对其数据。而对象头又包含了MarkWord、类指针(指向类的信息),而对于锁这一部分,其实主要是MarWord这一部分:分代年龄(GC)、hashCode(HashMap)、标记位(这一部分标记了无锁、偏向锁、轻量级锁、重量级锁)
- 简单说一下:无锁、偏向锁(只有一个线程)、轻量级锁(多个线程顺序执行)、重量级锁(抢锁),当然还有自旋锁,自适应自旋锁等。
所以对于锁来说,其实就是对象监视器来针对当前对象的对象头的标志位的一个抢占(对于类的话,应该是类对象吧,只有一个),主要以对象头标志抢占为主:
- 拥有对象监视器(锁)的线程
- 其他线程来的时候会进行cas抢锁,抢不到进到等待队列中(竞争队列、候选队列),因为可能同时有大量线程在等待队列中,所以我们将一部分线程拿到候选队列,作为下一次的锁的优先竞争者
- Ondesk,下一次获取锁的线程,在当前线程释放锁之后,会在候选队列的一个线程中唤醒一个指定到Ondesk,然后再开始竞争,这里的竞争主要是Ondesk线程和此时正在cas的线程,所以这是竞争切换,明显不公平
- 还有一个阻塞队列,是调用wait函数的线程,会把当前对象的锁释放,然后把当前线程放到阻塞队列中,只有其他线程进行notify唤醒,才可以重新进入等待队列的候选队列
注:
三种队列:等待队列(竞争队列+候选队列)、阻塞队列、运行的线程、下次竞争的线程
2. ReetrantLock
顺便把它讲了吧,这是JDK实现的,而上面的是内置的;并且ReentrantLock需要手动释放锁,出现异常可能会产生死锁,而Synchronized会自动释放;两者都是非公平锁+可重入锁,但是前者(lock)可以实现公平锁,并且可中断。
- 内部最重要的可以说是AQS-队列抽象同步器
- 继承它的内部类分别实现了公平锁、和非公平锁
- 思想:一个是state状态 = c、一个是线程队列
- 1.公平锁
- 刚开始不会直接CAS抢锁
- if c == 0
- 判断队列是否为空,为空则cas抢锁,c = 1;否则加入队列
- else 可重入(一般判断线程名是否相等)
- 加入队列
- 2.非公平锁
- 直接cas抢锁(明显不公平)
- if c == 0
- cas 抢锁成功 c ==1;失败加入队列
- else 可重入
- 加入队列
注:可以看到其实和synchroized一样都是刚开始进行cas抢锁,明显不公平。
3. 线程池
继续--------
为什么需要?
1.可以进行重用,来任务的时候快速的启动,避免线程的销毁
2.线程池统一管理、统一分配
参数:
核心线程数量、最大线程数量、存活时间、单位、阻塞队列、拒绝策略、线程工厂
常见的几种线程池:
- newFixedPoolThread(n,n,0,s,LinkedBlockingQuene)
- newSinglePoolThread(1,1,0,s,LinkedBlockedQueue)
- newCachePoolThread(0,Max_Value,60,s,SynchronousQuene)
- newSchedulePoolThread–定时执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); scheduledThreadPool.schedule(new Runnable() { @Override public void run() { System.out.println("delay 3 seconds"); } }, 3, TimeUnit.SECONDS);
工作原理:
对于新来的任务
- 创建核心线程
- 核心线程数量达到最大,进入队列
- 队列满了,创建非核心线程
- 都满了,则拒绝策略
- 时间到了,非核心线程死亡
拒绝策略:
- 直接丢弃
- 丢弃 + 并抛出异常
- 抛弃队列队首,加入当前线程
- 调用当前任务的线程代为执行
阻塞队列:
- LinkedBlockingQueue 无穷大
- SynchronousQueue 不存储任何线程
- 还有其他的就不一一列举了