Java线程
参考:JavaGuide
线程
线程的实现
一个Java程序作为一个进程,运行时实际上是多线程并发的, main函数就是主线程。每个调用过start()方法,并且还未结束的java.lang.Thread类的实例就是一个线程。Java线程是Java程序的并发执行单位。
一对一线程模型✨
每个Java线程对应操作系统中的一个内核线程,充分利用多核处理器的优势。(现在Windows 和 Linux 都是这种)
JVM通过调用操作系统提供的线程库实现Java线程的创建和同步,Java线程的调度和管理由操作系统负责。
多对一线程模型
什么是用户线程?是完全在用户空间实现的线程,操作系统内核不知道它的存在。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也能够支持 规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。
多个用户线程映射到一个内核线程。线程切换非常快速,可以支持大量的用户线程。但是无法利用多核处理器,阻塞操作会导致整个进程挂起。
Java早期采用这种模式。
多对多线程模型
多个用户线程映射到少量内核线程。实现较复杂。goroutine采用这种模型。
创建线程
严格意义上讲,Java创建新线程的方式只有start()方法。
继承Thread类
override Thread类的run()方法即可。
|
|
实现 Runnable 接口
需要把我的实现的类对象作为参数传递给 Thread 的创建函数。
|
|
Runnable接口更灵活,可以更好地实现资源共享(多个线程可以共享同一个Runnable对象, 也更好地和线程池的高级 API 配合。
FutureTask
可以返回任务的执行结果。
|
|
可以直接调用Thread类的run()方法吗?
如果新建了一个线程,然后在主函数里执行thread.run(),实际上还是主线程(main方法的线程)来执行run()方法里的逻辑。
如果执行thread.start(),则是开启一个新线程,让它处于RUNNABLE状态,然后把run()里的逻辑交给新线程去执行。
线程的生命周期与状态
Java线程一共有六种状态。
- NEW, 线程被创建后的初始状态
- RUNNABLE,线程在运行 start()以后的状态,包含OS线程的READY和RUNNING状态。即要么在等待OS调度它执行,要么正在运行。
- WAITING,线程不会被CPU调度,直到它被其它线程显式唤醒。
- TIMED WAITING,线程不会被CPU调度,一段时间后会被系统唤醒。
- BLOCKED,线程没有获取锁而被阻塞,直到其它线程释放锁,JVM会随机挑选/按顺序选择一个被该锁BLOCKED的线程赋予锁。
- TERMINATE,线程已经结束执行。
为什么不区分READY 和 RUNNING? 因为time slice切换得很快,可能一个线程执行很短的时间,就schedule到其它线程运行了。
sleep()和wait()
sleep()
Thread.sleep(2000); //让该线程睡眠 2 秒
sleep()是 Thread 类的静态native方法,让线程从 RUNNABLE 状态变成 TIMED WAITING 状态。这个操作不会释放锁。
wait()
wait()是 Object 类的实例 native 方法,必须在 synchronized 代码块/方法中调用,会释放锁。
|
|
wait()方法会让一个线程从RUNNABLE状态变成WAITING状态,这个线程会进入该对象的waiting set(等待队列)中。
另一个线程对同一个lock执行lock.notify()操作,那么随机唤醒该lock的等待队列中的一个线程,并把它加入entry set(同步队列)中,状态从(TIMED)WAIT变成BLOCKED。
同步队列:当线程想要去拿synchronized锁,却没有拿到的时候,它把自己加入该锁的同步队列中,状态被设置为BLOCKED。等锁释放的时候,JVM会随机(unfair)选择一个线程赋予锁。
wait()必须是instance method,因为释放锁这个操作实际上是让当前线程释放对象锁,操作的是锁那个对象。
多线程
Java线程调度
调度方式分为协同式(Cooperative Threads-Scheduling)和抢占式(Preemptive Threads-Scheduling。
协同式调度:线程的执行时间由线程本身控制。当线程执行完工作之后,主动通知系统切换到另一个线程上。优点是不会有同步性问题,缺点是可能某个线程的工作运行太久,阻塞其它线程。
抢占式调度:线程的执行时间是系统可控的。
Java语言提供了10个线程优先级,如果有两个线程都处于RUNNABLE状态,那么优先级高的线程会被先调度。由于Java线程是one-to-one映射到内核线程的,Java优先级部分对应内核线程的优先级。
单核CPU运行多线程
线程分为CPU密集型和IO密集型。对于CPU密集型线程,频繁的上下文切换会降低执行效率;对于IO密集型线程,由于在等待IO的时候切换到其它线程,它们可以做别的事,所以这样效率是合理的。
死锁
死锁产生的条件:mutual exclusion, hold and wait, no preemption, circular wait
|
|
运行jconsole,选想要查看的进程,点进去看它的线程