Java多线程的浅要分析

多线程


线程&进程

可以简单地把一个进程看作一个任务,而线程就是一个进程里面的子任务,所以同一个进程里面的线程是可以共享进程里面的资源。要深刻理解多线的异步效果。


一、线程基础

1.实现多线程

在JDK包中已经自带了对多线程技术的支持,主要通过2种方式:

  • 继承Thread

    public class Thread implements Runnable 
    

    这里需要提到一下Tread类中的run方法

    //所使用的Runnable实例
    private Runnable target;
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    

    在代码使用start()方法来启动一个线程,线程启动后会自动调用线程对象中的run()方法,run()方法里面的代码就是线程对象要执行的任务,是线程执行任务的入口。可以简单地理解为一旦继承了这个Thread就成为了线程,然后有Run方法可以启动。

    就比如:

    public class myThread extends Thread{
        @Override
        public void run(){
            System.out.println("run");
        }
    
        public static void main(String[] args) {
            myThread myThread=new myThread();
            myThread.start();
            System.out.println("运行");
        }
    }
    
  • 实现Runnable接口

    public interface Runnable {
        public abstract void run();
    }
    

    可以看到实际上这个接口是被Tread类实现的,所以统一来说,实现多线程的方法也是通过实现Runnable接口(并且,由于Java的单继承,所以往往实现接口会变得容易许多)

    而且由于有new Thread(Runnable runable)的构造方法,所以任何线程都可以交由给其他线程进程操作(这里的原因也是因为上面,调用run方法之前会提前判断Thread类中是否有Runable的成员对象,并调用它的run方法,也就是套了一层外套)。

    private Runnable target;
      MyRunnable run = new MyRunnable();
    	Thread t = new Thread(run);
      t.start();//这里其实调用的就是run对象的run方法
    

    其实针对这个这个调用另外一个线程的缘由是因为这个:

    //这里是调用了这个构造函数,特地用public就是为了其他线程调用的
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    //init方法
    private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize) {
            init(g, target, name, stackSize, null, true);
        }
    
    //真实被调用的地方
        private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals) {
    		//省略一部分代码
            ...
            this.target = target;
    		...
        }
    

    这里还需要了解什么是非线程安全,

    //这里我们通过创建各个不同的线程,操作同一个线程中的共享变量(可以简单理解为,不同线程操作统一进程的资源i)
    public class myThread extends Thread{
        private int i=5;
    
        @Override
        public void run(){
            while (i-->0){
                System.out.println(i);
            }
        }
    
        public static void main(String[] args) {
            myThread thread=new myThread();
    
            Thread thread1=new Thread(thread);
            Thread thread2=new Thread(thread);
            Thread thread3=new Thread(thread);
            Thread thread4=new Thread(thread);
            Thread thread5=new Thread(thread);
    
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
        }
    }
    

    出现的结果如下,可以看到,我们看到的并不是递减的,所以有时候拿到的就是错误的资源,而目前的解决办法就是把共享的进程run方法标记为synchronized,从字面就可以看到这是为了将他们对run方法的操作进行同步的,被标记后的代码块就称为临界区,在同一时间只允许一个线程进行操作共享资源。

