无锡华庄行业网站建设,北京好的设计公司有哪些,紫搜科技建站,宣传软文是什么意思第1章 多线程基础
1.1.2 线程与进程的关系
进程可以看成是线程的容器#xff0c;而线程又可以看成是进程中的执行路径。
1.2 多线程启动
线程有两种启动方式#xff1a;实现Runnable接口#xff1b;继承Thread类并重写run()方法。
执行进程中的任务时才会产生线程而线程又可以看成是进程中的执行路径。
1.2 多线程启动
线程有两种启动方式实现Runnable接口继承Thread类并重写run()方法。
执行进程中的任务时才会产生线程因此需要一种描述任务的方式这可以由Runnable接口来提供。要想定义任务需要实现Runnable接口并且重写run()方法然后再将Runnable的实现对象作为参数传递给Thread类。
调用Thread类的start()方法启动线程向CPU发出请求去执行任务。
还可以采用继承Thread类并且重写run()方法然后调用start()启动线程。
通常情况下实现Runnable接口然后启动线程是一个更好的选择这可以提高程序的灵活性和扩展性并且用Runnable接口描述任务也更容易理解。
1.2.1 线程标识
Thread类用于管理线程如设置线程优先级、设置Daemon属性、读取线程名字和ID、启动线程任务、暂停线程任务、中断线程等。
为了管理线程每个线程在启动后都会生成一个唯一的标识符并且在其生命周期内保持不变。当线程被终止时该线程ID可以被重用。而线程的名字更加直观但是不具有唯一性。
1.2.2 Thread与Runnable
Runnable接口表示线程要执行的任务。当Runnable中的run()方法执行时表示线程在激活状态run()方法一旦执行完毕即表示任务完成则线程将被停止。
Thread类默认实现了Runnable接口并且其构造方法的重载形式允许传入Runnable接口对象作为任务。
通过Thread类的源代码可以发现线程的两种启动方式其本质都是实现Thread类中的run()方法。而实现Runnable接口然后传递给Thread类的方式比Thread子类重写run()方法更加灵活。
1.2.3 run()与start()
调用Thread对象的start()方法使线程对象开始执行任务这会触发Java虚拟机调用当前线程对象的run()方法。调用start()方法后将导致两个线程并发运行一个是调用start()方法的当前线程另外一个是执行run()方法的线程。
如果重复调用start()方法这是一个非法操作它不会产生更多的线程反而会导致IllegalThreadStateException异常。
1.2.4 Thread源码分析
创建Thread类实例首先会执行registerNatives()方法它在静态代码块中加载。线程的启动、运行、生命期管理和调度等都高度依赖于操作系统Java本身并不具备与底层操作系统交互的能力。因此线程的底层操作都使用了native方法registerNatives()就是用C语言编写的底层线程注册方法。
无论通过Thread类的哪种构造方法去创建线程都需要首先调用init()方法初始化线程环境
在init()方法中做了如下操作
1设置线程名称。
2将新线程的父线程设置为当前线程。
3获取系统的安全管理SecurityManager并获得线程组。SecurityManager在Java中被用来检查应用程序是否能访问一些受限资源如文件、套接字socket等。它可以用在那些具有高安全性要求的应用程序中。
4获取线程组的权限检查。
5在线程组中增加未启动的线程数量。
6设置新线程的属性包括守护线程属性默认继承父线程、优先级默认继承父线程、堆栈大小如果为0则默认由JVM分配、线程组、线程安全控制上下文一种Java安全模式设置访问控制权限等。
1.3 线程状态
Java中的线程存在6种状态分别是NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。我们可以通过Thread类中的Thread.getState()方法获取线程在某个时期的线程状态。在给定的时间点线程只能处于一种状态。
1.3.1 NEW状态
NEW代表着线程新建状态一个已创建但是未起动start的线程处于NEW状态。
1.3.2 RUNNABLE状态
RUNNABLE状态表示一个线程正在Java虚拟机中运行但是这个线程是否获得了处理器分配资源并不确定。调用Thread的start()方法后线程从NEW状态切换到了RUNNABLE状态。
1.3.3 BLOCKED状态
BLOCKED为阻塞状态表示当前线程正在阻塞等待获得监视器锁。当一个线程要访问被其他线程synchronized锁定的资源时当前线程需要阻塞等待。
1.3.4 WAITING状态
WAITING表示线程处于等待状态。
在当前线程中调用如下方法之一时会使当前线程进入等待状态
Object类的wait()方法没有超时设置
Thread类的join()方法没有超时设置
LockSupport类的park()方法。
处于等待状态的线程正在等待另外一个线程去完成某个特殊操作。例如在某个线程中调用了Object对象的wait()方法它会进入等待状态等待Object对象调用notify()或notifyAll()方法。一个线程对象调用了join()方法则会等待指定的线程终止任务。
1.3.5 TIMED_WAITING状态
TIMED_WAITING表示线程处于定时等待状态。
在当前线程中调用如下方法之一时使当前线程进入定时等待状态
Object类的wait()方法有超时设置
Thread类的join()方法有超时设置
Thread类的sleep()方法有超时设置
LockSupport类的parkNanos ()方法
LockSupport类的parkUntil()方法。
1.3.6 WAITING与BLOCKED的区别
WAITING、TIMED_WAITING、BLOCKED这几个线程状态都会使当前线程处于停顿状态因此容易混淆。下面简单总结一下这些状态之间的区别
1Thread.sleep()不会释放占有的对象锁因此会持续占用CPU。
2Object.wait()会释放占有的对象锁不会占用CPU。
3BLOCKED使当前线程进入阻塞后为了抢占对象监视器锁一般操作系统都会给这个线程持续的CPU使用权。
4LockSupport.park()底层调用UNSAFE.park()方法实现它没有使用对象监视器锁不会占用CPU。
1.3.7 TERMINATED状态
TERMINATED表示线程为完结状态。当线程完成其run()方法中的任务或者因为某些未知的异常而强制中断时线程状态变为TERMINATED。
1.3.8 线程状态转换
前面我们学习了Java线程的6种状态接下来通过图1-2对线程状态转换进行汇总。在图1-2中把线程RUNNABLE状态细分为两种runnable准备就绪和running运行中。runnable表示线程刚刚被JVM启动还没有获得CPU的使用权running表示线程获得了CPU的使用权正在运行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M6gT7bvG-1678243017014)(null)]
1.4 sleep()与yield()
1.4.1 线程休眠sleep()
Thread类的sleep()方法使当前正在执行的线程以指定的毫秒数暂时停止执行具体停止时间取决于系统定时器和调度程序的精度和准确性。当前线程状态由RUNNABLE切换到TIMED_WAITING。调用sleep()方法不会使线程丢失任何监视器所有权因此当前线程仍然占用CPU分片。
调用sleep()方法可能会抛出InterruptedException异常它应该在run()方法中被捕获因为异常无法传递到其他线程如主线程就无法捕获子线程抛出的异常。
Java SE5引入了更加显式的sleep()版本作为TimeUit类的一部分。例如TimeUnit.MILLISECONDS.sleep(1000)等价于Thread.sleep(1000)表示休眠1秒。TimeUnit类提供了更好的可读性。
1.4.2 线程让步yield()
Thread类的yield()方法对线程调度器发出一个暗示即当前线程愿意让出正在使用的处理器。调度程序可以响应暗示请求也可以自由地忽略这个提示。如图1-2所示线程调用yield()方法后可能从running状态转为runnable状态。
需要强调的是**yield()仅仅是一个暗示没有任何机制保证它一定会被采纳。**线程调度器是Java线程机制的底层对象可以把CPU的使用权从一个线程转移到另外一个线程。如果你的计算机是多核处理器那么分配线程到不同的处理器执行任务要依赖线程调度器。
1.5 线程优先级
每个线程都有优先级。具有较高优先级的线程可能优先获得CPU的使用权。创建一个新的Thread对象时新线程的优先级默认与创建线程的优先级一致。
JDK中实际上存在着10个优先级但是这与大多数操作系统不能建立很好的映射关系。比如Windows有7个线程优先级设置而Sun的Solaris只有两个线程优先级因此在Java中一般只使用下面的三种优先级设置。 不应该过分依赖于线程优先级的设置理论上线程优先级高的会优先执行但实际情况可能并不明确。例如线程调度机制还没有来得及介入时线程可能就已经执行完了。所以优先级具有一定的“随机性”。
1.5.1 线程优先级与资源竞争
具有较高优先级的线程会优先得到调度系统资源分配。也就是说优先级高的线程可以优先竞争共享资源。但线程的优先级调度和底层操作系统有密切的关系在各个平台上表现不一并且无法精准控制。因此在要求严格的场合需要开发者在应用层解决线程调度问题。
当调用Thread.yield()方法时会给线程调度器一个暗示即优先级高的其他线程或相同优先级的其他线程都可以优先获得CPU分片。
1.6 守护线程
1.6.1 守护线程的概念
在Java线程中有两种线程一种是用户线程另一种是守护线程Daemon。
所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程。比如垃圾回收线程就是一个很称职的守护者当一个对象不再被引用的时候内存回收它占领的空间以便空间被后来的新对象使用。
Daemon线程与用户线程在使用时没有任何区别唯一的不同是当所有用户线程结束时程序也会终止Java虚拟机不管是否存在守护线程都会退出。
调用Thread对象的setDaemon()方法可以把用户线程标记为守护者。调用isDaemon()方法可以判断线程是否是一个守护线程。
空间以便空间被后来的新对象使用。
Daemon线程与用户线程在使用时没有任何区别唯一的不同是当所有用户线程结束时程序也会终止Java虚拟机不管是否存在守护线程都会退出。
调用Thread对象的setDaemon()方法可以把用户线程标记为守护者。调用isDaemon()方法可以判断线程是否是一个守护线程。