Java 基础--线程 锁 线程池
Posted danfengw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 基础--线程 锁 线程池相关的知识,希望对你有一定的参考价值。
线程
1 概念
进程与线程的区别:
进程:是资源分配的最小单位
线程:是cpu调度的最小单位
进程是抢占处理机的调度单位,线程属于某个进程,共享其资源
线程只由堆栈寄存器 程序计数器 和 TCB组成
进程与线程的区别
线程不能看作独立应用,而进程可以看作独立应用
进程有独立的地址空间,相互不影响,而线程只是进程的不同执行路径
线程没有独立的地址空间,多进程的程序比多线程程序健壮
进程的切换比线程切换开销大
线程启动方式三种
1.继承Thread类,并复写run方法,创建该类对象,调用start方法开启线程。
2.实现Runnable接口,复写run方法,创建Thread类对象,将Runnable子类对象传递给Thread类对象。调用start方法开启线程。
3.创建FutureTask对象,创建Callable子类对象,复写call(相当于run)方法,将其传递给FutureTask对象(相当于一个Runnable)。
创建Thread类对象,将FutureTask对象传递给Thread对象。调用start方法开启线程。这种方式可以获得线程执行完之后的返回值。
线程中start 与 run 方法的区别
start() 会创建一个新的子线程并启动
run()只是Thread的一个普通方法的调用
Thread 与 Runnable 是什么关系
Thread 是实现了Runnable接口的类,使得run支持多线程
因为类的单一继承原则,推荐使用Runnable接口
如何给run方法传参数
实现方式有三种:
(1)构造函数传参
(2)成员变量传参
(3)回调函数传参
如何实现处理线程的返回值
实现方式主要有三种:
(1)主线程等待法(需要自己实现循环等待逻辑)
public class CycleWait implements Runnable
private String value;
public CycleWait()
@Override
public void run()
try
Thread.currentThread().sleep(5000);
catch (InterruptedException e)
e.printStackTrace();
value ="CycleWait run finish";
public static void main(String[] args)
CycleWait cycleWait=new CycleWait();
Thread thread=new Thread(cycleWait);
thread.start();
// 循环等待
while (cycleWait.value==null)
try
Thread.currentThread().sleep(5000);
catch (InterruptedException e)
e.printStackTrace();
// 打印返回
System.out.println("value=" +cycleWait.value);
(2)使用Thread的join()方法阻塞当前线程,等待子线程,处理完毕
// while (cycleWait.value==null)
// try
// Thread.currentThread().sleep(5000);
// catch (InterruptedException e)
// e.printStackTrace();
//
//
try
thread.join();
catch (InterruptedException e)
e.printStackTrace();
(3)通过Callable接口实现:通过FutureTask Or 线程池获取
FutureTask 方式:
public class MyCallable implements Callable<String>
@Override
public String call() throws Exception
String value="test";
System.out.println("Ready to work");
Thread.currentThread().sleep(5000);
System.out.println("task done");
return value;
public class FeatureTaskDemo
public static void main(String[] args)
FutureTask<String> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
if (!task.isDone())
System.out.println("task has not finished ,please wait");
try
System.out.println("task return =" + task.get());
catch (ExecutionException e)
e.printStackTrace();
catch (InterruptedException e)
e.printStackTrace();
线程池方式:
public class ThreadPoolDemo
public static void main(String[] args)
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
Future<String> task = newCachedThreadPool.submit(new MyCallable());
if (!task.isDone())
System.out.println("task has not finished ,please wait");
try
System.out.println("task return =" + task.get());
catch (ExecutionException e)
e.printStackTrace();
catch (InterruptedException e)
e.printStackTrace();
finally
newCachedThreadPool.shutdown();
线程的状态
六个状态:NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED
NEW:创建后还未启动的线程状态
RUNNABLE:包含Running 和Ready 两个状态
BLOCKED:等待获取排它锁 (等待锁资源的状态)
TERMINATED:已终止线程的状态
TIMED_WAITING(有限等待):
WAITING(无限等待):不会被分配CPU执行时间,需要显式被唤醒 (wait() 和 join())
引起无限等待可能有一下几种情况:
调用了---没有设置Timeout参数的Object.wait()方法
调用了---没有设置Timeout参数的Threas.join()方法
调用了---LockSupport.park()方法
TIMED_WAITING(有限等待):一段时间后会由系统自动唤醒
引起有限等待可能有一下几种情况:
调用了--Thread.sleep()方法
调用了---设置Timeout参数的Object.wait()方法
调用了---设置Timeout参数的Thread.join()方法
调用了---LockSupport.parkNanos()方法 或者 LockSupport.parkUntil()方法
sleep 与wait的区别
(1)sleep是Thread类中的方法 而wait 是object 类中的方法
(2)sleep 方法可以在任何地方使用 而 wait 方法只能在synchronized 方法或者 synchronied 块中使用
(3)Thread.sleep() 会让出cpu,但是不会导致锁行为发生变化 object.wait()不仅让出cpu 还会释放锁
notify 和 notifyAll 区别
概念理解
想知道notify 和 notifyAll 区别 需要先了解2个概念:锁池EntryList 和 等待池WaitSet
锁池:
等待池:
二者区别
notifyAll会让所有处于等待池的线程全部进入锁池区竞争获取锁的机会,
notify:只会随机选取一个等待池中的线程进入锁池区竞争获取锁的机会
yield 区别
作用:当调用Thread.yield()函数时候,会给线程调度器一个当前线程愿意让出cpu使用的暗示,但是线程调度器可以忽略这个暗示
对锁没有影响。
如何中断线程
已经被抛弃的方法:
(1)通过调用stop()方法停止线程
(2)通过调用suspend()和resume()方法
目前使用的方法:
调用interrupt(),通知线程应该中断了
(1)如果线程处于被阻塞的状态,那么线程将立即退出被阻塞的状态(wait join sleep),并跑出一个IntertuptedException 异常
(2)如果线程处在正常活动状态,那么会将该线程的中断标志设置为true,被设置中断标志的线程将继续正常运行,不受影响,因此interrupt并不一定能中断线程,需要被调用的线程配合中断,比如:
正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
三线程打印ABC
同步改异步、异步改同步的实现
锁
https://tech.meituan.com/2018/11/15/java-lock.html
https://xiaomi-info.github.io/2020/03/24/synchronized/
https://tech.meituan.com/2014/09/23/java-memory-reordering.html
线程安全问题的主要诱因
存在共享数据(也成临界资源)
存在多条线程共同操作这些共享数据
解决问题的根本方法:
同一个时刻有且只有一个线程在操作共享数据,其他线程必须等待该线程处理完数据后再对共享数据进行操作湖泊;
互斥锁
互斥性:在统一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制
可见性:确保锁在被释放之前,对共享变量所做的修改,对于随后获取该锁的线程是可见的。
Synchronized
概念相关
根据获取锁的分类: 对象锁 和 类锁
对象锁:
(1)同步代码块(synchronized(this) synchronized (类实例对象)),锁是小括号()中的实例对象
(2)同步非静态方法(synchronized method ),锁是当前对象的实例对象。
类锁
(1)同步代码块(sunchronized(类.class),锁是小括号()中类对象的class对象)
(2)同步静态方法 (synchronized static method),锁是当前对象的类对象(class 对象)
synchronized 锁的不是代码,锁的是对象,类锁可以看作是特殊的对象锁
Synchronized 底层实现原理
Synchronized的四种状态
无锁、偏向锁、轻量级锁、重量级锁
锁膨胀方向:无锁-》偏向锁-》轻量级锁-》重量级锁
偏向锁:
减少同一线程获取锁的代价,
出现原因:大多数情况下,锁不存在多线程竞争,总是由同一线程多次获得
核心思想:
如果一个线程获得了锁,那么锁就进入偏向模式,此时MarkWord 的机构就变成偏向锁结构,当该线程再次请求锁的时候,无需再做任何同步操作,即获取锁的过程只需要检查Mark Word的锁标记位为偏向锁,以及当前线程Id 等于 Mark Word 的ThreadID 即可,这样就省区了大量有关锁申请的操作
不适用于锁竞争比较激烈的多线程场合
轻量级锁:
轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当有第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。
适用场景: 线程交替执行同步块
若存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁
实现synchronized 的基础
java对象头
Monitor:每个java对象天生自带了一把看不见的锁,
对象在内存中的布局
对象头
实例数据
对齐填充
对象头部分包含两类信息。
1、第一类是自身运行时数据,如何哈希码(hashcode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID等,这部分数据官方称它为“Mark Word”。
2、第二类是类型指针,即对象指向它的类型元数据的指针,虚拟机通过它来确定对象是哪个类型的实例。
重量级锁也就是synchronized锁
synchronized锁,
(1)synchronized(this)在编译成.class 文件后 会以 monitorenter 和monitorexit 来标记锁的进入与退出
(2)sychronized method 编译后 是通过 ACC_SYNCHRONIZED 的标志位来标示的。
为什么会对synchronized 嗤之以鼻
原因是早起版本中(1)synchronized 属于重量级锁,效率低下,依赖于Mutex Lock 实现,线程之间的切换需要从用户态转换到核心态,开销较大
但是java6 以后,synchronized性能得到了很大的提升
什么是重入
从互斥锁的设计上来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源的时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入。
jvm 锁优化都有哪些
自旋锁(Adaptive Spinning)
锁消除(Lock Eliminate)
锁出化(Lock Coarsening)
轻量级锁(LightWeight Locking)
偏向锁(Biased Locking)
自旋锁与 自适应自旋锁
自旋锁
许多情况下,共享数据的锁状态持续时间较短,切换线程不值得,通过让线程执行忙循环,等待锁的释放,不让出cpu
缺点:若锁被其他线程长时间占用,会带来许多性能上的开销
自适应自旋锁
自旋的次数不再固定
由前一次在同一个锁上的自旋时间及锁拥有者的状态决定
锁消除
更彻底的优化:JIT 编译时。对运行上下文进行扫描,去除不可能存在竞争的锁
锁粗化
另一种极端:
(1)通过扩大加锁的范围,避免反复加锁、解锁
锁的内存语义
当线程释放锁时,java内存模型会把该线程对应的本地内存变量更新到主内存
当线程获取锁时,java内存模型会把该线程对应的本地内存变量置为无效,从而使得被监视器保护的临界区代码必须从主内存获取共享变量
CAS
cas是什么?如何解决ABA问题的?
Compare and Swap,即是比较并替换。
CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,需要替换的值B。
当需要更新一个变量的值的时候,只有当变量的预期值A和内存地址V中的实际值相同的时候,才会把内存地址V对应的值替换成B
CAS是乐观锁
通过版本号可以解决
Lock
synchronized和lock区别,重入锁
https://blog.csdn.net/hefenglian/article/details/82383569
ReentrantLock(再入锁)
与synchronized 功能类似
它与CountDownLatch、FutureTask、Semaphore 一样基于AQS实现
能够实现比synchronized更细粒度的控制,如控制fairness
调用lock()之后,必须调用unlock()释放锁
性能未必比syn高,并且也是可以重入的
设置公平性
synchronized是非公平锁
参数为true时,倾向于将锁赋予等待时间最久的线程,
公平锁:获取锁的顺序按先后调用lock方法的顺序(慎用)
非公平:抢占锁的时机不确定,看运气。
ReentrantLock reentrantLock=new ReentrantLock(true)
ReentrantLock与 Syn区别
volatile (轻量级同步机制)
1 概念
保证被volatile修饰的共享变量对所有线程总是可见的
禁止指令重排序
2 volatile 变量为何立即可见
通过加入内存屏障和禁止重排序来优化实现
的。
(1)对 volatile 变量执行写操作时,会在写操作后加入一条 store 屏障指
令;
(2)对 volatile 变量执行读操作时,会在读操作前加入一条 load 屏障指 令。
volatile 如何禁止重排优化
通过插入内存屏障指令禁止在内存屏障前后的指令执行重排序优化,强制刷新各种cpu 的缓存数据,因此任何cpu上线程都能读取到这些数据的最新版本。
内存屏障:1 保证特定的执行顺序 2 保证某些变量的内存可见性
volatile 与synchronized 的区别
(1)volatile 本质是告诉JVM 当前变量在工作内存中值是不确定的,需要从主内存读取,
synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞,知道该线程变量操作完成
(2)volatile 仅可以修饰 变量, synchronized 可以修饰 类、 变量、方法
(3)volatile 仅能实现变量修改的可见性,不能保证原子性,而synchronized 则保证可见性 和原子性
(4)volatile 不会造成线程阻塞,synchronized 可能造成线程阻塞
(5)volatile 标记的变量不会被编译器优化,synchronized 标记的变量可以被编译器优化
CAS(Compare and Swap)
一种高效实现线程安全性的方法
(1)支持原子更更操作,适用于计数器,序列发生器等场景
(2)属于乐观锁机制,号称lock-free
(3)cas操作失败时由操作者决定是继续尝试,还是执行别的操作
CAS 思想
包含三个操作数----内存位置(V)、预期原值(A) 新值(B)
将内存位置的值与预期原值进行比较,如果匹配,那么处理器就将该位置的值更新为新值。否则,不操作。
缺点:
若循环时间长,则开销很大
只能保证一个共享变量的原子操作
ABA 问题 解决:AtomicStampedRefrence
死锁形成条件
互斥条件,不可剥夺条件、请求与保持条件、循环等待条件
线程池
为什么要适用线程池
降低资源小,提高线程的可管理性
线程池的状态
SHUTDOWN :调用shutDown()方法进入该状态
STOP:调用了shutDownNow()方法后进入该状态
线程池handler的抛弃策略
AbortPolicy:直接抛出异常,这是默认策略
DiscardPolicy:直接抛弃
DiscardOldestPolicy:丢弃队列最靠前的任务,并执行当前任务
CallerRunsPolicy:用调用者所在的线程执行任务。
也可以通过实现RejectedExecutionHandler 接口,自定义handler
线程池的大小如何选定
CPU密集型: 线程数=按照核数或者核数+1设定
I/O密集型: 线程数=CPU核数*(1+平均等待时间/平均工作时间)
线程池五种
https://blog.csdn.net/u010690828/article/details/77170533
keepAliveTime含义
keepAliveTime指非核心线程空闲时间达到的阈值会被回收。
Lock 锁原理
AQS +CAS ()
ReetrantLock
悲观锁与乐观锁
可重入: +1
公平与非公平:fair nonFairSync: 维持了线程的队列,取队列中第一个,非公平锁
读写锁:
共享锁与排他锁:
缓存队列 与 cas 操作
LockSupport.park()
怎么解决的aba的问题,要么抛出异常,要么等待成功
LockCopyWrite 用的是2把锁,读锁与写锁,适用场景读多写少
线程阻塞
CAS 自旋转锁
cas:原子性
volatile
其他
interrupt 为什么抛出异常
守护线程:
spleep(0)
lock syn automic 相关的
lock syn 什么时候可以中断
Runnable Callable Future FutureTask cancel canceled
okhttp的线程池
线程池的状态的状态是跟线程池相关的,跟线程池中线程的状态无关
以上是关于Java 基础--线程 锁 线程池的主要内容,如果未能解决你的问题,请参考以下文章
java并发编程:管程内存模型无锁并发线程池AQS原理与锁线程安全集合类并发设计模式