2.常见线程相关方法

  • i–与System.out.println()

    在源码中println方法内部是同步的代码块,但是当我们在println方法中使用i–时,也是会有线程安全问题的,因为进行i–的操作是发生在进入println方法之前的,所以需要再用同步方法。

    public void println(int x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
    
  • currentThread()

    这是在Thread类内部的方法,用于返回当前被调用的线程

    这里需要提出**run方法和start**方法的区别

    public class myThread extends Thread{
        private int i=5;
    
        @Override
        public void run(){
            System.out.println(Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            myThread thread=new myThread();
            thread.start();
            //thread.run();
        }
    }
    

    ​ 所以可以看到,当我们唤醒一个线程的时候,是真的会创建一个线程并调用里面的run函数,而当我们不再需要它去创建新线程时,即通过main这个线程可以调用普通方法一样调用其他的方法,即

    ​ 1) my.run();:立即执行run()方法,不启动新的线程

    ​ 2)my.start();:执行run()方法时机不确定,启动新的线程

  • isAlive()

    该方法用于判断当前正在被执行的线程是否存活,这个获取当前线程需要明白this. 和 Thread.currentThread 的区别,一个是获取被执行的线程,一个是获取当前正在执行的线程

  • sleep(long millis)

    sleep() 方法的作用是在指定的时间(毫秒)内让当前“正在执行的线程”休眠(暂停执行),这个“正在执行的线程”是指Thread.currentThread()返回的线程

    public class myThread extends Thread {
        @Override
        public void run() {
            try {
                //开始暂停线程
                System.out.println("run threadName="
                        + this.currentThread().getName() + " begin");
                Thread.sleep(2000);
                //暂停结束
                System.out.println("run threadName="
                        + this.currentThread().getName() + " end");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            myThread mythread = new myThread();
            System.out.println("begin =" + System.currentTimeMillis());
            mythread.run();
            System.out.println("end =" + System.currentTimeMillis());
        }
    
    }
    

在这里插入图片描述

可以看到主线程停下了2000ms,(这里并没有调用Thread-0)所以还是相当于调用main线程里面的方法。

如果修改成mythread.start();,则结果如下:

在这里插入图片描述

可以看到是Thread-0线程停下了,只不过由于异步,end的出现就在之前了

  • sleep(long millis,int nanos)

    sleep(long millis,int nanos)方法的作用是在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序的精度和准确性的影响。

  • getStackTrace()

    作用是返回一个表示该线程堆栈跟踪元素数组。如果该线程尚未启动或已经终止, 则该方法将返回一个零长度数组。如果返回的数组不是零长度 的,则其第一个元素代表堆栈顶,它是该数组中最新的方法调 用。最后一个元素代表堆栈底,是该数组中最旧的方法调用

    public class myThread extends Thread{
        private int i=5;
    
        public void a(){
            b();
        }
        public void b(){
            c();
        }
        public void c(){
            d();
        }
        public void d(){
    e();
        }
        public void e(){
            StackTraceElement[] array = Thread.currentThread().getStackTrace();
            if (array != null) {
                for (int i = 0; i < array.length; i++) {
                    StackTraceElement eachElement = array[i];
                    System.out.println("className=" + eachElement.toString());
                }
            }
        }
    
        @Override
        public void run(){
            System.out.println(Thread.currentThread().getName());
        }
    
        public static void main(String[] args) {
            myThread thread=new myThread();
            thread.a();
        }
    }
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ltL3nTp-1602422785331)(D:\App\Typora\resources\md-image\多线程\image-20200912155504217.png)]

  • getId()

    用于取得线程的唯一标识

    /*
     * Thread ID
     */
    private long tid;
    
  • yield()

    放弃当前的CPU资源,让其他任务去占用CPU的执行事件,也就是让当前的执行者放弃

    关于CPU的分配的话,其实还涉及到线程的优先级,

    在Thread类中有这个属性,其实就是线程的优先级,在Java中,线程的优先级分为1~10共10个等级,理论上优先级越高越容易被调用,但不是一定被先调用。线程的优先级还有继承性的特点会继承父类的线程优先级。

        private int            priority;
    

    还有就是线程类型,分为守护进程和非守护进程,在Thread进程中,有这个属性标识为true的为守护进程

        private boolean        daemon = false;
    

    这个是标识,

    在这里就顺带提一下线程的6种状态:

    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,
    
        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,
    
        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,
    
        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,
    
        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,
    
        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }
    
    • NEW 新建,也就是线程刚创建,为唤醒(这个是指从未启动之前)之前

    • RUNNABLE 可运行/就绪,这个状态的线程,正在等待资源,例如CPU等其他资源一到位,就可以立马就解决问题,Java系统系统中将操作系统中的就绪和运行两种状态笼统地称为“运行中”

    • BLOCKED 阻塞,一般是线程等待获取一个锁,来继续执行下一步的操作,也就是处于等待队列中

    • WAITING 等待,对应”阻塞”状态,代表此线程正处于无限期的主动等待中,直到有人唤醒它,它才会再次进入就绪状态

    • TIMED_WAITING 对应”阻塞”状态,代表此线程正处于有限期的主动等待中,要么有人唤醒它,要么等待够了一定时间之后,才会再次进入就绪状态

    • TERMINATED 终止态,线程已经执行完毕

      preview

