IT学习笔记--Java高并发编程

1. 线程的介绍

对计算机来说,每一个任务就是一个进程,在每一个进程内部至少要有一个线程是在运行中,有时线程也称为轻量级的进程。

线程是程序执行的一个路径,每一个线程都有自己的局部变量表、程序计数器(指向正在执行的指令指针)以及各自的生命周期。

线程启动必须在其中一个任务之前,否则线程将永远得不到启动,因为前一个任务永远不会结束。

只用调用了Thread的start方法,才代表派生了一个新的线程,否则与普通的Java对象没有任何区别。

2.线程的生命周期

主要分为以下5个主要的阶段:

(1)NEW(初始状态)

用关键字new新创建了一个线程对象,但还没有调用start()方法。

(2)RUNNABLE(可执行状态)

线程对象进入RUNNABLE状态必须调用start方法,可执行状态说明它具备执行的资格,但是并没有真正的执行起来而是等待CPU的调度。

RUNNABLE状态的线程只能意外终止或者进入RUNNING状态。

(3)RUNNING(运行状态)

一个RUNNING状态的线程事实上也是RUNNABLE的,但是反过来不成立。

在该状态中,线程的状态可以发生如下的状态转换:

  • 直接进入TERMINATED状态,比如调用了stop
  • 进入BLOCKED状态,比如调用了sleep
  • 进行某个阻塞的IO操作
  • 获取某个锁资源,从而加入到该锁的阻塞队列中而进入了BLOCKED状态
  • 由于CPU的调度器轮询使该线程放弃执行,进入RUNNABLE状态
  • 线程主动调用yield方法,放弃CPU执行权,进入RUNNABLE状态

(4)BLOCKED(阻塞状态)

在该状态的线程可以切换到如下几个状态:

  • 直接进入TERMINATED状态,比如调用了stop
  • 线程阻塞的操作结束
  • 线程完成了指定时间的休眠,进入到了RUNNABLE状态
  • Wait中的线程被其他线程notify/notifyall唤醒,进入RUNNABLE状态
  • 线程获取到了某个锁资源进入RUNNABLE状态
  • 线程在阻塞过程中被打断,比如其他线程调用了interrupt方法,进入RUNNABLE状态

(5)TERMINATED(终止状态)

TERMINATED是一个线程的最终状态,在该状态中线程将不会切换到其他任何状态,也意味着该线程的整个生命周期都结束了。以下情况将会使线程进入TERMINATED状态:

  • 线程运行正常结束,结束生命周期
  • 线程运行出错意外结束
  • JVM Crash,导致所有的线程都结束

3. 几点结论:

  • 一旦线程启动,线程的名字将不再被修改。
  • 一个线程的创建肯定是由另外一个线程完成的;被创建线程的父线程是创建它的线程。
  • main线程所在的ThreadGroup称为main。
  • 构造一个线程的时候如果没有显式地指定ThreadGroup,那么它将会和父线程同属于一个ThreadGroup。
  • 栈内存划分的大小将直接决定在一个JVM进程中可以创建多少个线程;线程的创建数量随着虚拟机栈内存的增多而减少
  • java进程的内存大小为:堆内存+线程数量*栈内存;堆内存作为影响进程内存的基数,它的增大对线程数量的影响也是反比关系,但是并没有栈内存那样明显。

4.守护线程

它是一类比较特殊的线程,一般用于处理一些后台的工作,比如JDK的垃圾回收线程。

在正常情况下,若JVM中没有一个非守护线程,则JVM的进程会退出。

设置守护线程的方法很简单,调用setDaemon方法即可,true代表守护线程,false代表正常线程;setDaemon方法只在线程启动之前才能生效,如果一个线程已经死亡,那么再设置setDaemon则会跑出IllegalThreadStateException异常。

守护线程的作用:

具备自动结束生命周期的特性。当你希望关闭某些线程的时候,或者退出JVM进程的时候,一些线程能够自动关闭,此时就可以考虑用守护线程为你完成这样的工作。

5. 线程sleep

该方法会使当前线程进入指定毫秒数的休眠,暂停执行。休眠有一个重要的特性,那就是其不会放弃monitor所的所有权。

