JDK8 之线程Thread小记

一、线程的实现

两种方式: 
   1.直接实现Thread类 
   2.继承Runnable接口 
   上面两种方法都要实现run()方法,最终都会生成一个Thread对像;一般推荐使用第二种办法,因为线程只是实现了Runnable接口,还可以继承其他类。很适合多个相同线程处理同一份资源,能很好的将cpu、代码和数据分开,形成清晰模型,很好的体现了面向对象编程的思想.

 //线程初始化
 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        //必须指定线程名称,具体规则可以看源码
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        //父线程,即当前的“主线程”
        Thread parent = currentThread();
        //获取安全管理器,不是我们本期的重点
        SecurityManager security = System.getSecurityManager();
        //线程组为空时,会有一个默认获取线程组的策略
        //可以说每一个线程都属于一个线程组
        if (g == null) {
              //确认它是否为applet应用

             //如果存在安全管理器,则获取它重写的线程组
            if (security != null) {
                g = security.getThreadGroup();
            }

            //如果安全性不强,使用父线程组
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        //线程组中未启动+1
        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;
        //设置优先级:当大于‘线程组’的maxPriority时,取maxPriority
        setPriority(priority);
        //初始化用于继承父线程的inheritableThreadLocals
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        //设置堆栈大小:用于请求虚拟机分配的堆栈的大小,不过一般虚拟机不会看它的
        this.stackSize = stackSize;

        //设置线程ID
        tid = nextThreadID();
    }

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

   关于inheritableThreadLocals和ThreadLocals的区别,通过源码可以看到,父线程的inheritableThreadLocals的内容会被子线程复制一份继承。而ThreadLocals是不会这样做的。

二、 线程的状态及属性

这里写图片描述

