zoukankan      html  css  js  c++  java
  • 线程的补充



    1. ThreadLocal

    ThreadLocal让线程有自己的局部变量,其中重要的方法有:set(),get(),remove()


    ThreadLocal的使用

    public static void main(String[] args) {
    	
    	ThreadLocal<String> local = new ThreadLocal();
    
    	Thread thread1 = new Thread( () -> {
    		local.set("我是线程1的消息");
    		System.out.println("线程1的输出:" + local.get());
    	});
    	
    	Thread thread2 = new Thread( () -> {
    		System.out.println("线程2的输出:" + local.get());
    	}) ;
    	
    	thread1.start();
    	thread2.start();
    }
    
    线程1的输出:我是线程1的消息
    线程2的输出:null
    

    线程1存的内容,只有线程1能使用,其他线程拿不到



    2. 原理


    • 来看看set方法
    public void set(T value) {
        Thread t = Thread.currentThread();		//获取当前线程
        ThreadLocalMap map = getMap(t);			//获取ThreadLocalMap,下面会说明
        if (map != null)						//map非空就设值
            map.set(this, value);				//键为threadloacl
        else									//空了就创建,map的键为当前线程,值为存入的值
            createMap(t, value);
    }
    

    • ThreadLocal内部维护了一个静态内部类--ThreadLocalMap,其内部又维护了Map
    static class ThreadLocalMap {
    
    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    

    • 在Thread中维护了一个变量ThreadLocalMap,真实复杂
    • 即Thread维护了一个特殊的map集合,键值分别为线程和要存的值,这样就实现了ThreadLocal了
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    


    3. 内存泄漏

    ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用,想要避免内存泄露就要手动remove(),key是弱引用,值不是



    4. 线程死锁

    • 互斥条件:线程使用的资源不共享
    • 请求与保持条件:一个线程有一个资源且等待获取一个被其他线程拥有的资源
    • 非剥夺条件:分配的资源不能从相应的线程中被强制剥夺
    • 循环等待条件:一个线程等待其他线程,其他线程又等待该线程

    避免死锁:

    • 固定加锁的顺序(针对锁顺序死锁)
    • 开放调用(针对对象之间协作造成的死锁)
    • 使用定时锁tryLock(),如果等待获取锁时间超时,则抛出异常而不是一直等待!


    5. Atomic

    public class Synchronizedtest implements Runnable {
    
    	int i = 0;
    
    	@Override
    	public void run() {
    			i++;
    			System.out.println("i :" + i);
    	}
    	
    	public static void main(String[] args) throws InterruptedException {
    		
    		Synchronizedtest st = new Synchronizedtest();
    		
    		for (int i = 1; i <= 1000; i++) {
                new Thread(st).start();
            }
    	}
    }
    

    多试几次会发现最后结果不是1000,为什么会这样呢?上面操作是线程不安全的,因为 i++ 不是原子操作,要分为三步:

    • 读取 i 值
    • 把读取的值 + 1
    • 再把新值赋到 i 中

    但如果用synchronized方法未免太浪费了,一个加法就用上了锁

    public synchronized void run() {
        i++;
        System.out.println("i :" + i);
    }
    

    那么还有一种方法:java.util.concurrent.atomic,原子类,通过自旋CAS操作volatile变量实现

    下面以AtomicInteger为例

    public class Synchronizedtest implements Runnable {
    
    	AtomicInteger i = new AtomicInteger(0);
    
    	@Override
    	public void run() {
    			i.addAndGet(1);
    			System.out.println("i :" + i);
    	}
    	
    	public static void main(String[] args) throws InterruptedException {
    		
    		Synchronizedtest st = new Synchronizedtest();
    		
    		for (int i = 1; i <= 1000; i++) {
                new Thread(st).start();
            }
    	}
    }
    

    创建原子类后,要使用该类特有方法来实现加减乘除,而不是直接 i++;

    之后的结果一直为1000,实现了原子性

    下面列出几个常见方法:

    • set(int newValue) 设置新值

    • get() 获得当前值

    • getAndDecrement() :-=

    • getAndIncrement() :+=

    • addAndGet(int delta):+/-= delta可以为负数

    • compareAndSet(int expect, int update): CAS操作


    ABA问题

    • 假设 num = 0
    • 线程1修改其为10:compareAndSet(0, 10)
    • 线程2修改其为 0 :compareAndSet(10, 0)
    • 线程3修改为100,重点在于线程3不知道 num 已经多次修改,只是最后才将值改回来::compareAndSet(0, 100)

    下面也举了个例子

    public class ABADemo {
        
        private static AtomicReference atomicReference = new AtomicReference(0);
    
        public static void main(String[] args) {
            
            new Thread( () -> {
                atomicReference.compareAndSet(0, 10);
                atomicReference.compareAndSet(10, 0);
            },"t1").start();
            
            new Thread( () -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicReference.compareAndSet(100, 2019);
            },"t2").start();
        }
    }
    

    可以发现t2可以修改t1改变之后的值


    解决方法:增加一个版本号

    这件事如果发生在链表中就是不安全的,所以JDK提供了类来解决

    • AtomicStampedReference

    • AtomicMarkableReference

    public class ABADemo {
        
        private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);
    
        public static void main(String[] args) {
            
            new Thread(() -> {
                
                //t1的初始版本号
                System.out.println(atomicStampedReference.getStamp());
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicStampedReference.compareAndSet(0, 10,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
                atomicStampedReference.compareAndSet(10, 0,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            },"t1").start();
            
            new Thread(() -> {
                //t2初始版本号
                System.out.println(atomicStampedReference.getStamp());
                
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //最新版本号
                System.out.println(atomicStampedReference.getStamp());
                atomicStampedReference.compareAndSet(0, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            },"t2").start();
        }
    }
    

    可以看到t2因为版本号对不上而不能操作成功



  • 相关阅读:
    vue.js
    kendo ui DatePicker 时区转换
    List Except 失效 差集失效
    RabbitMQ的安装
    net core 使用 Redis
    SQLSERVER学习八:事务和锁
    SQLSERVER学习七:存储过程
    SQLSERVER学习六:函数
    SQLSERVER学习五:流程控制语句
    SQLSERVER学习四:常用检索语句
  • 原文地址:https://www.cnblogs.com/Howlet/p/12234203.html
Copyright © 2011-2022 走看看