Java并发
进程:程序的一次执行,系统进行资源分配的最小单位。进程切换涉及新分配以及回收资源空间,耗费处理器时间较多
线程:CPU执行计算调度的基本单位,一个进程包含一个或多个线程,它们共享进程资源
进程状态
创建:向OS申请空白PCB,填入进程id、控制信息等
就绪:等待CPU调度即可执行
执行
阻塞:在某些事件发生前不能继续执行如I/O操作
死亡
(挂起:内存不足,就绪的或阻塞的进程放入磁盘,等待载入内存、载入内存和事件发生)
线程状态
引用 https://i-blog.csdnimg.cn/blog_migrate/96163b4bef1fbff0ac39755c0ca6e982.png、
JVM对线程状态的实现:
- Thread t=new Thread()创建线程
- t.start()就绪状态
- 执行run()方法体为执行状态
- (不确定)等待monitor lock的Blocked状态,等待notify()的waiting状态,Thread.sleep()(不释放锁)导致的waiting-timed状态对应阻塞
- run()方法体执行完对应线程销毁
多线程创建方式
1. 继承Thread类,重写run方法
public class MyThread extends Thread{
@Override
public void run(){
//实现
}
}
psvm(String[] args){
MyThread t1=new MyThread();
//
t1.start();
}
2. 实现Runnable接口,重写run方法(lambda表达式使得更方便new Thread(()->{System.out.print("Success!"}).start())
public class MyThread implements Runnable{
@Override
public void run(){
//实现
}
}
psvm(String[] args){
MyThread t1=new MyThread();
Thread t1=new Thread(t1);
//
t1.start();
}
线程中断涉及到interrupt()、interrupted()、isInterrupted()
1.interrupt()是给线程设置一个中断标志位,如果线程正处于休眠时调用interrupt()会抛出异常,同时会把中断标志位清除。interrupt()只是把线程的中断标志改成true,并不会对线程的状态进行改变。
2.interrupted()是静态方法,可以使用Thread直接调用,它用来判断线程是否含有中断标志位,同时它会把中断标志位清除。
3.isInterrupted()方法也可以用来判断线程是否含有中断标志位,它不会清除中断标记位
当收到了中断请求后,如何结束该线程呢?
return或更优雅的方式则是:抛出InterruptedException异常
线程池
接口:Executor基础接口-->ExecutorService接口(提供了线程池生命周期管理的方法)
—>AbstractExecutorService抽象类提供默认实现-->ThreadPoolExcutor类
重要参数:
- 核心线程数corepoolsize:如果运行的线程少于corepoolsize则创建新线程处理请求,即使有其它空闲线程
- 最大线程数maximumpoolsize:大于corepoolsize,小于maximumpoolsize,加入任务队列,仅当队列满时才创建新线程;超过max根据任务拒绝策略(报错、扔。。)
- 如果core和max相同则创建固定大小的线程池
- 阻塞队列大小
- 线程空闲时间:如果线程数大于core,空闲线程时间>this则销毁线程
常见实现:newfixedthreadpool固定大小
newcachedthreadpool根据新任务创建新线程
newsinglethreadexcutor单个线程
BlockingQueue:ArrayBlockingQueue(有界队列实现类,底层采用数组来实现。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。)、
SynchronousQueue(当一个线程往队列中写入一个元素时,写入操作不会立即返回,需要等待另一个线程来将这个元素拿走;同理,当一个读线程做读操作的时候,同样需要一个相匹配的写线程的写操作。这里的 Synchronous 指的就是读线程和写线程需要同步,一个读线程匹配一个写线程。不像ArrayBlockingQueue、LinkedBlockingDeque之类的阻塞队列依赖AQS实现并发操作,SynchronousQueue直接使用CAS实现线程的安全访问)、
DelayQueue()
ThreadLocal
创建hreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题。内部是ThreadLocalMap,键为线程对象,值为变量副本。
锁
jmm
每个线程拥有其工作内存,主内存中存在线程共享的数组数据、实例变量、静态变量等,线程读取主内存数据并在工作内存创建一个数据副本,对数据副本进行操作并在适当的时候写回内存。
volatile关键字
实现变量的可见性、禁止指令重排序。happens-before规则:对一个volatile变量的写,happens-before于任意后续对这个volatile变量的读。
synchronized
synchronized可用于代码块和方法(静态方法锁住类对象,实例方法锁住实例对象)
图来自 https://www.jianshu.com/p/d53bf830fa09
原理:Synchronized实现线程同步,关键是必须要对对象的监视器monitor进行获取,当线程获取monitor后才能继续往下执行,否则就只能等待。获取的过程互斥,同一时刻只有一个线程能够获取到monitor。同时每个对象有个计数器,进入代码块的线程执行monitorenter命令计数器加1,完毕执行monitorexit指令计数器减1,为0时真正释放锁,即为可重入。
释放锁的时候会将值刷新到主内存中,其他线程获取锁时会强制从主内存中获取最新的值,完成可见性。
优化:
对象头记录线程id,没有则cas替换,替换失败撤销偏向锁。参考上文图片获取链接讲解。
Lock锁接口
ReentrantLock
需手动释放锁(标准用法是try之前lock,finally中unlock)、ReentrantLock可以实现公平锁(加了一个判断,是否是同步队列中的第一个)
state变量----》CAS----》失败后进入等待队列----》释放锁后唤醒
ReentrantReadWriteLock
读写锁,读取数据时可以多个线程同时进入到临界区
写数据时无路是读数据还是写数据都是互斥的
AQS:Abstract Queue Synchronizer 抽象队列同步器
是什么?比如ReadWriteLock、ReentrantLock等常用的锁,都是通过内部类来实现该抽象,从而实现锁功能
核心变量:
1、state(volatile的):尝试用CAS方式去更新state=1
2、等待队列:线程2加锁失败,进入等待队列,挂起
独占式:需要子类实现的方法为tryAcquire、tryRelease;涉及方法有:
acquire:AQS实现的方法,用于阻塞式获取资源。
tryAcquire:子类需要实现的方法,用于获取资源的具体实现。
release:AQS实现方法,释放资源。
tryRelease:子类实现,释放资源的具体实现。
共享式:需要子类实现的方法为tryAcquireShared、tryReleaseShared;涉及方法有:
acquireShared:AQS实现的方法,用于阻塞式获取共享资源。
tryAcquireShared:子类需要实现的方法,用于获取共享资源的具体实现。
releaseShared:AQS实现方法,释放共享资源。
tryReleaseShared:子类实现,释放共享资源的具体实现。