线程与进程理论知识入门05-(新启线程,线程的生命周期,死锁,ThreadLocal)

新启线程- 只有两种

之前认为有3种

  1. 派生自Thread 这个类
  2. 实现一个Runnable接口,把接口实例化后交给一个线程去执行
  3. 实现一个Callable接口,把接口实例化后交给一个线程去执行

新启线程- 只有两种方式
Callable这一种严格意义上讲不能算是

Thread源码:

 1. There are two ways to create a new thread of execution. One is to
 2. declare a class to be a subclass of <code>Thread</code>.
 3. ......
 4. The other way to create a thread is to declare a class that
 5. implements the <code>Runnable</code> interface. 
只有两种方式创建一个线程去执行
 派生自Thread 这个类
 实现一个Runnable接口,把接口实例化后交给一个线程去执行

看看Thread的构造方法:
在这里插入图片描述
构造方法并没有可以接收Callable这种参数的构造方法,一个都没有。
我们把Callable交给Thread的执行的时候,本质上是Callable的实例包装成了一个FutureTask,FutureTask又实现了RunnableFuture接口,RunnableFuture接口派生自Runnable接口,说到底Callable包装成Runnable交给了线程去执行的,本质上还是实现了Runnable接口

线程的状态(线程的生命周期)

在这里插入图片描述
Java里面线程的状态总共分为六种:
1.初始态:
new 出一个线程,并没开始执行,调用start()方法以后就开始Runnable运行状(就绪态,运行态)
2.运行态:

  1. 就绪态: 线程的CPU时间片用完了或者某种原因被操作系统剥夺了或者放弃了就进入就绪态,等待操作系统分配时间片。
  2. 运行态: 当前线程被分配了时间片

3.等待态:
Object.wait() 当前线程进入等待,如果没有唤醒,(Object.notify()/Object.notifyAll())就会一直等,会等待超时,
Object.wait(long) 传一个时间长度,时间到了没人唤醒,也要恢复
4.等待超时态:
Object.wait() 当前线程进入等待,如果没有唤醒,会等待超时
5.阻塞态:
当前线程调用了同步方法,调用了synchronized关键字修饰的代码块或者方法的时候,如果没有拿到锁,这个时候就进入阻塞态,重新获得锁就进入Runnable状态
6.消亡态:
run()结束、main()结束

问题:

  1. 调用sleep进入等待/等待超时状态
  2. 使用了显示锁里面的lock()没有拿到锁,底层实现使用的是LockSupport,进入的是等待态或者超时等待态。
  3. 一个线程进入阻塞态有且仅有调用使用synchronized关键字的时候才会进入阻塞态

死锁

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

  1. 多个操作者(m>=2)争夺多个资源(n>=2),n<=m
  2. 争夺资源的顺序不对 (定好顺序)
  3. 拿到资源不放手 (显示锁尝试拿锁)

学术化死锁定义:

  1. 互斥条件(独享)
  2. 请求和保持(拿到了一个,请求一个新的资源)
  3. 不剥夺
  4. 环路等待

尝试拿到锁:

/**
 *类说明:演示尝试拿锁解决死锁
 */
public class TryLock {
    private static Lock No1 = new ReentrantLock();//第一个锁
    private static Lock No2 = new ReentrantLock();//第二个锁

