(1)进程:当一个程序运行,从磁盘加载程序代码至内存,这时就开启了一个进程
(2)线程:线程就是一个指令流,将指令流中的一条条指令以必定的顺序交给CPU执行
(3)并发:(1)从业务上简单理解就是多个用户共同竞争一个资源(2)从操做系统层面同一时刻只能有一条指令执行,但多个线程指令被快速的轮换执行。
(4)并行:同一时刻多条指令在多个处理器上同时执行
(5)同步:须要等待结果返回才能继续执行
(6)异步:不须要等待结果返回就能继续执行java
(1)方法一:继承thread类,实现run方法(Thread)api
Thread t = new Thread(){ public void run(){ //执行的任务 } }; t.start();
(2)方法二:实现Runnable接口,实现run方法(Runnable配合Thread)数组
Runnable task = new Runnable(){ @Overried public void run(){ //要执行的任务 } } Thread t = new Thread(task); t.start();
(3)方法三:实现Callable接口,用来处理有返回结果的状况缓存
FutureTask<String> task = new FutureTask<String>(new Callable<String>(){ public String call() { return searcher.search(target); } }); new Thread(task).start(); //获得结果 Integer result = task.get();
ps -ef 查看全部进程
jps 查看java的进程
kill 进程id 杀死进程
top 动态查看进程
top -h -p 进程id 查看线程信息
jstack 进程id 查看某一时刻进程中全部的线程信息(更详细,包括线程状态)安全
方法名 | 功能说明 |
---|---|
start() | 让线程进入就绪状态 |
join() | 等待线程结束(线程B中调用A的join方法,等待A执行完毕才继续执行B) |
interrupt() | 打断线程 |
sleep(long n) | 当前线程休眠n毫秒,让出cpu时间片 |
yield() | 让出当前线程对cpu使用 |
默认状况下Java进程须要等待全部线程都运行结束才会结束
但守护线程,其它非守护线程运行结束,即便守护线程正在运行,也会结束性能优化
thread.setDaemon(true)
(1)新建:线程对象建立后进入新建状态(Thread t = new MyThread();)
(2)就绪:当调用对象的start方法,线程进入就绪状态,说明此线程已准备好,随时等待cpu调度执行
(3)运行:cpu调度该线程,进入运行状态
(4)阻塞:处于运行状态的线程因为某种缘由暂时放弃cpu使用权,中止执行,此时进入阻塞状态
(阻塞分类:①等待阻塞:运行状态的线程执行了wait方法②同步阻塞:线程获取sychronized同步锁失败③其它阻塞:调用线程的sleep或join或发出I/O请求时)
(5)消亡:线程执行完或因异常退出了run方法,该线程结束生命周期并发
对共享资源进行读写操做的代码块称为临界区框架
synchronized其实是用对象锁保证了临界区代码的原子性异步
①synchronized修饰普通同步方法,此时锁的是当前实例的对象
②synchronized修饰静态同步方法,此时锁的是类的class对象
③synchronized修饰同步代码块,此时锁的是括号内的对象函数
1.对象头
以32位虚拟机为例
2.monitor
monitor被翻译为监视器或管程,每一个java对象均可以关联一个monitor对象,若是使用synchronized给对象上锁后,该对象头的Mark word就被设置为指向monitor对象的指针。
monitor中有owner waitset entrylist
(1)当monitor中的owner为null
(2)当一个线程执行synchronized,会将monitor中全部者设置为当前线程,monitor只能由一个owner
(3)这时若是另外一个线程也执行synchronized获取当前锁对象,就会进入entrylist阻塞
(4)若是线程执行过程当中调用wait方法会进入waitset
1.偏向锁
偏向锁会偏向于第一个占有锁的线程。若是没有竞争,已经得到偏向锁的线程,在未来进入同步块时不会进行同步操做。
(1)markword中偏向锁标识设置为1,锁标志位为01
(2)若是为可偏向状态,则检查线程ID是否指向当前线程,若是是,执行同步代码块
(3)若是不是,则经过CAS操做竞争锁。若是竞争成功,则将Mark Word中线程ID设置为当前线程。竞争失败,则说明发生竞争,得到偏向锁的线程被挂起(撤销偏向锁,会致使stop the world),偏向锁升级为轻量级锁
2.轻量级锁和自旋锁
(1)虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝
(2)尝试用cas替换锁对象的markword,将markword存入锁记录 对象指针指向锁对象,并尝试用cas替换锁对象的markword,将markword存入锁记录
若是cas成功,对象头存储了锁记录地址和锁状态00,表示由该线程给对象加锁
若是cas失败,当前线程尝试使用自旋来获取锁。若是自旋获取锁成功,则依然处于轻量级锁状态,不然.有两种状况:
①若是其它线程已经持有了该对象的轻量级锁,代表有竞争,进入锁膨胀状态 ②若是是本身进行锁重入,那么再添加一条锁记录做为重入的计数
3.锁膨胀(重量级锁)
(1)为锁对象申请monitor锁,让锁对象指向重量级锁地址
(2)而后进入monitor的entrylist阻塞
(3)当轻量级锁解锁时,会失败。这时根据monitor地址找到monitor对象,唤醒entrylist中阻塞的线程
调用wait方法便可进入waitset变为WATTING状态
(1)api介绍
obj.wait()让进入object监视器的线程到waitset等待
obj.notify() 在waitset等待的线程挑一个唤醒
obj.notifyall() 唤醒waitset中全部的线程
(2)sleep和wait的区别
①sleep是Thread方法,而wait是object的方法
②sleep不须要和synchronized配合使用,wait须要和synchronized一块儿使用
③sleep在睡眠的同时不会释放对象的锁,但wait在等待的时候会释放锁
(3)交替输出(ABCABCABC)
class Syncprint { private int flag; private int loopNumber; public Syncprint(int flag, int loopNumber) { this.flag = flag; this.loopNumber = loopNumber; } private void print(String str, int waitflag, int nextflag){ for (int i = 0; i < loopNumber; i++){ synchronized (this){ while (this.flag != waitflag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print(str); flag = nextflag; this.notifyAll(); } } } } public class Test{ public static void main(String[] args) { Syncprint sync= new Syncprint(1, 5); new Thread(() -> { sync.print("a", 1, 2); }).start(); new Thread(() -> { sync.print("b", 2, 3); }).start(); new Thread(() -> { sync.print("c", 3, 1); }).start(); } }
LockSupport.park(); //暂停当前线程
LockSupport.unpark(t1);//开启t1线程
(1)顺序打印
Thread t1 = new Thread(() -> { LockSupport.park(); log.debug(""); },"t1"); Thread t2 = new Thread(() -> { log.debug("2"); LockSupport.unpark(t1); },"t2");
t1线程获取A对象锁,接下来想获取B对象的锁
t2线程获取B对象锁,接下来想获取A对象的锁
Object A = new Object(); Object B = new Object(); Thread t1 = new Thread(() -> { synchronized(A){ sleep(1); synchronized(B){ } } },"t1"); Thread t2 = new Thread(() -> { synchronized(B){ sleep(1); synchronized(A){ } } },"t2"); ti.start(); t2.start();
1.互斥条件
A线程得到资源, 其余线程不能再获取
2.不可剥夺
A线程未释放的资源其它线程不能强行夺走
3.请求和保持
请求其它资源,被其它线程占有.而本身保持的资源不释放
4.循环等待
存在一种线程资源的循环等待链。链中每一个线程得到的资源同时被链中下一个线程请求
1.加锁顺序
当多个线程须要相同的一些锁,按顺序加锁
2.加锁时限
线程尝试获取锁的时候加上必定的时限,超过期限则放弃对该锁的请求,并释放本身占有的锁
3.死锁检测
当一个线程请求锁失败时。这个线程能够遍历锁的关系图看看是否有死锁发生(例如,线程A请求锁7,可是锁7这个时候被线程B持有,这时线程A就能够检查一下线程B是否已经请求了线程A当前所持有的锁。若是线程B确实有这样的请求,那么就是发生了死锁)
(1)原子性:一个或多个操做,要么所有执行,要么所有不执行。
(2)可见性:一个线程对主内存的修改可及时被其它线程观察到
(3)有序性:一个线程中指令的执行顺序(因为指令重排序,顺序通常杂乱)
volatile的底层实现原理是内存屏障
对volatile的读指令前会加入读屏障
对volatile变量的写指令后会加入写屏障
(1)保证可见性
读屏障保证在该屏障后,对共享变量的读取,加载的是主内存中最新数据
写屏障保证在该屏障前,对共享变量的改动,都同步到主存当中
(2)有序性
读屏障保证指令重排序不会将屏障后的代码排在屏障前
写屏障保证指令重排序不会将屏障前的代码排在屏障后
CAS有3个操做数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。
volatile修饰变量,能够保证其可见性
AutomicBollean
AutomicInteger
AutomicLong
AutomicIntefer i = new AutomicInteger(0); i.incrementAndGet(); //++i i.getandincrement();//i++ i.decrementAndGet();//--i i.getAndAdd(5);//增长5
AutomicRefence
AutomicMarkableRefence
AutomicStampedRefence
AutomicRefence<BigDecimal> account;
(1).ABA问题
主存中的值被由A改成B再改成A,本地内存再cas时会认为没有变过,故更新成功
new AtomicStampedRefence<String> ref = AtomicStampedRefence<>("A",0);
(2).有时候并不关心变量被改了几回,而是有没有被改过。就有了AutomicMarkedRefence
new AtomicMarkableRefence<>(bag, true)
AtomicIntegerArray
AtomicLongArray
AtomicRefenceArray
针对某个域进行原子操做(好比引用的属性,这里的属性定义时必须用volatile修饰)
AutomicRefenceFieldUpdater(什么字段均可以)
AutomicIntegerUpdater(整型字段)
AutomicLongUpdater(long字段)
AutomicRefenceFieldUpdater updater = AutomicRefenceFieldUpdater.newUpdater(student.class, String.class, "name"); updater.compareAndSet(stu,null,"张三")
IntegerAdder
LongAdder
提供比原子整数更快的累加效果
原理:
有竞争时,设置多个累加单元,最后将各个累加单元再汇总,减小cas失败的次数
new LongAdder(); adder.increment();
ThreadPoolExecutor使用int的高3位来表示线程池状态,低29位表示线程数量
目的是将线程状态和线程个数合二为一,这样能够用一次cas原子操做进行赋值
①corePoolSize:核心线程池的大小,若是核心线程池有空闲位置,新的任务就会被核心线程池新建一个线程执行,执行完毕后不会销毁线程,线程会进入缓存队列等待再次被运行。
④workQueue:缓存队列,用来存放等待被执行的任务。
②maximunPoolSize:线程池能建立最大的线程数量。若是核心线程池和缓存队列都已经满了,新的任务进来就会建立救急线程来执行。可是数量不能超过maximunPoolSize,否侧会采起拒绝接受任务策略,咱们下面会具体分析。
③keepAliveTime:救急线程线程可以空闲的最长时间,超过期间,线程终止。这个参数默认只有在线程数量超过核心线程池大小时才会起做用。只要线程数量不超过核心线程大小,就不会起做用。
⑤threadFactory:线程工厂,用来建立线程
⑥handler:拒绝策略
abortPolicy:抛出异常(默认)
discardPolicy:放弃本次任务
discardoldestPolicy:放弃队列中最先的任务,本任务取代
callerrunPolicy:让调用者运行任务
(1)newFixedThreadPool
核心线程数=最大线程数(没有救急线程,所以也无需超时时间)
阻塞队列是无界的,能够听任意数量任务
(2)newCachedThreadPool
核心线程数为0,最大线程数是Integer.MAX_VALUE,救急线程存货时间60s
队列采用SynchronousQueue。它没有容量,没有线程取是放不进去的
(3)newSingleThreadExecutor
线程固定为1,其它任务来时放进无界队列等待
//执行任务 void execute(Runnable command); //提交任务(有返回值) submit(Callable task); //提交任务队列全部任务 invokeAll() //提交任务队列全部任务,哪一个先执行完毕,返回结果,其它任务取消 invokeAny();
若是当前线程池中正在执行的线程数目小于corePoolSize,则每来一个任务,就会建立一个线程去执行这个任务;
若是当前线程池中正在执行任务的的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(通常来讲是任务缓存队列已满),则会尝试建立新的线程去执行这个任务; ;
若是线程池中的线程数量大于 corePoolSize时,若是某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;
若是当前线程池中的线程数目达到maximumPoolSize,则会采起任务拒绝策略进行处理
abstractQueuedSynchronizer,是阻塞式锁和相关同步工具的框架
1.特色:
(1)用state表示资源状态(独占和共享),子类须要定义如何维护这个状态,控制如何获取和释放锁
(2)提供基于fifo的等待队列,相似与monitor的entrylist
(3)条件变量来实现等待唤醒,支持多种条件变量,相似monitor的waitset
jdk1.8加入,是为了进一步优化读性能,它的特色是在使用读锁,写锁时都必须配合戳使用
怎样实现性能优化?stampedlock支持乐观读取完毕后进行一次戳校验,代表这段时间没有写操做,能够安全使用,若是没有经过,才从新获取读锁保证数据安全。
信号量,用来限制同时访问共享资源的线程上限
用来进行线程同步协做,等待全部线程完成倒计时
构造函数初始化等待计数值,await()等待计数归零,countDown()计数减1
1.concurrent的弱一致性
(1)遍历时的弱一致性:当利用迭代器遍历时,若是容器发生修改,迭代器仍然能够进行遍历,这时内容是旧的
(2)求大小弱一致性:size操做未必是100%准确
2.hashmap为何线程不安全? (1).在JDK1.7中,当并发执行扩容操做时会形成环形链和数据丢失的状况。 (2).在JDK1.8中,在并发执行put操做时会发生数据覆盖的状况。