目录
因为一般ThreadLocal对象都是声明在静态变量,使用gc时不会被回收,也就不会key为null,也就不会触发a) b).因此使用完手动remove
map是线程持有的,生命周期随线程 ,如果线程没有remove,entry就会在。
0 synchronized和Reentrantlock的区别
JMM(java内存模型)
java内存模型是一套在多线程读写共享数据时,对共享数据的可见性,有序性,原子性的规则和保障。
并发编程的三个问题
可见性
原子性
有序性
创建线程的方式:
1,继承Thread
t = new Thread(){
public void run(){//匿名内部类,而不用自己写子类继承Thread类
}
}
t.start()//就绪状态,具体执行由os调度器决定什么时候执行
2 Runnable()
实现runnable接口重写run方法,
r = new Runnable(){//任务对象
public void run(){
}
}
new Thread(r).start();
1,2方法的区别,两者都是调用了Thread的run方法
3,Callable()
//执行Callable 方式,需要FutureTask 实现,用于接收运算结果
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
new Thread(futureTask).start();
//接收线程运算后的结果
try {
Integer sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
Thread.wait()方法会占用锁 不会释放 但是会让出cpu使用权
join()方法会等 线程执行完毕,join的底层就是wait
interrupt()方法是打断线程,不管是正常运行的线程还是正在sleep,wait,join的线程
守护线程
:正常情况下,只要有线程还在运行,进程就不会停止,但是设置线程为守护线程之后 ,其他非守护线程都停止了,该守护线程也就停止了。守护线程守护的是进程。
gc线程就是守护线程。
线程安全问题:
一 synchronized(悲观锁)
对象头:
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
下面是对象头里的Mark Word
轻量级锁:如果一个对象,多个线程访问,但是多线程访问时间都是错开的,没有竞争,那么就使用轻量级锁来优化。
1.让锁记录中Object reference指向锁对象,并尝试用cas替换Object的mark word,将markword的值存入锁记录。2 如果cas替换成功,对象头中存储的是锁记录地址和状态00 表示由该线程给对象加锁。3 如果cas失败,有两种情况: 如果是其他线程已经持有该Object的轻量级锁,这表明有竞争,进入锁膨胀过程。如果是自己执行了重入,那么再添加一条Lock Record作为重入的计数。
锁膨胀:如果在尝试加轻量级锁的过程中,cas失败,进入锁膨胀,将轻量级锁变为重量级锁。
即为Object申请monitor锁,让object指向重量级锁。然后自己进入monitor的EntryList blocked
当之前占用轻量级锁的线程退出解锁时,使用cas恢复对象头失败,这时会进入重量级锁的解锁流程,按照monitor地址找到monitor对象,设置owner为null 唤醒EntryList中的blocked线程。
默认开启偏向锁,那么对象创建后,markword最后三位是101,偏向锁默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加vm参数-xx:BiasedLockingStartupDelay=0来禁止延迟。
公平锁,非公平锁
轻量级锁
偏向锁
锁膨胀
wait notify/notifyall
pack unpack
位于java.util.concurrent.locks包下,线程的阻塞原语,用来阻塞线程和唤醒线程,每个使用LockSupport的线程都会与一个许可关联,如果该许可可用,并且可在线程中使用,则调用park()将会立即返回,否则可能阻塞。如果许可尚不可用,则可以调用 unpark 使其可用。但是注意许可不可重入,也就是说只能调用一次park()方法,否则会一直阻塞。
与wait/nofity的区别:
wait、nofity必须在synchronized一起 使用。
wait、nofity必须先wait再notify才有用,pack/unpack不需要
二 volatile
修饰共享变量,保持对共享变量的可见性,禁止指令重排序,但是不保证原子性。
volatile关键字会开启总线得mesi缓存一致性协议。
mesi缓存一致性协议:多个CPU从主内存读取同一个数据到各自得高速缓存,当其中某个cpu修改了缓存里得数据,该数据会马上同步回主内存,其他cpu通过总线嗅探机制可以感知到数据得变化从而将自己缓存里得数据失效
禁止指令重排序
为什么会有重排序呢?
现代cpu支持多级指令流水线,例如支持同时执行 取指令-指令译码-执行指令-内存访问-数据写回的处理器,就可以称之为五级指令流水线。这时cpu可以在一个时钟周期内,同时运行五条指令的不同阶段。提高了指令的吞吐率。
volatile的底层实现原理是内存屏障
对volatile变量的写指令后会加入写屏障
对volatile变量的读指令前会加入读屏障
写屏障:写屏障就是把屏障加在写指令的后面,这个指令及之前的写操作都会被同步到主存中去;
读屏障:读屏障就是把屏障加在读操作指令的前面,包括这条以及之后的读都是从主存中读。
volatile的应用----单例模式
public class Singleton {
private volatile static Singleton singleton;//懒汉式
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
syn