浅析线程

线程与进程
进程具有一定独立功能的程序,它是系统进行资源分配和调度的一个独立单位。
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。在运行时,只是暂用一些计数器、寄存器和栈。

两者关系
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
同一进程的所有线程共享该进程的资源。
线程是进程内的一个执行单元。

调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。

线程状态

状态描述
NEW初始状态,线程构建,未调用start()方法
RUNNABLE运行状态,java线程将操作系统中的就绪(ready)、运行中(running)统称为可运行runnable
BLOCKED阻塞状态,表示线程阻塞于锁
WAITING等待状态,表示线程进入等待状态,需要等待其它线程做出一些特定行为(中断interrupt或通知notify/notifyAll等)
TIME-WAITING超时等待状态,不同于WAITING,线程在指定时间自动返回
TERMINATED终止状态,表示当前线程已经执行结束

线程执行start()方法只是进入就绪状态。
线程执行yield()方法会从Running状态进入就绪状态,从而让其它具有相同优先级的等待线程获取执行权,但是并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权,也有可能是当前线程又进入到运行状态继续运行。

线程状态变迁

阻塞状态是线程 阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态,但是阻塞在 java.concurrent包中Lock接口的线程状态却是等待状态,因为java.concurrent包中Lock接口对于 阻塞的实现均使用了LockSupport类中的相关方法。

线程优先级
Java线程可以有优先级的设定,高优先级的线程比低优先级的线程有更高的几率得到执行,注意是更高的几率而不是优先执行,记住当线程的优先级没有指定时,所有线程默认为普通优先级。

// 线程的优先级属性
private int priority;
// 线程所能拥有的最小优先级
public final static int MIN_PRIORITY = 1;
// 线程默认的优先级
public final static int NORM_PRIORITY = 5;
// 线程所能拥有的最大优先级
public final static int MAX_PRIORITY = 10;

// 优先级操作方法
int getPriority() // 得到线程的优先级
void setPriority(int newPriority) // 当线程被创建后,可通过此方法改变线程的优先级

注意点
①线程优先级不能作为程序正确性的依赖,因为某些操作系统会对优先级的设置进行忽略,如Mac OS X 10.10,Java版本为1.7.0_71,Ubuntu 14.04环境。
②线程的优先级无法保障线程的执行次序,只不过优先级高的线程获取CPU资源的概率较大。
③线程优先级继承于创建它的线程的优先级。

daemon线程
daemon线程是一种支持型后台线程,当一个jvm中不存在非daemon线程的时候,java虚拟机就会退出。一个线程设置为后台线程使用Thread.setDaemon(true)即可,不过需要在线程启动之前设置,不能在启动之后设置。
不是所有的finally块都会执行的,daemon线程中的finally块是否一定执行?
上述示例在jvm执行完main方法后,虚拟机中已经不存在非daemon线程了,虚拟机需要退出,jvm中所有的daemon线程都需要立即终止,DaemonRunner在sleep阶段就终止了,导致finally块执行不到。可见上述答案是NO。

线程创建的三种方式
1.继承Thread,重写run方法
java单继承,对原有的类继承的限制
2.实现Runnable接口,作为Thread构造器参数传入
不会影响原先类继承其它类
3.使用callable和future创建线程
前面两种方式都无返回值,而此种方式允许有返回值,FutureTask实现了RunnableFuture接口,RunnableFuture是Runnable和Future的子接口,所以FutureTask可以作为Thread构造方法参数。

线程常用方法
1.setName线程命名
2.sleep线程睡眠
需要捕获异常InterruptedException,sleep后线程会进入超时等待状态,睡眠时间结束,会进入就绪状态,等待cpu分派时间片,如果成功获得时间片,则变成运行状态
注意:sleep方法释放cpu执行权不释放锁
3.yield线程礼让
线程由运行态到就绪态,调用yield会使当前线程放弃cpu,给其它具有相同优先级的线程运行的机会,不过也有可能自己再次抢到cpu进入运行状态。
注意:yield方法会释放cpu,不会释放锁
4.join线程插队
调用t.join的线程会进入等待状态,直到t线程执行结束后才会被唤醒

Object.wait/notify
该方法与notify与notifyAll一同使用,都是Object类方法,必须在synchrnoized中使用。因为wait和notify方法使用的前提是必须先获取一个锁。Wait的作用是使当前线程进入阻塞状态并释放持有的对象锁,线程会进入该对象的等待池中,但不会主动去竞争该对象的锁;notify是随机唤醒一个等待当前对象的锁的线程,notifyAll是唤醒所有等待当前对象的锁的线程

5.1 为什么wait、notify必须在synchrnoized内?
wait方法语义:
①释放当前对象锁
②使得当前线程进入等待队列
wait既然是释放锁,那么必须先获取锁,notify是唤醒一个线程,所以需要知道待唤醒的线程等待在哪个对象监视器上,就必须找到这个对象,获取这个对象的锁,然后到这个对象的等待队列去唤醒一个线程

5.2 wait/notify原理
首先引入一个概念:对象监视器,大家知道重量级锁是通过对象内部的监视器实现的,monitor的本质是依赖于底层操作系统的Mutex Lock实现。

原理解析:
主要有以下三个步骤
1.调用wait方法前首先需要获取对象监视器,获取成功后状态为等待状态,从而进入等待队列并且释放锁。
2.当其它线程调用notify和notifyAl后,会从等待队列选择任意一个或全部唤醒。
3.执行完notify后,并不会立马唤醒线程(只是将WaitSet等待队列中线程转移到CXQ/EnterList中),原因是当前线程仍然持有这把锁,处于等待状态的线程无法获得锁。必须要等到当前的线程执行完按monitorexit指令之后,也就是被释放之后,处于等待队列的线程就可以开始竞争锁了。

当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线程:
Contention List(CXQ):所有请求锁的线程将被首先放置到该竞争队列
Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List
Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set
OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck
Owner:获得锁的线程
!Owner:释放锁的线程

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值