3.线程的停止

每一个线程都有一个中断标志位(实际不存在,但是提供了接口供我们获取),表征了当前线程是否处于被中断状态,我们可以把这个标识位理解成一个boolean类型的变量

//获取中断标识位的方法,ClearInterrupted用于决定是否清除中断标志位
private native boolean isInterrupted(boolean ClearInterrupted);

首先我们要会判断我们要停止的线程是否停止了,在JDK的线程包种,提供了两个判断的方法:

1) public static boolean interrupted():测试currentThread()是否已经中断,线程的中断状态由该方法清除,所以如果连续2 次调用该方法,则第二次调用将返回 false ,也就是线程的中断位的状态由这个方法会清除(也就是再次启动该线 程)并返回结果

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

2) public boolean this.isInterrupted():测试this关键字所在类的对象是否已经中断,这里的线程主要指运行this.interrupt()方法的线程,false表示它只返回相应的中断位标识,并不做出改变。

public boolean isInterrupted() {
    return isInterrupted(false);
}

让线程停止主要有3种方法,

  • Thread.stop()

    但不推荐使用此方法,虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且是被弃用作废的,意味着在将来的Java 版本中,这个方法将不可用或不被支持。stop()方法已经是作废的方法,因为如果暴力性地强制让线程 停止,则一些清理性的工作可能得不到完成,或者数据添加不完整,造成java.lang.ThreadDeath异常

  • Thread.interrupt()

    这个方法不会终止一个调用该方法的实例的线程,还需要加入一个判断才可以完成线程的停止,interrupt()方法仅仅是在当前线程中做了一个停止的标记,并不是真正停止线程,就相当于是要拦截一辆车,我们在它要运行的到的地方提前设置停止的标识,等它到了就会停止

  • 退出标志使线程正常退出

    比如return和抛异常,也就是退出run方法(利用这2个语句,推荐后者,方便拓展性)

  • **suspend() 和 resume() **

    这2个方法分别是为了阻塞进程和唤醒进程的。用于让线程不再执行任务,线程对象并不销毁,只是再当前的代码处暂停,还可以恢复运行。

    但是这个两个方法如果使用不当会造成死锁,比如线程A使用同步代码块使线程A暂停,这时又用线程B来访问同步代码块,这样就会死锁了。同理也会造成脏读的现象。这个方法容易造成线程所占的临界区的资源被锁死,也就是因为独占所引起的问题,所以该方法一般已经被废弃了。

4.线程的异常


二、线程同步

线程同步主要是为了解决实例变量中的 变量的 使用不同步问题(因为方法中的变量一般是不会存在线程安全问题的,由于其私有的特性)

1.synchronized

方法内的变量是天生线程安全的,很容易理解,因为在JVM虚拟机内的,虚拟机栈、包括本地方法栈,都是线程私有的,所以不会被多个线程共同修改。所以我们需要解决的是实例变量的异步解决问题。为了解决该问题,我们需要做的只是需要在修改实例变量的方法前加上synchronized(实现同步方法有很多先介绍这一种)。

为什么加上这关键字就可以实现同步了呢?它其实是通过一种锁的机制,只有获取到了该线程的锁,才能调用该方法。本质上是利用了ACC_SYNCHRONIZED这关键字,它会给该方法加上需要同步的标识,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C4BrRcf0-1602422785334)(D:\App\Typora\resources\md-image\多线程\image-20201011145133156.png)]

