Java并发(八)线程知识点

优先级高的线程分配时间片的数量要多于优先级低的线程

设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级;
而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级;
确保处理器不会被独占

线程优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会Java线程对于优先级的设定


Java线程六种状态:
在这里插入图片描述
状态变迁:
在这里插入图片描述
线程调用阻塞式 API 时,在操作系统层面,线程会转换到休眠状态;
但是在 JVM 层面,Java 线程的状态不会发生变化,也就是说 Java 线程的状态会依然保持 RUNNABLE 状态。
JVM层面并不关心操作系统调度相关的状态,因为在 JVM 看来,等待 CPU 使用权(操作系统层面此时处于可执行状态)与等待 I/O(操作系统层面此时处于休眠状态)没有区别,都是在等待某个资源,所以都归入了 RUNNABLE 状态。
而所谓的 “Java 在调用阻塞式 API 时,线程会阻塞”,指的是操作系统线程的状态,并不是 Java 线程的状态。


interrupt:通知线程,线程有机会执行一些后续操作,同时也可以无视这个通知

通知方式:异常、主动检测

  • 异常

1.当线程 A 处于 WAITING、TIMED_WAITING 状态时,如果其他线程调用线程 A 的 interrupt()方法,会使线程 A 返回到 RUNNABLE 状态,同时线程 A 的代码会触发 InterruptedException异常。
转换到 WAITING、TIMED_WAITING 状态的触发条件,都是调用了类似wait()、join()、sleep() 这样的方法,这些方法都会 throws InterruptedException 这个异常。
这个异常的触发条件就是:其他线程调用了该线程的interrupt() 方法。
2.当线程 A 处于 RUNNABLE 状态,如果其他线程调用线程 A 的 interrupt() 方法:
阻塞在java.nio.channels.InterruptibleChannel时,线程A会触发java.nio.channels.ClosedByInterruptException 异常;
阻塞在java.nio.channels.Selector时,线程A的java.nio.channels.Selector会立即返回。

  • 主动检测

如果线程处于 RUNNABLE 状态,并且没有阻塞在某个 I/O 操作上,例如中断计算圆周率的线程 A,这时就得依赖线程 A 主动检测中断状态了。
如果其他线程调用线程 A 的 interrupt() 方法,那么线程A可以通过 isInterrupted() 方法,检测是不是自己被中断了。


Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时Daemon线程中的finally块并不一定会执行;
所以构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。

线程的终止:通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅


线程通信:
wait/notify:
在这里插入图片描述

//等待方
synchronized(对象) {
    while(条件不满足) {
    	对象.wait();
    }
    对应的处理逻辑
}
//通知方
synchronized(对象) {
	改变条件
	对象.notifyAll();
}

管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于:它主要用于线程之间的数据传输,而传输的媒介为内存

4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。


join原理:

// 加锁当前线程对象,当前线程即调用了其他线程.join()的线程,当前线程会进入阻塞状态
public final synchronized void join() throws InterruptedException {
    // 条件不满足,继续等待
    while (isAlive()) {
    	wait(0);
    }
    // 条件符合,方法返回
}

当线程终止时,会调用线程自身的notifyAll()方法,会通知所有等待在该线程对象上的线程

所以join方法的原理就是调用相应线程的wait方法进行等待操作的
例如A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法
当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。


线程个数:

对于 CPU 密集型的计算场景,理论上“线程的数量 =CPU 核数”就是最合适的。
不过在工程上,线程的数量一般会设置为“CPU 核数 +1”,这样的话,当线程因为偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程可以顶上,从而保证 CPU 的利用率。

对于 I/O 密集型计算场景,最佳的线程数是与程序中 CPU 计算和 I/O 操作的耗时比相关的,我们可以总结出这样一个公式:最佳线程数 =1 +(I/O 耗时 / CPU 耗时)
我们令 R=I/O 耗时 / CPU 耗时:当线程 A 执行 IO 操作时,另外 R个线程正好执行完各自的 CPU 计算。这样 CPU 的利用率就达到了 100%。
不过上面这个公式是针对单核 CPU 的,至于多核 CPU,也很简单,只需要等比扩大就可以了
计算公式如下:最佳线程数 =CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]

以CPU 计算和 I/O 操作的耗时是 1:2为例,三个线程就合适:
在这里插入图片描述
注:针对IO密集型,公式使用:2CPU核数+1不合理:
这个公式相当于这里有个潜在估计,假设了IO消耗时间与CPU消耗时间1:1
再加一个线程用来预防其中有某个线程被阻塞,及时顶上。
而针对IO密集型,要考虑的就是IO耗时与CPU耗时之比;
这个经验公式只是针对其中1:1耗时比一种情况,不够全面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值