1创建线程方式
继承Thread 实现Runnable 使用 FutureTask Callable 使用线程池ThreadPoolExecutor
2查看进程和线程
linux : ps-fe 查看所有进程 ps-fT -p <.pid> 查看进程pid所有线程 top -H -p 进程id 查看进程中的线程
java命令 jps 查看java进程 jstack 进程id 的所有线程状态 jconsole图形化
3线程运行原理
每个线程启动虚拟机就会分配一块栈内存,每个栈由栈帧组成,对应着每次方法调用占用内存,有一个活动栈帧,对应着当前正在执行的方法
main线程栈 程序计数器 锁记录 操作数栈 局部变量表 返回地址
4上下文切换
线程cpu时间片用完,垃圾回收,有更优先级线程运行,线程自己调用sleep yield wait join等方法
5常见方法
start() 启动一个新线程 让线程进入就绪状态如果在构造Thread对象传入Runnale 线程启动后会调用Runnable的中的run方法 只能调用一次 调用多次会报错
run()新线程启动后调用 join()等待线程运行结束
isInterrupted判断是否打断(不会清除打断标记) isAlive()是否存活
interrupt 打断线程 (如果打断线程正在sleep wait join 会导致被打断的线程抛出异常,并清除打断标记,如果打断正在运行的线程会设置打断标记,park的线程被打断 也会设置打断标记)
interruptd() 判断当前线程是否打断会清除打断标记 currenThread() 获取当前线程 yield()让出当前线程对cpu的使用
6 sleep方法
调用sleep 当前线程从running进入TimedWaiting,其他线程使用interrup方法打断正在睡眠线程,这时sleep方法抛出InterruptedException 睡眠结束后线程未必立刻得到执行
7yiely 当前线程从running进入runnable就绪 具体实现依赖操作系统
8 两阶段终止模式
在一个线程下如何优雅终止另一个线程
9守护线程
只要有其他非守护线程运行结束,即使守护线程的代码没有执行完也会结束
Thead.setDeamon(true) 应用垃圾回收线程就是守护线程
10线程状态
操作系统 五种 初始状态 ->(可运行状态->运行状态)阻塞状态->终止状态
javaAPI Thread的state枚举六中状态
public enum State {
NEW,//线程被创建没有调用start()方法
RUNNABLE,//调用了start()方法(运行,阻塞可运行)
BLOCKED,//等待拿到锁
waiting ,//无限时等待
Timed waiting ,有时限等待
TERMINATED;//结束
}
11共享模型
临界区:一段代码块内如果存折对共享资源的多线程读写操作,称为这段代码块为临界区
静态条件:多个线程临界区内执行,由于代码的执行序列不同而导致结果无法预测,称为发生静态条件
解决方案:应用值互斥,为了避免临界区竞态条件发生 .阻塞式方案 synchronize lock 非阻塞式 原子变量
12synchronized简单分析
synchronized中对象可以想象为一个房间只有一个入口,这时有两个线程T1T2,假如T1拿到锁之后,T2也运行到了syn,这是T2阻塞,发生了上下文切换,这中间及时T1cpu时间片用完并且没有执行完发生上下文切换,T1任然拿着锁 T2阻塞,当T1执行完释放锁,唤醒其他等待线程竞争.
syn加锁同一个对象保证了临界区内代码原子性 可以加在对象 成员方法 静态方法上
13 成员变量和静态变量是否是线程安全的
1没有共享线程安全 2共享 根据状态是否改变,只有读操作 ,安全, 有读有写 这代码是临界区需要考虑安全
局部变量是否安全
局部变量是线程安全的 但是局部变量的引用对象未必 ,如果对象没有逃离方法的作用返回时安全的 ,反之要考虑线程安全
14 常见的线程安全类
string integer stringBuffer random vector hashTable java.util.concurrent包下的类
它们每个方法是原子的 ,但是多个方法组合在一起不安全
15 Monitor
java对象头以32位虚拟机为例
普通对象
mark word(32bits) ->hashcode 25的hash码 |age:4 分代年龄|biased_lock:0 是否是偏向锁|01加锁状态
klass word(32bits)
数组对象
mark word(32bits) klass word(32bits) arraylength(32)
工作原理
monitor(锁)监视器同一个对象同一个monitor
当线程T1执行到加锁代码的时候,java锁对象会和操作系统的monitor对象通过指针关联,T1拿到owner, 又一个线程过来,这时T1线程没有执行完,T2也会和moniter关联,进入entryList等待队列
monitor
waitset ------waiting thread3
entryList ----thread0 thread2 (blocked阻塞)
owner
↑ thread
markword(boj)
刚开始monitor中owner为null 当thread1执行synchronize(obj)就会将monitor的所有者owner设置为thread1, monitor中只有一个owner ,在thread1上锁过程中如果thread0 thread2也来执行syn(obj),就会进入entrylist中blocked ,thread1执行完同步代码块,然后唤醒entrylist等待线程竞争锁非公平锁.waitset中thread3是之前获得过锁,但条件不满足进入waiting状态的线程,syn必须是同一个对象的monitor才有上面的效果.
16 synchronized优化原理
16.1轻量级锁:如果一个对象虽然有多线程访问,但是多线程访问的时间是错开的(也就是没有竞争)
那么可以使用轻量级来优化
加锁
1创建锁记录对象每个线程的栈针包含一个锁记录,内部可以放锁定对象的markword
2让锁记录中object reference指向锁对象,尝试用cas替换object的markword.将warkword值存入锁记录
3如果cas替换成功,对象头中存了锁记录地址和状态00,表示由该线程给对象加锁
如果cas失败,有两种情况, 1如果其他线程已经持有了该obj的轻量级锁,这时表示有竞争,进图锁膨胀过程, 2如果是自己执行了syn(obj) 锁重入,那么再添加一条lockrecord作为重入的计数
解锁
当退出syn代码块 ,如果有取值为null的锁记录,表示有重入,这时重置锁记录,表示重入计数减一,
当退出syn代码块锁记录不为null,这时使用cas将markword的值恢复给对象头. 成功则解锁成功
失败说明轻量级锁进行了锁膨胀或者升级为重量级锁,进入重量级锁解锁流程
16.2 锁膨胀
如果在尝试加轻量级锁的过程中,cas操作无法成功,这时一种情况就是有其他线程为这个对象加上了轻量级锁(有竞争),这时要进行锁膨胀将轻量级锁变为重量级锁,
1当thread1进行轻量级加锁时,thread0已经对该对象加上了轻量级锁
2thread1加轻量级锁失败进入锁膨胀流程,为obj对象申请monitor锁让obj对象指向重量级锁地址,然后自己进入monitor的entrylist bloacked阻塞
3当thread0退出同步块解锁时,使用cas将markword的恢复给对象头,失败进入重量级解锁,按monitor地址找monitor对象设置owner为null,唤醒entrylist中blocked线程
16.3 自旋锁 占用cpu
重量级锁竞争时,还可以使用自旋锁进行优化,如果当前线程自旋成功(就是这个时候持有锁线程退出了同步块释放了锁),这样当前线程可以避免阻塞,
重量级锁hashcode在monitor对象 轻量级锁hashcode在线程栈针的锁记录中,
16.4偏向锁 一个线程使用
轻量级锁在没有竞争时(就自己这个线程) 每次重入仍然需要执行cas操作,java6引入偏向锁来优化,只有第一次使用cas将线程id设置到对象markword头之后发现这个线程id是自己的就表示没有竞争,不用重新cas.
一个对象创建时,如果开启了偏向锁(默认开启) markword值为0x05就是最后3位是101,这时thread epoch age 是0,偏向锁默认是延迟 ,避免 延迟,加jvm参数 -XX:BiasedLocking StartupDelay=0,如果没有开启 对象创建后markword后三位是001,这是它的hashcode age为0,第一次用到hashcode才会赋值,禁用偏向锁 ,-XX:-UseBiasedLocking
偏向锁不能调用hashcode 如果调用即使是偏向锁也会改成正常状态无锁,因为偏向锁没有地方存hashcode
偏向锁 --轻量级锁 - 自旋锁 - 重量级锁
16.5偏向锁 撤销
撤销 调用对象hashcode ,但偏向锁的对象maekword中存储的线程id如果调用hashcode会导致偏向锁被撤销, 轻量级锁会在锁记录中记录hashcode,重量级 monitor
不同线程加同一个对象锁,T1线程加锁后释放锁,T1是偏向锁 ,T2再去加偏向锁失效
批量重偏向
如果对象多个线程访问,但是没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象threadid 当撤销偏向锁阈值超过20次后jvm会觉得是不是偏向错了,于是会在给这些对象加锁时重新偏向到加锁线程,
批量撤销
当撤销偏向锁阈值超过10次,jvm会将整个类的对象编程不可偏向的,
17wait/notiify
属于object对象方法,必须获取锁才能调用
owner线程发现条件不满足 ,调用wait方法,即可进入waitset 变为watiing状态,blocked和waiting线程都处于阻塞状态,blocked会在owner线程释放锁唤醒,waiting 会在owner线程调用notify时唤醒,单唤醒后并不是立刻获取锁,需要进入entrylist冲洗竞争,
obj.wait()让进入obj监视器的线程到waitset中等待,obj.notify在obj上正在waitset等待的线程中挑一个唤醒.
sleep 是thread方法 wait是obj方法.sleep不需要强制和syn配合使用,但wait需要和syn一起用, sleep在睡眠的时候不会释放锁,wait在等待的时候会释放对象锁,
18保护性暂停 一一对应模式 产生结果线程和使用线程结果
用一个线程等待另一个线程的执行结果. 有一个结果从一个线程传入到另一个线程,让他们关联同一个guardedObject,如果有结果不断从一个线程到另一个线程,那么可以用消息队列, jdk的join实现 future的实现采用的就是保护性暂停.
t1--waitset有值--guardedObject --任务t2 完成赋值通知t1
19