并发笔记(三)线程的生命周期、合理设置线程数量、局部变量的安全性

一.线程的生命周期

1.1 java线程6中状态
1.NEW (初始化状态)

编译语言中的创建线程,此时操作系统中的线程没有对应创建

2.RUNNABLE(可运行/运行状态)

对应操作系统中的线程被创建,具有CPU使用权

3.BLOCK(阻塞状态)4.WAITING(无时限等待状态)5.TIMED_WAITING(有时限等待状态)

这三种状态可以统一看成线程休眠状态,就是导致线程休眠状态的原因不同。当线程处于休眠状态,会释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。

6.TERMNATED(中止状态)

线程中止状态不会切换到其他状态,意味着线程执行结束。

1.2线程状态切换
1.NEW ==> RUNNABLE

Java中创建Thread之后调用start方法。

2.RUNNABLE==>BLOCKED

争抢锁资源的时候,当前线程获取锁资源之后,其他线程就会进入阻塞(BLOCKED)

3.RUNNABLE==>WAITING

a)获取锁资源的线程调用wait方法,等待其他线程的唤醒,转换会RUNNABLE
b)Thread.join(),当前线程中调用t1.join方法,当前线程状态转为WAITING,t1执行完之后当前线程转为RUNNABLE。
c) LockSupport.part(),RUNNABLE转为WAITING,LockSupport.unpart(Thread),唤醒当前线程,转回可执行状态,可以唤醒指定线程。或者是interrupt方法会将中断标志设置为true,并且调用unpart()方法

LockSupport的park和unpark,类似于0\1机制,0时等待,1时运行,并且不分先后,而且unpark不会累加。

4.RUNNABLE==>TIMED_WAITING

a)调用带超时参数的 Thread.sleep(long millis) 方法;
b)获得锁的线程,调用带超时参数的Object.wait(long timeout) 方法;
c)调用带超时参数的 Thread.join(long millis) 方法;
d)调用带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
e)调用带超时参数的 LockSupport.parkUntil(long deadline) 方法。

5.RUNNABLE==>TERMNATED

a)线程正常执行完
b)stop()
c)interrupt()

stop和interrupt有啥区别?
stop直接杀死线程,如果是ReentrantLock,被杀死的线程持有锁的话,锁资源是无法得到释放的。
interrupt方法是修改线程的中止中断标识为true,可当做是一个通知,执行一些后续操作,也可以无视这个通知。

interrupt如何得到这个通知的呢?
1.抛出异常
当前线程为WAITING和TIMED_WAITING状态时,调用当前线程的interrupt方法时,类似 wait()、join()、sleep() 这样的方法,这些方法都会 throws InterruptedException 这个异常,也就是通过抛出异常的形式通知你当前线程被打断。并且抛出完异常之后,会将中断标识清除

java中运行时的线程调用阻塞的API的时候不会改变线程状态,还是RUNNABLE状态,这一点跟操作系统中的不一样,操作系统中的线程是会变成休眠状态。

当线程 A 处于 RUNNABLE 状态时,
并且阻塞在 java.nio.channels.InterruptibleChannel 上时,如果其他线程调用线程 A 的 interrupt() 方法,线程 A 会触发java.nio.channels.ClosedByInterruptException 这个异常;
而阻塞在 java.nio.channels.Selector 上时,如果其他线程调用线程 A 的 interrupt() 方法,线程 A 的 java.nio.channels.Selector 会立即返回。

前者是通过抛出异常的形式让你自己做后续操作,后者是直接结束阻塞的行为,IO部分的知识了解的不多,以后遇到这种情况,回来在补充。

2.主动感知
通过线程的isInterrupted(),并且此方法不会清除中断标志,主动监测自己是否被打断。

二.如何合理设置线程数量

设置合理的线程数量核心就是提升硬件的使用率,也就是CPU和IO硬件之间的配合效率。

对于操作系统:操作系统已经解决了磁盘和网卡的利用率问题,利用中断机制还能避免 CPU 轮询 I/O 状态,也提升了 CPU 的利用率。但是操作系统解决硬件利用率问题的对象往往是单一的硬件设备。

具体分是CPU密集型还是IO密集型。

CPU密集型理论上:线程数量 = CPU核数。但实际工程上一般设置为:CPU核数+1,多出的一个线程主要是备用,应对偶尔的内存页失效或其他原因导致阻塞。

IO密集型:最佳的线程数是与程序中 CPU 计算和 I/O 操作的耗时比相关的
公式:

最佳线程数 =CPU核数*[1 +(I/O 耗时 / CPU 耗时)]

也有按照2*cpu核数+1的直接设置的,实际工程中还是通过压测分析。
压测时,我们需要重点关注 CPU、I/O 设备的利用率和性能指标(响应时间、吞吐量)之间的关系。

三.局部变量的安全性

这个问题比较基础,因为方法在执行时候是通过不断的押栈,这种结构称之为调用栈,每个方法在调用栈中都有自己独立空间,称之为栈帧,局部变量(应用对象的话指针在调用栈中,对象在堆中)和方法参数以及返回地址都在调用栈中,而且对于不同的线程,拥有自己的调用栈,互不干扰。也就是没有共享变量的问题,所以线程安全。

对于栈溢出的解决方法:
1.递归改为非递归形式
2.限制递归次数,也就是base case 一定要写清楚。

并发策略。
避免共享:避免共享的技术主要是利于线程本地存储以及为每个任务分配独立的线程。
不变模式:这个在 Java 领域应用的很少,但在其他领域却有着广泛的应用,例如 Actor 模式、CSP 模式以及函数式编程的基础都是不变模式。
管程及其他同步工具:Java 领域万能的解决方案是管程,但是对于很多特定场景,使用 Java 并发包提供的读写锁、并发容器等同步工具会更好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值