Java并发实现原理笔记-多线程基础

Scroll Down

线程的优雅关闭

stop()与destory()函数

线程是一段运行中的代码,或者说是一个运行中的函数。既然是在运行中,就存在一个最基本的问题:运行到一半的线程能否强制杀死?
答案是肯定不能。Java中,有stop()、destory()之类的函数,但这些函数都是官方明确不建议使用的。原因很简单,如果强制杀死线程,则线程中所使用的资源,例如文件描述符、网络连接等都不能正常关闭。
因此,一个线程一旦运行起来,就不要去强行打断它,合理的关闭办法是让其运行完(也就是函数执行完毕),干净地释放掉所有资源,然后退出。如果是一个不断循环运行的线程,就需要用到线程间的通信机制,让主线程通知退出。

轻量级阻塞与重量级阻塞

能够被中断的阻塞称为轻量级阻塞,对应的线程状态是WAITING或者TIMED_WAITING;而像synchronized这种不能被中断的阻塞称为重量级阻塞,对应的状态是BLOCKED。
初始线程处于NEW状态,调用start()之后开始执行,进入RUNNING或者READY状态,如果没有调用任何的阻塞函数,线程只会在RUNNING和READY之间切换,也就是系统的时间片调度。这两种状态的切换是操作系统完成的,开发者基本没有机会介入,除了可以调用yield()函数,放弃对CPU的占用。
一旦调用了任何阻塞函数,线程就会进入WAITING或者TIMED_WAITING状态,两者的区别只是前者为无限期阻塞,后者则传入了一个时间参数,阻塞一个有限的时间。如果使用了synchronized关键字或者synchronized块,则会进入BLOCKED状态。
除了常用的阻塞\唤醒函数,还有一个不太常见的阻塞\唤醒函数,LockSupport.park()/unpark()。这对函数非常关键,Concurrent包中Lock的实现即依赖这一对操作原语。
故而t.interrupted()的精确含义是唤醒轻量级阻塞,而不是字面意思中断一个线程。

t.isInterrupted()与Thread.interrupted()的区别

因为t.interrupted()相当于给线程发送了一个唤醒的信号,所以如果线程此时恰好于WAITING或者TIMED_WAITING状态,就会抛出一个InterruptedException并且线程被唤醒。而如果线程此时并没有被阻塞,则线程什么都不做。但在后续,线程可以判断自己是否收到过其他线程发来的中断信号,然后做一些对应的处理。
这两个函数都是线程用来判断自己是否收到过中断信号的,前者是非静态函数,后者是静态函数,两者的区别在于,前者只是读取中断状态,不修改状态;后者不仅读取中断状态,还会重置中断标志位。

synchronized关键字

锁的对象是什么

对于非静态成员函数,锁其实是加在对象a上面的;对于静态成员函数,锁是加在A.class上面的,当然,class本身也是对象。
synchronized的常见问题,一个静态成员函数和一个非静态成员函数,都加了synchronized关键字,分别被两个线程调用,它们是否互斥?很显然,因为是两把不同的锁,所以不会互斥。

锁的本质是什么

无论是什么编程语言,只要是多线程的,就一定会涉及锁。既然锁如此常见,那么锁的本质到底是什么呢?
多个线程要访问同一个资源,线程就是一段段运行的代码;资源就是一个变量、一个对象或者是一个文件等;而锁就是要实现线程对资源的访问控制,保证同一时间只能有一个线程去访问某一个资源。
锁其实就是一个对象,这个对象要完成以下几件事情:
1、这个对象内部得有一个标志位(state变量),记录自己有没有被某个线程占用。最简单的情况是这个state有0,1两个取值,0表示没有线程占用这个锁,1表示有某个线程占用了这个锁。
2、如果这个对象被某个线程占用,它得记录这个线程的Thread ID,知道自己是被哪个线程占用了。
3、这个对象还得维护一个thread id list,记录其他所有阻塞的、等待拿这个锁的线程。在当前线程释放锁之后从这个Thread id list里面取一个线程被唤醒。

synchronized实现原理

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