天天看点

并发编程:多线程设计原理

作者:日拱一卒程序猿

一、Thread和Runnable

1、java中的线程

创建执行线程有两种方法: 扩展Thread 类。 实现Runnable 接口。

2、Java中的线程:特征和状态

1. 所有的Java 程序,不论并发与否,都有一个名为主线程的Thread 对象。执行该程序时, Java 虚拟机( JVM )将创建一个新Thread 并在该线程中执行main()方法。这是非并发应用程序中 唯一的线程,也是并发应用程序中的第一个线程。

2. Java中的线程共享应用程序中的所有资源,包括内存和打开的文件,快速而简单地共享信息。 但是必须使用同步避免数据竞争。

3. Java中的所有线程都有一个优先级,这个整数值介于Thread.MIN_PRIORITY(1)和 Thread.MAX_PRIORITY(10)之间,默认优先级是Thread.NORM_PRIORITY(5)。线程的 执行顺序并没有保证,通常,较高优先级的线程将在较低优先级的钱程之前执行。

4. 在Java 中,可以创建两种线程: 守护线程。 非守护线程。 区别在于它们如何影响程序的结束。 Java程序结束执行过程的情形: 程序执行Runtime类的exit()方法, 而且用户有权执行该方法。 应用程序的所有非守护线程均已结束执行,无论是否有正在运行的守护线程。 守护线程通常用在作为垃圾收集器或缓存管理器的应用程序中,执行辅助任务。在线程start之前调 用isDaemon()方法检查线程是否为守护线程,也可以使用setDaemon()方法将某个线程确立为守护线程。

5. Thread.States类中定义线程的状态如下: NEW:Thread对象已经创建,但是还没有开始执行。 RUNNABLE:Thread对象正在Java虚拟机中运行。 BLOCKED : Thread对象正在等待锁定。 WAITING:Thread 对象正在等待另一个线程的动作。 TIME_WAITING:Thread对象正在等待另一个线程的操作,但是有时间限制。 TERMINATED:Thread对象已经完成了执行。 getState()方法获取Thread对象的状态,可以直接更改线程的状态。 在给定时间内, 线程只能处于一个状态。这些状态是JVM使用的状态,不能映射到操作系统的线程 状态。

3、Thread类和Runnable 接口

Runnable接口只定义了一种方法:run()方法。这是每个线程的主方法。当执行start()方法启动新线 程时,它将调用run()方法。

Thread类其他常用方法: 获取和设置Thread对象信息的方法。

getId():该方法返回Thread对象的标识符。该标识符是在钱程创建时分配的一个正 整数。在线程的整个生命周期中是唯一且无法改变的。

getName()/setName():这两种方法允许你获取或设置Thread对象的名称。这个名 称是一个String对象,也可以在Thread类的构造函数中建立。

getPriority()/setPriority():你可以使用这两种方法来获取或设置Thread对象的优先 级。

isDaemon()/setDaemon():这两种方法允许你获取或建立Thread对象的守护条件。

getState():该方法返回Thread对象的状态。

interrupt():中断目标线程,给目标线程发送一个中断信号,线程被打上中断标记。

interrupted():判断目标线程是否被中断,但是将清除线程的中断标记。

isinterrupted():判断目标线程是否被中断,不会清除中断标记。

sleep(long ms):该方法将线程的执行暂停ms时间。

join():暂停线程的执行,直到调用该方法的线程执行结束为止。可以使用该方法等待另一个 Thread对象结束。

setUncaughtExceptionHandler():当线程执行出现未校验异常时,该方法用于建立未校验异 常的控制器。

currentThread():Thread类的静态方法,返回实际执行该代码的Thread对象。

4、Callable

Callable 接口是一个与Runnable 接口非常相似的接口。Callable 接口的主要特征如下。

  • 接口。有简单类型参数,与call()方法的返回类型相对应。
  • 声明了call()方法。执行器运行任务时,该方法会被执行器执行。它必须返回声明中指定类型的 对象。
  • call()方法可以抛出任何一种校验异常。可以实现自己的执行器并重载afterExecute()方法来处 理这些异常。

5、synchronized关键字

(1)锁的对象

synchronized关键字“给某个对象加锁”,示例代码:

并发编程:多线程设计原理

等价于:

并发编程:多线程设计原理

实例方法的锁加在对象myClass上;静态方法的锁加在MyClass.class上。

6、锁的本质

如果一份资源需要多个线程同时访问,需要给该资源加锁。加锁之后,可以保证同一时间只能有一个线程访问该资源。资源可以是一个变量、一个对象或一个文件等。

并发编程:多线程设计原理

锁是一个“对象”,作用如下:

1. 这个对象内部得有一个标志位(state变量),记录自己有没有被某个线程占用。最简单的情况 是这个state有0、1两个取值,0表示没有线程占用这个锁,1表示有某个线程占用了这个锁。

2. 如果这个对象被某个线程占用,记录这个线程的thread ID。

3. 这个对象维护一个thread id list,记录其他所有阻塞的、等待获取拿这个锁的线程。在当前线 程释放锁之后从这个thread id list里面取一个线程唤醒。

要访问的共享资源本身也是一个对象,例如前面的对象myClass,这两个对象可以合成一个对象。 代码就变成synchronized(this) {…},要访问的共享资源是对象a,锁加在对象a上。当然,也可以另外新建一个对象,代码变成synchronized(obj1) {…}。这个时候,访问的共享资源是对象a,而锁加在新建的对象obj1上。

资源和锁合二为一,使得在Java里面,synchronized关键字可以加在任何对象的成员上面。这意味着,这个对象既是共享资源,同时也具备“锁”的功能!

锁的实现原理:

在对象头里,有一块数据叫Mark Word。在64位机器上,Mark Word是8字节(64位)的,这64位 中有2个重要字段:锁标志位和占用该锁的thread ID。因为不同版本的JVM实现,对象头的数据结构会有各种差异。

继续阅读