在JDK1.5以后,引入了一个枚举TimeUnit,可以使用它来替代Thread.sleep。因为sleep能做的事,TimeUnit全部都能完成,并且功能更加强大。如下:

IT学习笔记--Java高并发编程_第1张图片

6.线程yield

yield方法属于一种启发式的方法,其会提醒调度器我愿意放弃当前的CPU资源,如果CPU的资源不紧张,则会忽略这种提醒。

调用yield方法会使当前线程从RUNNING状态切换到RUNNABLE状态,一般这个方法不太常用。

yield与sleep的区别:

  • sleep会导致当前线程暂停指定的时间,没有CPU时间片的消耗
  • yield只是对CPU调度器的一个提示,如果CPU调度器没有忽略这个提示,它会导致线程上下文的切换
  • sleep会使线程短暂block,会在给定的时间内释放CPU资源
  • yield会使RUNNING状态的Thread进入RUNNABLE状态(如果CPU调度器没有忽略这个提示)
  • sleep几乎百分之百地完成了给定时间的休眠,而yield的提示并不能一定担保
  • 一个线程sleep另一个线程调用interrupt会捕获到中断信号,而yield则不会

7.设置线程的优先级

理论上是优先级比较高的线程会获取优先被CPU调度的机会,但事实上往往并不会如你所愿,设置线程的优先级同样也是一个提示操作,具体如下:

  • 对于root用户,它会提示操作系统你想要设置的优先级别,否则它会被忽略
  • 如果CPU比较忙,设置优先级别可能会获得更多的CPU时间片,但是闲时优先级的高低几乎不会有任何作用。

线程的优先级不能小于1也不能大于10,如果指定的线程优先级大于线程所在group的优先级,那么指定的优先级会失效,取而代之的是group的最大优先级。如下:

       //定义一个线程组
        ThreadGroup group = new ThreadGroup("test");
        //将线程组的优先级指定为7
        group.setMaxPriority(7);
        //定义一个线程,将该线程加入到group中
        Thread thread = new Thread(group,"test-thread");
        //企图将线程的优先级设定为10
        thread.setPriority(10);
        //企图未遂
        System.out.println(thread.getPriority());//输出的是7

借助优先级设定某个任务的权重,这种方式是不可取的,一般定义线程的时候使用默认的优先级就好,线程默认的优先级和它的父类保持一致,一般情况下都是5.

8.线程interrupt

如下方法的调用会使得当前线程进入阻塞状态,而调用当前线程的interrupt方法,就会打断阻塞。

IT学习笔记--Java高并发编程_第2张图片

打断一个线程并不等于该线程的生命周期结束,仅仅是打断了当前线程的阻塞状态;一旦线程在阻塞的情况下被打断,都会抛出一个InterruptedException异常,这个异常就像一个信号通知当前线程被打断了。

(1)isInterrupted

是Thread的一个成员方法,主要判断当前线程是否被中断,该方法仅是对interrupt标识的一个判断,并不会影响标识发生任何改变。

在run方法中使用了sleep这个可中断方法,它会捕获到中断信号,并且会擦除interrupt标识。

(2)interrupted

interrupted是一个静态方法,也用于判断当前线程是否被中断,调用该方法会直接擦除掉线程的interrupt标识。如果当前线程被打断了,那么第一次调用interrupted方法会返回true,并且立即擦除了interrupt标识,第二次包括以后的调用永远都会返回false,除非此期间线程又一次地被打断。

如果一个线程设置了interrupt标识,那么接下来的可中断方法会立即中断。

9.线程join

与sleep一样也是一个可中断的方法。join某个线程A,会使当前线程B进入等待,直到线程A结束生命周期,或者到达给定的时间,那么在此期间B线程是处于BLOCKED的,而不是A线程。

join方法会使当前线程永远地等待下去,直到期间被另外的线程中断,或者join的线程执行结束。

10.补充:

  • 基本类型(如int、chart等)、引用类型(如对象等)的赋值和引用是原子操作
  • 但long和double的赋值和引用是非原子操作
  • long或double在线程间共享时,需要将其放入syncharonized中操作,或者声明为volatile

 

 

 

 

 

你可能感兴趣的:(Java高并发编程)