zoukankan      html  css  js  c++  java
  • 多线程


    阻塞线程关键字

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

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

  • 相关阅读:
    angular
    客户端存储cookie ---(优缺点及定义及用途)
    cookie的设置和获取
    和谐敏感字
    移动端上滑下滑换图片
    移动端适配方式
    Viewport及判断移动端上下滑动
    HTML5拖放&地理定位
    用canvas 做一个钟表
    用canvas上传图片
  • 原文地址:https://www.cnblogs.com/yanhui007/p/12585918.html
Copyright © 2011-2022 走看看