也就是,当线程进入到monitorenter指令后,线程将会持有Monitor对象,退出monitorexit指令后,线程将会释放Monitor对象,而其他线程也想获取该方法的时候需要也获取该Monitor对象。但这个是对于修饰同步代码块来说的。在用synchronized修饰方法时,它是不会显示需要获取Monitor对象的

synchronized void test(){
}

  synchronized void test();
    descriptor: ()V
    flags: ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return//也就是这个只是直接返回,而我们的确是需要同步修饰符的
      LineNumberTable:
        line 12: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/zxc/theThread/myThread;

原因是JVM使用了ACC_SYNCHRONIZED访问标志来区分一个方法是否为同步方法;当方法被调用时,调用指令会检查该方法是否拥有ACC_SYNCHRONIZEDS标志优先(也就是可以理解为忽略了同步命令,但是还是要做那一步的)关键字synchronized取得的锁都是对象锁,但是这个对象锁是针临界资源区的方法的锁,锁的是对象。

  • synchronizaed有锁重入的功能,

    什么是锁重入:当一个线程获取到了相应的对象锁,还没释放时,该线程可以再次申请并获取该对象的锁。这为了方便支持继承的那部分

  • 方法重写和Synchronizaed

    重写后,方法如果没加Synchronizaed的话,也就不是临界资源了

  • 同步代码块

    synchronizaed修饰方法时,是将当前对象作为锁,而同步代码块可以锁任意的对象,并且可以指定范围,写法如下

    synchronized (obj.class) {
                ···
                方法
                ···
            }
    //比如println(String x),这样也是为了确保输出的东西不会断断续续的
        public void println(String x) {
            synchronized (this) {
                print(x);
                newLine();
            }
        }
    //这是锁当前对象
    synchronized (this) {
                ···
                方法
                ···
            }
    //其实同步代码块的属性不一定要对象,而是字符串也可以,可以简单理解为一种标识
    synchronized (String x) {
                ···
                方法
                ···
            }
    

    在使用同步synchronized(this)代码块时需要注意,当一个 线程访问object的一个synchronized(this)同步代码块时,其他线 程对同一个object中所有其他synchronized(this)同步代码块的访 问将被阻塞,这说明synchronized使用的对象监视器是同一个,即使用的锁是同一个。

2.锁的释放

  • 异常

    当线程的代码出现异常时,其持有的锁会自动释放

  • tips

    类Thread.java中的suspend()方法和sleep(millis)方法被调用后并不释放锁。

4.几种读取错误

  • 脏读

    它是


三、线程死锁

线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,也就是说在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2

示例:

class DealThread implements Runnable {
    private String username;
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();

    void setFlag(String username) {
        this.username = username;
    }

    @Override
    public void run() {
        if (username.equals("a")) {
            synchronized (lock1) {
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("按lock1->lock2代码顺序执行了");
                }
            }
        }
        if (username.equals("b")) {
            synchronized (lock2) {
                try {
                    System.out.println("username = " + username);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("按lock2->lock1代码顺序执行了");
                }
            }
        }
    }
}

public class myThread extends Thread {
    

    @Override
    public void run() {
        super.run();
    }

