美文网首页
笔记:多线程并发编程(1)锁、ThreadLocal、同步机制使

笔记:多线程并发编程(1)锁、ThreadLocal、同步机制使

作者: 盐海里的鱼 | 来源:发表于2021-03-09 14:35 被阅读0次

    死锁

    是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

    产生死锁的条件:

    1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
    2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
    3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
    4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

    • 自己总结(人话):
      1. 争夺者数目大于争夺资源
      2. 争夺资源顺序不对
      3. 拿到资源不放手
      4. 有另外一个等待使用资源的线程
    解决死锁:

    只要打破四个必要条件之一就能有效预防死锁的发生。
    打破互斥条件:改造独占性资源为虚拟资源,大部分资源已无法改造。
    打破不可抢占条件:当一进程占有一独占性资源后又申请一独占性资源而无法满足,则退出原占有的资源。
    打破占有且申请条件:采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待,这样就不会占有且申请。
    打破循环等待条件:实现资源有序分配策略,对所有设备实现分类编号,所有进程只能采用按序号递增的形式申请资源。

    • 自己总结(人话):
      1. 确定每个线程的拿锁顺序
      2. 采用尝试拿锁的方式
    活锁

    频繁申请锁两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一个线程总是拿到同一把锁,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。

    解决办法:每个线程休眠随机数,错开拿锁的时间。

    线程饥饿

    低优先级的线程,总是拿不到执行时间

    Thread Local

    Thread Local 线程本地变量 为每个线程提供一个变量副本。实现线程隔离
    eg: 创建三个线程分别对变量count+线程id

    • 未使用ThreadLocal:
    public class NoThreadLocal {
        static Integer count = new Integer(1);
        /**
         * 运行3个线程
         */
        public void startTArray(){
            Thread[] runs = new Thread[3];
            for(int i=0;i<runs.length;i++){
                runs[i]=new Thread(new TestTask(i));
            }
            for(int i=0;i<runs.length;i++){
                runs[i].start();
            }
        }
        /**
         *类说明:
         */
        public static class TestTask implements Runnable{
            int id;
            public TestTask(int id){
                this.id = id;
            }
            public void run() {
                System.out.println(Thread.currentThread().getName()+":start");
                count = count+id;
                System.out.println(Thread.currentThread().getName()+":"
                        +count);
            }
        }
    
        public static void main(String[] args){
            NoThreadLocal test = new NoThreadLocal();
            test.startTArray();
        }
    }
    
    打印结果:
    Thread-1:start
    Thread-1:2
    Thread-0:start
    Thread-0:2
    Thread-2:start
    Thread-2:4
    

    并没有每个线程按照 t0->0 、t1->1 、t2->2

    • 使用ThreadLocal:
    public class UseThreadLocal {
    
        //TODO
       private static ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>(){
            int count = 0;
            @Override
            protected Integer initialValue() {
                return count;
            }
        };
        /**
         * 运行3个线程
         */
        public void StartThreadArray(){
            Thread[] runs = new Thread[3];
            for(int i=0;i<runs.length;i++){
                runs[i]=new Thread(new TestThread(i));
            }
            for(int i=0;i<runs.length;i++){
                runs[i].start();
            }
        }
        
        /**
         *类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
         */
        public static class TestThread implements Runnable{
            int id;
            public TestThread(int id){
                this.id = id;
            }
            public void run() {
                System.out.println(Thread.currentThread().getName()+":start");
                //TODO
                int value = intThreadLocal.get()+id;
                intThreadLocal.set(value);
                System.out.println(Thread.currentThread().getName()+" : "+intThreadLocal.get());
            }
        }
    
        public static void main(String[] args){
            UseThreadLocal test = new UseThreadLocal();
            test.StartThreadArray();
        }
    }
    打印结果:
    Thread-0:start
    Thread-0 : 0
    Thread-1:start
    Thread-1 : 1
    Thread-2:start
    Thread-2 : 2
    
    • ThreadLocal简析:
      前面说到ThreadLocal是本地变量副本。那么他是怎么实现的呢。由ThreadLocal的set()入手
    ThreadLocal.java
    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);//通过线程获得ThreadLocalMap
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    拿到 ThreadLocal.ThreadLocalMap threadLocals
    可以看到ThreadLocal.ThreadLocalMap 里面有个Entity[]
    Entity的具体模型:
     static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
                //以threadLocal为key value 为值存入
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    因为可能存在多个类型的threadlocal 所以需要使用数组
    
    threadLocal解析.png 不同线程内部ThreadLocal解析.png

    volatile的使用 (最轻量的同步机制)

    适合一写多读的场景

    • 优点:保证可见性与快速更新
    • 缺点:无法保证线程安全与操作原子性
      使用 :
    • 可见性例子:
    public class VolatileCase {
        private  static boolean ready;
        private static int number;
    
        private static class PrintThread extends Thread{
            @Override
            public void run() {
                System.out.println("PrintThread is running.......");
                while(!ready);
                System.out.println("number = "+number);
            }
        }
    
        public static void main(String[] args) {
            new PrintThread().start();
            SleepTools.second(1);
            number = 51;
            ready = true;
            SleepTools.second(5);
            System.out.println("main is ended!");
        }
    }
    打印结果:
    PrintThread is running.......
    main is ended!
    PrintThread并不知晓ready已经变化
    
    加入 volatile关键字
    private  volatile  static boolean ready;
    打印结果:
    PrintThread is running.......
    number = 51
    main is ended!
    

    线程不安全例子:

    public class NotSafe {
        private volatile long count =0;
    
        public long getCount() {
            return count;
        }
    
        public void setCount(long count) {
            this.count = count;
        }
    
        //count进行累加
        public void incCount(){
            count++;
        }
    
        //线程
        private static class Count extends Thread{
    
            private NotSafe simplOper;
    
            public Count(NotSafe simplOper) {
                this.simplOper = simplOper;
            }
    
            @Override
            public void run() {
                for(int i=0;i<10000;i++){
                    simplOper.incCount();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            NotSafe simplOper = new NotSafe();
            //启动两个线程
            Count count1 = new Count(simplOper);
            Count count2 = new Count(simplOper);
            count1.start();
            count2.start();
            Thread.sleep(50);
            System.out.println(simplOper.count);
        }
    }
    理想结果:20000
    打印结果:
    20000
    13671
    13529
    因为线程的执行是需要有cpu执行权的 所以导致了结果的不确定性
    

    synchronized的使用

    synchronized 一定是作用在某个对象上 当所在static 的方法 或者静态块 时 锁住的是 X.class的对象
    注:锁只有作用在同个对象上才会起作用

    public class SynTest {
    
        private long count =0;
    
        public long getCount() {
            return count;
        }
    
        public void setCount(long count) {
            this.count = count;
        }
    
        /*用在同步块上*/
        public void incCount(){
                count++;
        }
    
        
    
        //线程
        private static class Count extends Thread{
    
            private SynTest simplOper;
    
            public Count(SynTest simplOper) {
                this.simplOper = simplOper;
            }
    
            @Override
            public void run() {
                for(int i=0;i<10000;i++){
                    simplOper.incCount();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            SynTest simplOper = new SynTest();
            //启动两个线程
            Count count1 = new Count(simplOper);
            Count count2 = new Count(simplOper);
            count1.start();
            count2.start();
            Thread.sleep(50);
            System.out.println(simplOper.count);//20000
        }
    }
    打印结果:
    18748 20000 16735 18307 
    加锁:
    public void incCount(){
            synchronized (obj){
                count++;
            }
        }
    打印结果:
    20000 20000 20000 20000
    

    锁的作用对象:

    public class SynTest {
    
        private long count =0;
        private Object obj = new Object();//作为一个锁
    
        public long getCount() {
            return count;
        }
    
        public void setCount(long count) {
            this.count = count;
        }
    
            /*锁的是SyncTest.class的类对象*/
        public void incCount(){
            synchronized (SyncTest.class){
                count++;
            }
        }
    
        /*用在方法上 锁的是obj对象*/
        public void incCount(){
            synchronized (obj){
                count++;
            }
        }
    
          /*用在方法上 锁的也是SynTest.class对象*/
        public static synchronized void incCount2(){
                count++;
        }
    
        /*用在方法上 锁的也是当前对象实例*/
        public synchronized void incCount2(){
                count++;
        }
    
        /*用在同步块上,但是锁的是当前类的对象实例*/
        public void incCount3(){
            synchronized (this){
                count++;
            }
        }
    }
    
    

    相关文章

      网友评论

          本文标题:笔记:多线程并发编程(1)锁、ThreadLocal、同步机制使

          本文链接:https://www.haomeiwen.com/subject/unevqltx.html