    //先尝试拿No1 锁,再尝试拿No2锁,No2锁没拿到,连同No1 锁一起释放掉
    private static void fisrtToSecond() throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        Random r = new Random();
        while(true){
            if(No1.tryLock()){
                System.out.println(threadName
                        +" get 1");
                try{
                    if(No2.tryLock()){
                        try{
                            System.out.println(threadName
                                    +" get 2");
                            System.out.println("fisrtToSecond do work------------");
                            break;
                        }finally{
                            No2.unlock();
                        }
                    }
                }finally {
                    No1.unlock();
                }

            }
            //休眠一小段时间
            //不休眠的话,拿锁的过程会拉长 活锁
            Thread.sleep(r.nextInt(3));
        }
    }

    //先尝试拿No2锁,再尝试拿No1锁,No1锁没拿到,连同No2锁一起释放掉
    private static void SecondToFisrt() throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        Random r = new Random();
        while(true){
            if(No2.tryLock()){
                System.out.println(threadName
                        +" get 2");
                try{
                    if(No1.tryLock()){
                        try{
                            System.out.println(threadName
                                    +" get 1");
                            System.out.println("SecondToFisrt do work------------");
                            break;
                        }finally{
                            No1.unlock();
                        }
                    }
                }finally {
                    No2.unlock();
                }

            }
            //休眠一小段时间
            //不休眠的话,拿锁的过程会拉长 活锁
            Thread.sleep(r.nextInt(3));
        }
    }

    private static class TestThread extends Thread{

        private String name;

        public TestThread(String name) {
            this.name = name;
        }

        public void run(){
            Thread.currentThread().setName(name);
            try {
                SecondToFisrt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread.currentThread().setName("MainThread");
        TestThread testThread = new TestThread("TestThread");
        testThread.start();
        try {
            fisrtToSecond();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
TestThread get 2
MainThread get 1
MainThread get 2
fisrtToSecond do work------------
TestThread get 2
TestThread get 1
SecondToFisrt do work------------

如果代码中没有休眠一小段随机时间(把两段时间错开)Thread.sleep(r.nextInt(3));,那么会拿锁的过程会拉长,活锁
A(1)<2> A 拿到1尝试拿2
B(2)<1> B拿到2尝试拿1
在不断拿锁释放锁子中进行下去—活锁

MainThread get 1
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
MainThread get 1
MainThread get 1
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
TestThread get 2
MainThread get 1
TestThread get 2
MainThread get 1
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
TestThread get 2
MainThread get 1
MainThread get 2
fisrtToSecond do work------------
TestThread get 2
TestThread get 1
SecondToFisrt do work------------

线程饥饿 : 线程总是拿不到CPU执行权

ThreadLocal

线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。ThreadLocal可以让每个线程拥有一个属于自己的变量的副本,不会和其他线程的变量副本冲突,实现了线程的数据隔离。
在这里插入图片描述
自己用map实现ThreadLocal:

/**
 * 类说明:自己实现的ThreadLocal
 */
public class MyThreadLocal<T> {
    /*存放变量副本的map容器,以Thread为键,变量副本为value*/
    private Map<Thread,T> threadTMap = new HashMap<>();

    public synchronized T get(){
        return  threadTMap.get(Thread.currentThread());
    }

    public synchronized void set(T t){
        threadTMap.put(Thread.currentThread(),t);
    }

}
public class UseThreadLocal {

    //static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    //static ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
	static MyThreadLocal<String> threadLocal = new MyThreadLocal<>();

    /**
     * 运行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() {
            String threadName = Thread.currentThread().getName();
            threadLocal.set("线程"+id);
            /*if(id==1) {
                threadLocal2.set(id);//线程1才会执行
            }*/
            System.out.println(threadName+":"+threadLocal.get());
        }
    }

    public static void main(String[] args){
    	UseThreadLocal test = new UseThreadLocal();
        test.StartThreadArray();
    }
}
Thread-0:线程0
Thread-1:线程1
Thread-2:线程2

这样的实现看似的的确确是让每一个线程拥有一个自己变量的副本,但是多个线程却会在这个大的map上面产生一个激烈的竞争,

JDK里面的ThreadLocal是怎么实现的呢?

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

把当前线程传给 getMap(t)

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

点击查看threadLocals,进入了Thread.java,返回的ThreadLocalMap 是当前线程的一个成员变量

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap 是在 ThreadLocal.java 里面定义的(ThreadLocalMap 和ThreadLocal 没有关系,把ThreadLocalMap 提出来也可以,静态内部类和普通的类没有差别)

ThreadLocalMap 里面定义了一个Entry型的数组

static class ThreadLocalMap {
......
private Entry[] table;
......
}

Entry型是ThreadLocal作为键,Object作为value

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

总结逻辑:
1.线程thread-1已经有了
2.在每个线程实例的内部产生一个threadLocalMap这样一个对象实例
3.threadLocalMap内部有一个Entry型的数组
4.当调用set方法时,就有了元素,元素的键就是threadLocal
在这里插入图片描述

什么要用一个数组呢?
上面的代码换成ThreadLocal,并且在id = 1时多set一个值

public class UseThreadLocal {

    static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    static ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
	//static MyThreadLocal<String> threadLocal = new MyThreadLocal<>();

    /**
     * 运行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() {
            String threadName = Thread.currentThread().getName();
            threadLocal.set("线程"+id);
            if(id==1) {
                threadLocal2.set(id);//线程1才会执行
            }
            System.out.println(threadName+":"+threadLocal.get());
        }
    }

    public static void main(String[] args){
    	UseThreadLocal test = new UseThreadLocal();
        test.StartThreadArray();
    }
}

结果是:
在这里插入图片描述
在这里插入图片描述
ThreadLocal是如何实现让每个线程有自己的副本呢?不是用一个容器来存,而是在每个线程内部自己保存变量的副本,同时一个线程是允许多个threadLocal型的变量的,所以内部就用数组来保存多个threadLocal型的变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值