    public static void main(String[] args) {
        try {
            DealThread t1 = new DealThread();
            t1.setFlag("a");
            Thread thread1 = new Thread(t1);
            thread1.start();
            Thread.sleep(100);

            t1.setFlag("b");
            Thread thread2 = new Thread(t1);
            thread2.start();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

可以看到线程不会停止,利用JDK进程分析的工具对线程进行查看,可以看到死锁的产生:

在这里插入图片描述


四、线程通信

线程间通信本质上是A线程通过修改公有变量(能够影响B线程的因素)来将B线程结束/其他操作。

1. notify和wait

如果我们直接设置变量,来让B线程不断判断A线程是否修改该标识,读取的间隔时间过短,会很浪费CPU的时间在不断读取该变量上,并且,如果读取的间隔时间过长则可能漏过判断的条件。所以,这并不是线程之间能进行正常通信的方式。

需要注意的是:拥有相同锁的线程才可以实现wait/notify机制 ,所以其实该机制也是通过锁来实现的;Java为每个Object 都实现了wait()和notify()方法,它们必须 用在被synchronized同步的Object的临界区内

  • wait()

    wait()是Object类的方法,它的作用是使当前执行wait()方法的线程等待在wait()所在的代码行处暂停执行并释放锁,直到接到通知或被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,所以只能在同步方法或同步块中调用wait()方法。

  • notify()

    上面提到线程如何转换等待,而唤醒某个线程是需要用notify的(并使当前线程放弃相应的锁),而选择哪个线程进行唤醒又得根据wait()执行的顺序(遵循FIFO)。同理notify也只能在同步方法或同步块中调用

    需要说明的是,执行notify()方法后,当前线程不会马上 释放该锁,呈wait状态的线程也并不能马上获取该对象锁,要等 到执行notify()方法的线程将程序执行完,也就是退出synchronized —405— 同步区域后,当前线程才会释放锁

  • notifyAll ()

    这是通知所有线程唤醒,方法会按照执行wait()方法的倒序依次对其他线程进行唤醒(其实不管是notify还是notifyall唤醒的顺序具体还是取决于JVM)

  • wait(long )

    作用和wait()相同,但是是有限等待,在等待时间结束后,再获取到了锁之后,才能继续运行

  • 生产者消费者问题

    情况问题/解决办法
    一生产者、一消 费者、一容量 |可以进行同一个锁,然后加判断条件解决问题
    多生产者、多消费者、一消费者因为所有线程(包括生产者和消费者)都处于同一个锁的等待队列当中,
    所以某个线程唤醒的不一定是异类,还有可能是同类,这样的话会造成无
    限等待。而解决的办法就是唤醒所有的线程(notifyAlll()),这样确保需要的异类线程一定有存在着醒着的
    一生产者、多消费者、一容量会产生异常情况,因为当消费者1,先唤醒生产者,并waiting,然后生产者完成了生产并唤醒消费者2,消费了容量为空,此时再唤醒消费者1,从上次停下的地方继续执行就会导致没有容量可以供它消费,有异常。此时需要斟酌判断条件,因为根本原因是因为,其实消费者1被2次调用的时候,并没有满足它要调起的条件(而if只能进行一次的判断,所以需要修改为while,一直进行相应的判断)
    如果只是修改刚才的话,还是会产生假死的原因,所以还是需要进行相应的修改(和上面的多消费者、多生产者、一容量类似)
    多生产者、一消费者、一容量类比于上面的情况
    多生产者、多消费者、多容量在上面的基础上需要设置相应的额外线程进行唤醒相应类的线程

2. 字节流

上面只是介绍了线程之间如果相应的唤醒工作,而实际上线程之间不止如此,还需要各个线程之间进行数据的交互,这时就需要用到了字节流。而Java中有各式各样的字节流,能够使我们方便对数据进性操作,其中管道流是一种特殊的流,用于在不同线程间直接传送数据。线程之间读取数据都只需要从管道中读取即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nRYWS5pX-1603264780486)(D:\App\Typora\resources\md-image\多线程\未命名文件.jpg)]

3. 字符流

当然数据的传输还能通过字符流来解决同理,只不过传输的单位是受限为字符而已。

4. 类ThreadLocal

这个类是为了让每个线程 有自己的本地变量而产生的,是为了将相应的共享数据放入当前线程对象的Map中去,而ThreadLocal是作为一种Map—实例变量的连接而存在的。数据 →ThreadLocal→currentThread()→Map。执行后每个线程中的Map存有自己的数据,Map中的key存储 的是ThreadLocal对象,value就是存储的值。实际上本地变量是以一种map的形式存放在线程的本地当中的。

//这个是ThreadLocal类的静态内部类,它是相当于每个线程本地变量存取的地方
//所以它就相当于是,如果要获得一个线程的Map的值那么就要先获取到该线程的ThreadLocal才能决定获取哪个Value
//而这个对象在Thread中是以ThreadLocalMap为实例变量来获取的
static class ThreadLocalMap {
    ...
}

//这个方法是ThreadLocal的构造函数
//可以看到具体的数据是存放在table中的,而table如下
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
     table = new Entry[INITIAL_CAPACITY];//初始容量16
     int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//本ThreadLocal的hash值和1111做同或操作,算出所在数组的位置
     table[i] = new Entry(firstKey, firstValue);//放入相应的数据
     size = 1;//修改本地变量的数量
     setThreshold(INITIAL_CAPACITY);//设置resize的阈值
}

