Java多线程、死锁、活锁、ThreadLocal源码分析

2020-05-24  本文已影响0人  仕明同学

Java里的程序天生就是多线程的,启动线程的方式只有两种,继承Thead和实现Runnable接口

程序启动Main的线程


image.png image.png

Java中线程的状态分为6种:

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  3. 阻塞(BLOCKED):表示线程阻塞于锁。
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。1
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
  6. 终止(TERMINATED):表示该线程已经执行完毕。

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

image.png

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

活锁?

两个线程在尝试拿锁的机制中,发生多个线程之间互相谦让,不断发生同一个线程总是拿到同一把锁,在尝试拿另一把锁时因为拿不到,而将本来已经持有的锁释放的过程。
解决办法:每个线程休眠随机数,错开拿锁的时间。

线程饥饿

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

ThreadLocal

ThreadLocal与Synchonized的比较

ThreadLocal和Synchonized都用于解决多线程并发訪问。可是ThreadLocal与synchronized有本质的差别。synchronized是利用锁的机制,使变量或代码块在某一时该仅仅能被一个线程訪问。而ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。

image.png
image.png
image.png image.png
image.png

先取到当前线程,然后调用getMap方法获取对应的ThreadLocalMap,ThreadLocalMap是ThreadLocal的静态内部类,然后Thread类中有一个这样类型成员,所以getMap是直接返回Thread的成员。

可以看到有个Entry内部静态类,它继承了WeakReference,总之它记录了两个信息,一个是ThreadLocal<?>类型,一个是Object类型的值。getEntry方法则是获取某个ThreadLocal对应的值,set方法就是更新或赋值相应的ThreadLocal对应的值。

image.png
image.png

回顾get方法,其实就是拿到每个线程独有的ThreadLocalMap

然后再用ThreadLocal的当前实例,拿到Map中的相应的Entry,然后就可以拿到相应的值返回出去。当然,如果Map为空,还会先进行map的创建,初始化等工作。

方法解析

• void set(Object value) 
• public Object get() 
• public void remove() 
• protected Object initialValue() 

RESOURCE代表一个能够存放String类型的ThreadLocal对象。此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是线程安全的。

public final static ThreadLocal<String> RESOURCE = new ThreadLocal<String>();

源码分析
ThreadLocal 说到底就是来安全访问线程的变量,不是这个线程的就访问不了。在Handler的底层用来Looper.prepareMainLooper();方法

    ////Looper的prepare方法,并且关联到主线程
    public static void prepareMainLooper() {
        //Only one Looper may be created per thread"
        // false意思不允许我们程序员退出(面向我们开发者),因为这是在主线程里面
        // TODO: 2018/5/17   
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //把Looper设置为主线程的Looper
            sMainLooper = myLooper();
        }
    }

关于prepare(false)方法: Only one Looper may be created per thread 也就是说,一个线程只有一个Looper对象,要不然会抛出异常

   private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

我们可以自己实现这个功能对吧?如下的代码实现的过程

public class MyThreadLocal<T> {
    /*存放变量副本的map容器,以Thread为键,变量副本为value*/

    private Map<Thread,T> threadTMap = new HashMap<>();

    /**
     * 不去抢线程了,但是会去抢这个锁,对象锁,
     * 就好像很多的篮球放到很多的柜子里面,但是里面只有一把锁能开开
     *
     * @return
     */
    public synchronized T get(){
        return  threadTMap.get(Thread.currentThread());
    }

    /**
     * 这个锁很精华了
     * @param t
     */
    public synchronized void set(T t){
        threadTMap.put(Thread.currentThread(),t);
    }

}

但是这个Demo有个问题就是:不去抢线程了,但是会去抢这个锁,对象锁,就好像很多的篮球放到很多的柜子里面,但是里面只有一把锁能开开,这个是有很大的性能的问题的
在回头过来看这个问题:是以Thread.currentThread()为key的,是以当前的线程,

image.png
上一篇 下一篇

猜你喜欢

热点阅读