多线程
阻塞线程关键字
线程有几种状态?
新建状态、就绪状态、运行状态、阻塞状态、死亡状态
使线程进入阻塞状态的几种方式?
wait、yield、sleep、join、interrupt
wait
使当前线程让出锁,进入阻塞状态,直到超时或者notify,线程进入就绪状态
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(1000);
System.out.println("2");
synchronized (this){
notify();
}
System.out.println("3");
}catch (Exception e){
}
}
});
t1.start();
synchronized (t1){
try{
System.out.println("5");
t1.wait();
System.out.println("4");
}catch (Exception e){
}
}
}
关于wait有两点要注意
1、wait和notify必须要sychronize代码块中
2、wait阻塞的不是调用它的那个线程,而是当前线程。如上代码,t1.wait(),阻塞的是当面线程(也就是main方法的线程),而t1这个线程继续运行。
yield
让线程进入就绪状态,但是不会释放锁。
sleep
让现场进入阻塞状态,到时间后进入就绪状态,不释放锁。
join
阻塞父线程,等待子线程执行完毕,底层用wait方法实现
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(1000);
System.out.println("3");
}catch (Exception e){
}
}
});
t1.start();
try{
t1.join();//主线程是父线程、t1是子线程
System.out.println("123");
}catch (Exception e){
}
}
interrupt
给线程一个中断标识,不会中断线程,需要自己根据中断标识去中断线程
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try{
while (!Thread.currentThread().isInterrupted()){
System.out.println("xx");
}
}catch (Exception e){
}
}
});
t1.start();
try{
Thread.sleep(3000);
t1.interrupt();
}catch (Exception e){
}
}
java内存模型
内存模型(jmm)是java定义的一些规则,目的是为了解决并发安全问题。
线程工作的时候会有把变量重主内存load到工作内存中去操作,然后在回写到主内存,这个过程如果发生在多线程下就会存在安全问题。jmm就是解决这个问题的。
jmm定义了三个原则:有序性、可见行、原子性。只要代码能保证这三个原则,就能避免并发安全问题。
jmm8个基本原子动作,lock unlock read load use assgin store write
但是long和double类型的这9个基本动作不能保证原子性,也就是对long和double类型的read和load操作中间可能被其他线程插入。
这三个原则分别是什么?jmm如何保证这三个原则?
原子性:当线程要修改一个变量i的值时,分三步
1、将i load到工作内存
2、修改工作内存中i的值
3、回写i的值到主内存中
这个过程并不是原子的,也就是当i回写到主内存时其他线程可能已经修改过i的值了。
可见性:还是上边那个例子,在当前线程在使用i的过程中,其他线程修改了i的值,如果能当前线程可以立马知道,就是可见性。
有序性:比如有如下代码:
int i=0;
int j=0;
int i=j+1;
理论上java应该是按照顺序执行这三行代码,但是并不是,jvm为了优化性能,会打乱顺序来执行这三行代码,这就算有序性。
那么如何保证这三个原则?
1、锁。
2、jmm制定了一些天然的规则(happen-before原子)可以保证这三个原则。
3、concurrent包下的automic系列关键字、cas、volitle关键字。
happen-before原则:比如A代码在B代码之前,那么当执行B代码的时候,A代码产生的且对B有影响的改变,B都是可以感知到的。
具体有8个原则:
1、程序次序规则:单线程程序按照代码顺序执行,单线程环境,不需要过多考虑。
2、管程锁定规则:对于同一个锁,unlock肯定先行发生与lock,其实就是多线程竞争锁
public class XxxTest {
public static void main(String[] args) {
XxxTest a = new XxxTest();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(a){//线程t1获取对象a的锁
try{
new Thread().sleep(2000);
System.out.println("22");
}catch (Exception e){
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized(a) {//线程t1 unlock之前,此处t2的lock会等待
System.out.println("123");
}
}
});
t1.start();
t2.start();
}
}
3、volatile变量规则:对一个变量的写操作先行发生与读操作。
个人理解这个就是指volatile可见性。
public class XxxTest {
static volatile boolean flag = false;
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try{
while (!flag){//一旦线程t2更新flag,这里马上可见
}
System.out.println("线程t1读取到了线程t2更新到主内存的值");
}catch (Exception e){
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
flag = true;//线程t2更新flag后马上刷新主内存
}
});
t1.start();
try{
new Thread().sleep(2000);//这里保证先进入线程t1
t2.start();
}catch (Exception e){
}
}
}
4、传递规则:A发生B之前,B发生在C之前,那么A一定发生在C之前。
5、线程启动规则:Thread对象的start方法先行发生于此对象的每一个操作。
6、线程终止规则:线程的所有操作都先行发生与对此线程的终止检测
7、线程中断规则:
8、对象终结规则:一个对象的初始化(构造函数)的执行先行发生于他的finalize方法
单利模式
1、最初级写法:
public class Singleton {
private static Singleton self;
private Singleton(){}
public static Singleton getInstance(){
if(self==null){
//并发环境,多个线程可能同时进到这里
return new Singleton();
}
return self;
}
}
但是多线程的情况下就不是单利了,改进加锁:
2、
public class Singleton {
private static Singleton self;
private Singleton(){}
public synchronized static Singleton getInstance(){//此处加锁解决并发安全,但是锁整个方法效率低
if(self==null){
return new Singleton();
}
return self;
}
}
锁整个方法效率低,在次优化,锁方法改成锁代码块:
public class Singleton {
private static Singleton self;
private Singleton(){}
public static Singleton getInstance(){
if(self==null){
//并发下A、B两个线程可以同时到这里,A获取锁执行下边代码,B卡在这里等待
//当A执行完释放锁后,B又可以获取锁再次new出个对象
synchronized(Singleton.class){
return new Singleton();
}
}
return self;
}
}
还存在并发问题,继续优化:
public class Singleton {
private static Singleton self;
private Singleton(){}
public static Singleton getInstance(){
if(self==null){
//并发下A、B两个线程可以同时到这里,A获取锁执行下边代码,B卡在这里等待
//当A执行完释放锁后,B又可以获取锁再次new出个对象
synchronized(Singleton.class){
//B线程进来的时候再次判断一下
if(self!=null){
return new Singleton();
}
}
}
return self;
}
}
但是此时还存在一个指令重排的问题,new Singleton()这端代码在内存中包括三个步骤:
1、分配内存空间
2、初始化对象
3、赋值给变量
这三个步骤存在指令重排的情况,可能先执行3,后执行2,当3执行完了,其他线程就可能已经获取到该对象了,但是2还未执行,使用该对象时就会出现问题。
新的内存模型可以使用volitatle关键字禁止指令重排,如上代码把Singleton变量用volitatle修饰就可以解决,但是volitatle性能低。所以又出现了终极方案,内部类。
public class Singleton {
private Singleton(){}
private static class SingletonFactory{
public static final Singleton self = new Singleton();
}
public static Singleton getInstance(){
return SingletonFactory.self;
}
}
threadLocal
多线程访问threadLocal类型的变量时,每个线程的变量数据会单独存储,互相隔离。
public static void main(String[] args) {
ThreadLocal threadLocal = new ThreadLocal();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("t1");
System.out.println("线程t1:"+threadLocal.get());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("t2");
System.out.println("线程t2:"+threadLocal.get());
}
});
t1.start();
t2.start();
}
内部实现
1、每个Thread持有一个ThreadLocalMap
2、ThreadLocalMap是ThreadLocal内部类,内部维护一个Entry数组,定义几个ThreadLocal变量,Entry数组就有几个值。结合下面代码更好理解:
public static void main(String[] args) {
//后续管它叫ThreadLocal变量
ThreadLocal threadLocal1 = new ThreadLocal();
ThreadLocal threadLocal2 = new ThreadLocal();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//向线程t1的ThreadLocalMap添加两个元素,
// 相当于在entry[0]位置添加值"value1",在entry[1]位置添加值"value2"
threadLocal1.set("value1");
threadLocal2.set("value2");
System.out.println(threadLocal1.get());
System.out.println(threadLocal2.get());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//向线程t2的ThreadLocalMap添加一个元素
threadLocal1.set("value2");
System.out.println("线程t2:"+threadLocal1.get());
}
});
//两个线程,t1、t2 相当于创建两个ThreadLocalMap,每个ThreadLocalMap是完全独立的
t1.start();
t2.start();
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
//k是当前ThreadLocal变量
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//table初始大小
private static final int INITIAL_CAPACITY = 16;
//用来存放ThreadLocal变量
private ThreadLocal.ThreadLocalMap.Entry[] table;
private int size = 0;
}
set方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = getMap(t);//获取当前线程的ThreadLocalMap
if (map != null)
map.set(this, value);
else//如果是第一次添加,初始化ThreadLocalMap
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化entry数组,大小为16
table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
//根据ThreadLocal变量作为key进行哈希 + entry数组大小,计算出当前ThreadLocal存放到数组的索引位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//存入数组
table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private void set(ThreadLocal<?> key, Object value) {
//先计算出要存入entry数组的索引位置,更新该位置的值
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {//循环实际只走一遍
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//rehash进行扩容
tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
get方法:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map =t.threadLocals;//先获取该线程的ThreadLocalMap
if (map != null) {
//通过哈希+entry数组大小计算索引位置,直接索引数组即可
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//该方法会初始化一个ThreadLocalMap,同时返回的value为null
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
弱引用
ThreadLocalMap是一个entry数组,每个索引上的结构为key value形势,其中key是threadLocal变量,value是实际数据。
其中key是对threadLocal变量的弱引用,所以当threadLocal变量不在使用的时候,key会自动回收。
而value是强引用,他的生命周期和引用线程是一样的,所以使用不好可能发生内存泄漏问题。
ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致内存泄漏。
分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏,因为这块内存一直存在。
返回顶部