//table是另一个内部类的数组
private Entry[] table;

//Entry类如下,可以理解为设置了一种k-v键值对
static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);//调用父类的构造函数
                value = v;//将value值设置给value
            }
        }

TreadLocal相关的还有InheritableThreadLocal类,它是针对普通的线程类继承值不传递的特性而产生的,以让子线程从父线程继承值。它的具体实现:

//这是关于InheritableThreadLocal类是如何继承相应的线程类
public class InheritableThreadLocal<T> extends ThreadLocal<T> { 
	protected T childValue(T parentValue) {
        return parentValue;
    }
	//它是取到了Thread类中的实例对象
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
	
    //这是创建一个对相应线程,产生一个新的Map
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
//这是ThreadLocal类中的set方法,可以看到其中就调用了InheritableThreadLocal重写的方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

那么它是怎么做到会继承父类的相关ThreadLocalMap的呢,其实是在创建一个线程的时候,构造方法中调用了相关的init方法

//构造方法,如下,其实还有很多构造方法,但是层层下来,我发现都是调用的同一个init方法
Thread(Runnable target, AccessControlContext acc) {
    init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}


//这个是最终调用的init方法,其中重要的一个变量是inheritThreadLocals,它决定了该线程是否需要继承相关的父类
 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess();
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
     
     	//关键在这里,如果我们需要继承父类的TheadLocalMap集合的话,那么就将本子类的inheritableThreadLocals,赋值为createInheritedMap方法的返回,传入参数为当前线程的可继承的TheadLocals,但是parent=currentThread();,所以对线程来说,谁创建了它,谁就是父类。
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

        this.stackSize = stackSize;


        tid = nextThreadID();
    }

	//可以看到调用的createInheritedMap方法是新建了一个ThreadLocalMap,并传入父类的ThreadLocalMap
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

	//
	private ThreadLocalMap(ThreadLocalMap parentMap) {
      	    //获取到父类的元素数组
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);//设置当前线程的阈值
            table = new Entry[len];//创建新的Entry表

            for (int j = 0; j < len; j++) {
                //取得父类集合中的值
                Entry e = parentTable[j];
                //大致意思是依次赋值给子类线程中的集合
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }	

到此,我们已经知道了InheritableThreadLocal类创建继承父类的集合中的值,但它后续是怎么更新的呢。其实这个要分Value值的类型来讨论,只要是可变类型的值,子线程还是能感知到相应的变化的。但是修改来说是可以的,但是如果直接替换相应的值,它是感知不到的,就相当于对于可变型的变量,它是给了个指针指向那个对象,所以修改是可知的,但是不会管他被替换,因为他被替换掉在GC之前还是会存在的。

在继承的基础上,我们还可以修改得到的继承的值,通过set方法是随时都可以修改,而我们可以通过重写childValue()方法,使在创建新的线程的时候,就修改最新的值。

五、随记

1.Timer

Timer的主要作用是设置计划任务,就相当于是闹钟在指定的时间内规定你做相应的事情,所以需要有TimeTask来封装相应的任务

//这是Timer存取的任务队列,以TaskQueue对象来存储
private final TaskQueue queue = new TaskQueue();

