多线程


阻塞线程关键字

线程有几种状态?
新建状态、就绪状态、运行状态、阻塞状态、死亡状态

使线程进入阻塞状态的几种方式?
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
image
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()方法,那么就会导致内存泄漏,因为这块内存一直存在。
返回顶部

posted @ 2020-03-28 10:11  平淡454  阅读(161)  评论(0编辑  收藏  举报