1. 线程状态:

   1) 新建(new):线程被创建后的初始状态 
   2) 就绪(runnable):调用start()方法,进入“就绪”状态 
   3) 运行(running):进入“就绪”状态后,获取到cpu分配时间进入“运行状态” 
   4) 阻塞/等待(blocked/*waiting): 
      a.线程需要等待某个条件时,而条件未成熟时,等待调度器通知,进入‘等待’状态 
     b.获取某个资源时,资源被加锁,等待资源被释放,进入‘阻塞’状态 
   5) 死亡(terminated):run()执行完毕或因异常导致退出,线程死亡

2. 线程重要属性:

1) 优先级

   在Java中,每个线程都会拥有一个默认的优先级(5),如果未定义,则会继承父类的优先级,也通过setPriority(int)设置(1-10之间)。 
   注意:优先级高的并不一定会先执行,cpu尽量将资源分配给优先级高的,即优先级高的执行几率要大很多,但并不是等优先级高执行完后才开始执行优先级低的,具有一定的随机性。

2) 守护线程

  在java中线程一般分为用户线程user和守护线程Daemon。它们在其他方面基本相同,但唯一的区别在于虚拟器中只剩下守护线程时,就可以退出了。 
  守护线程是一种比较低级别的线程,一般用于为其他类别线程提供服务,因此当其他线程都退出时,它也就没有存在的必要了,例如jvm中的垃圾回收线程。可以通过setDaemon(boolean) 设置线程的Daemon模式。

三、 线程涉及的常用方法

1. start方法

   线程对象被创建后,通过start方法启动,此时线程会被放置到等待队列中且状态由‘新建’进入‘就绪’,等待调度器分配资源,随时进入到‘运行’状态。

 public synchronized void start() {
       //防止线程重复启动,线程必须为‘新建’状态
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

         //添加到线程组(方便统一管理维护)
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                  //启动失败,从group中删除
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
              //do nothing. If start0 threw a Throwable
              // then  it will be passed up the call stack
            }
        }
    }
 //native方法,启动线程  
 private native void start0();
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

2. run方法

   线程被启动后,每当获取到资源,jvm就会去调用run方法,run执行结束,则线程结束。run只是一个普通的方法,存放业务逻辑。

3. wait - notify - notifyAll方法

  这三个方法为java.lang.Object类为线程提供的用于实现线程间通信的同步控制方法。因此,可以说每一个对象实例都存在一个等待队列。

 //源码中,可以看到wait的使用方法推荐如下
  synchronized (obj) {
              while (condition does not hold){
                obj.wait();
              }
              .....     
         }
 //使用wait时,必须先持有当前对象的锁,否则会抛错InterruptedException
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

   1) obj.wait() : 相当于wait(0),线程释放obj对象的monitor(对象监视器),即交出互斥锁,变为“等待”状态,进入‘等待队列’,必须由通信线程唤醒。 
   2)wait(M>0) :线程交出互斥锁后被挂起,变为“等待”状态,进入‘等待队列’,必须由通信线程唤醒或至多等待M毫秒后唤醒自身。 
   3)notify() :随机唤醒阻塞在obj对象上的一个线程,退出“等待状态”,然后线程再尝试获取互斥锁,获取成功则继续执行,失败则进入‘阻塞队列’中。 
   4)notifyAll():唤醒所有阻塞在obj对象上的线程,即所有线程都退出“等待状态”,一起竞争再尝试获取互斥锁,获取成功则继续执行,其他失败则进入‘阻塞队列’中。

4. join方法

join方法本质上还是利用wait的来实现,t.join()即为当前主线程释放了t对象的monitor,进入到t对象阻塞队列中,巧妙的将t线程的执行达到了同步执行的效果。 
   1)t.join(M=0):和t.join()效果相同。主线程到此处时进入阻塞状态,直到thread执行完毕,才会继续执行。 
   2)t.join(M>0): 主线程到此处时进入阻塞状态,直到thread执行完毕或至多等待M毫秒后,才会继续执行。

 //名词解释:例如 main(){ ***;  t.join();  ****; }
 //当前‘主线程’ 指的是当前执行t.join()的main主线程
 //当前‘线程’指的就是t
 public final synchronized void join(long millis)
    throws InterruptedException {
        //获取当前时间
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        //mills等于0时,当前‘主线程’对象存活时,会一直阻塞,直到当前‘线程’run执行完毕或抛出异常终止
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                 //当前‘主线程’阻塞millis毫秒
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

   //线程真正退出之前,系统会调用当前方法,给它一个清理的机会
   private void exit() {
        if (group != null) {
            //通知线程组,该线程已经死亡
            //满足某些条件下,该处会执行执行this.notifyAll()方法
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }


   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

  通过以上可以看到,一般最好不要主动对Thread对象使用t.wait(), t.notify() ,t.notifyAll() 方法,否则可能会引起一些不必要问题。

5. park - parkNanons - unpark方法

   LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。我们在阅读java锁和同步队列器AbstractQueuedSynchronizer中可以看到,就是通过LockSupport.park()和LockSupport.unpark(thread)来阻塞和唤醒的。

 //需要得到“许可”才能继续,否则进入waiting状态
 public static void park() {
        UNSAFE.park(false, 0L);
    }
 //blocker  记录线程被阻塞时,被谁阻塞的
 //deadline 阻塞多少时间
  public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }
  //给当前线程,释放一个“许可”,唤醒线程
  public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }


   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

另外这里要重点分析一下 park/unpark和wait/notify 方法的的区别为: 
1)park/unpark 针对的是一个“许可(permit)”的授权,unpark可以先执行,当另一个线程执行到park时,发现已经被“许可”了,则继续执行。注意:这个“许可”是不能叠加的,“许可”是一次性的。即便先多次执行unpark,也只相当于一次。 
2)线程B要用notify通知线程A,那么线程B要确保线程A已经在wait调用上等待了,否则线程A可能永远都在等待 
    park/unpark模型真正解耦了线程之间的同步,线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态。

6. interrupt方法

     一般来说,当线程调用thread.sleep()、obj.wait()、thread.join()处于阻塞状态时,此时再调用thread.interrupt(), thread会立即抛出InterruptedException异常,并清除“interrupt”状态。 
     而对于locksuport.park()方法来说,它会响应thread.interrupt()方法提前退出阻塞,但不会抛出InterruptedException异常及清除“interrupt”状态,使用者可自行对“interrupt”状态进行处理。 
   1)interrupt() 设置线程的状态为“中断”状态 
   2)interrupted() 返回线程是否“中断”状态,并立即重置状态 
   3)isInterrupted 线程是否为“中断”状态

   public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
           //关于这一段代码具体做了什么有兴趣可以研究下
            Interruptible b = blocker;
            if (b != null) {
                //仅设置一个中断标志   
                interrupt0();        
                b.interrupt(this);
                return;
            }
        }
        //仅设置一个中断标志   
        interrupt0();
    }

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

7. sleep方法

    执行当前方法后,当前线程暂停指定毫秒数,进入‘等待’状态,这段时间内cpu不会再执行它。注意的是,它并不会交出互斥锁。

//native方法
 public static native void sleep(long millis) throws InterruptedException;
   
   
  • 1
  • 2
  • 1
  • 2

8. yeid方法

    执行当前方法后,线程从‘运行’状态变为‘就绪’状态,同时立马和其他线程一起竞争CPU,有可能还是它自身继续执行。当然它也不会交出互斥锁。

//native方法
 public static native void yield();
   
   
  • 1
  • 2
  • 1
  • 2



http://blog.csdn.net/lh513828570/article/details/60144598

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值