//这是TaskQueue底层是以TimeTask的数组的形式来存储的
private TimerTask[] queue = new TimerTask[128];

//TimerTask是线程,所根本来说Timer是操作线程的集合
public abstract class TimerTask implements Runnable {
    /**
     * This object is used to control access to the TimerTask internals.
     */
    final Object lock = new Object();

一些主要方法:

  • schedule

    该方法主要用于在指定的日期执行一次某任务,

    public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        sched(task, System.currentTimeMillis()+delay, 0);//指定了任务要进行的时间,以及相应的线程任务
    }
    
        private void sched(TimerTask task, long time, long period) {
            //指定的时间不能小于0
            if (time < 0)
                throw new IllegalArgumentException("Illegal execution time.");
    
            if (Math.abs(period) > (Long.MAX_VALUE >> 1))
                period >>= 1;
    
            //这个queue指的是任务的队列,底层是用TimerTask数组
            synchronized(queue) {
                if (!thread.newTasksMayBeScheduled)
                    throw new IllegalStateException("Timer already cancelled.");
    			
                //每个任务线程都有一个final的Object对象,这里是设置一些任务线程的属性
                synchronized(task.lock) {
                    if (task.state != TimerTask.VIRGIN)
                        throw new IllegalStateException(
                            "Task already scheduled or cancelled");
                    task.nextExecutionTime = time;
                    task.period = period;
                    task.state = TimerTask.SCHEDULED;
                }
    			
                //将任务添加到任务的queue当中去
                queue.add(task);
                //如果队列的头个任务就是该任务,则将其唤醒
                if (queue.getMin() == task)
                    queue.notify();
            }
        }
    

    还需要注意的一个点就是即使Timer在构造方法的时候,还会启动一个新的线程,并且这个线程是天生设定为死循环的

       public Timer() {
            this("Timer-" + serialNumber());
        }
    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }
    
    //这个是定时器固有的私有属性,然后在run方法的内部启动该线程,
    private final TimerThread thread = new TimerThread(queue);
    
    //这是在TimerThread类的内部这样启动的
        public void run() {
            try {
                mainLoop();//这是死循环方法
            } finally {
                synchronized(queue) {
                    newTasksMayBeScheduled = false;
                    queue.clear();  
                }
            }
        }
    
    
    //死循环方法
        private void mainLoop() {
            while (true) {
                try {
                    TimerTask task;
                    boolean taskFired;
                    synchronized(queue) {
                        // Wait for queue to become non-empty
                        while (queue.isEmpty() && newTasksMayBeScheduled)//队列空并且标识也表示为空
                            queue.wait();//让当前的队列的任务让出锁
                        
                        if (queue.isEmpty())
                            break; // Queue is empty and will forever remain; die
    
                        // Queue nonempty; look at first evt and do the right thing
                        long currentTime, executionTime;
                        task = queue.getMin();
                        synchronized(task.lock) {
                            if (task.state == TimerTask.CANCELLED) {
                                queue.removeMin();
                                continue;  // No action required, poll queue again
                            }
                            currentTime = System.currentTimeMillis();
                            executionTime = task.nextExecutionTime;
                            if (taskFired = (executionTime<=currentTime)) {
                                if (task.period == 0) { // Non-repeating, remove
                                    queue.removeMin();
                                    task.state = TimerTask.EXECUTED;
                                } else { // Repeating task, reschedule
                                    queue.rescheduleMin(
                                      task.period<0 ? currentTime   - task.period
                                                    : executionTime + task.period);
                                }
                            }
                        }
                        if (!taskFired) // Task hasn't yet fired; wait
                            queue.wait(executionTime - currentTime);
                    }
                    if (taskFired)  // Task fired; run it, holding no locks
                        task.run();
                } catch(InterruptedException e) {
                }
            }
        }
    

    使用cancel方法是可以强制销毁TimerThread线程的,丢弃所有的当前已安排的任务。

2.线程组

-------------------------------------------------------------------------------------10.21 -在更

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值