Thread
线程是程序中的执行线程。Java 虚拟机允许应用程序同时运行多个执行线程。
每个线程都有一个优先级。具有较高优先级的线程优先于具有较低优先级的线程执行。每个线程可能会也可能不会被标记为守护进程。当在某个线程中运行的代码创建一个新Thread
对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时,它才是守护线程。
有两种方法可以创建一个新的执行线程。一种是将类声明为Thread
。
创建线程的另一种方法是声明一个实现Runnable
接口的类。
每个线程都有一个用于识别目的的名称。多个线程可能具有相同的名称。如果在创建线程时未指定名称,则会为其生成一个新名称。
常用属性
守护线程
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {//在线程启动之前设置守护线程
throw new IllegalThreadStateException();
}
daemon = on;
}
JVM进程中没有一个非守护线程,JVM自动退出,守护线程具备自动结束生命周期的特点,例如垃圾回收就是守护线程,守护线程一般用来执行后台任务。
线程名字
public final synchronized void setName(String name) {
checkAccess();
this.name = name.toCharArray();//转为字符数组
if (threadStatus != 0) {
setNativeName(name);
}
}
需要注意的是线程名称只能在线程启动之前修改,线程一旦启动,线程名称不能再修改。
如果没有显示的指定线程名称,线程会使用 "Thread-"作为前缀,与nextThreadNum进行组合,生成当前线程名称。
线程优先级
priority 等级。Java线程的等级是1~10,默认是5。需要注意的是:有些系统会忽略优先级,程序的正确性不能依赖线程的优先级。
线程本地变量
ThreadLocal
父子线程
Thread.currentThread()可以获取当前线程。
ThreadGroup一个线程组代表一组线程。此外,一个线程组还可以包括其他线程组。线程组形成一棵树,其中除了初始线程组之外的每个线程组都有一个父级。
允许线程访问有关其自己的线程组的信息,但不能访问有关其线程组的父线程组或任何其他线程组的信息。
private final ThreadGroup parent;
Thread threads[]; // 保留的线程数组
ThreadGroup groups[]; // 子线程组
初始化
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);//没有设置线程名字,设置默认
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
this.name = name.toCharArray();
//新的线程的创建是由父线程创建的,main函数所在的线程是JVM创建
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
//默认会分配到父线程的线程组,同时拥有和父线程同样的优先级
if (g == null) {
g = parent.getThreadGroup();
}
}
...
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
...
}
默认情况下,子线程继承了父线程的守护线程属性、优先级、线程组等特性。
启动
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();//这里使用了本地调用,通过C代码初始化线程需要的系统资源
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
运行
执行start后处于可运行状态
public void run() {
if (target != null) {
target.run();//这里的target实际上要保存的是一个Runnable接口的实现的引用
}
}
所以使用继承Thread创建线程类时,需要重写run方法,因为默认的run方法什么也不干。
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
当我们使用Runnable接口实现线程类时,为了启动线程,需要先勇该线程类实例初始化一个Thread
中断
不要使用过期的supend,resume,stop。
调用 interrupt() 方法仅仅是在当前线程中打一个停止的标记,并不是真的停止线程。
也就是说,线程中断并不会立即终止线程,而是通知目标线程,有人希望你终止。至于目标线程收到通知后会如何处理,则完全由目标线程自行决定。
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // 仅仅是在当前线程中打一个停止的标记
b.interrupt(this);
return;
}
}
interrupt0();
}
目标线程
if(Thread.currentThread().isInterrupted()){//通过判断是否中断标志位
//处理中断逻辑
break;
}
Thread.sleep() 方法会抛出一个 InterruptedException 异常,当线程被 sleep() 休眠时,如果被中断,这会就抛出这个异常。
等待通知
join
等待这个线程死掉。当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行。
public final synchronized void join(long millis)
throws InterruptedException {
...
if (millis == 0) {
while (isAlive()) {//判断这个线程是否是活的
wait(0);//此方法的调用与调用的行为方式完全相同。
}
} else {
...
}
}
线程退出的时候,清除状态,这个时候调用isAlive() 就会返回false。然后调用lock.notify_all(thread);
这个时候就会唤醒线程上所有的wait,然后Thread.join里面的while循环就会继续。
wait
使线程停止运行,会释放对象锁。
notify/notifyAll
通知一个在该对象上等待的线程,可以继续运行。
静态方法
yield
放弃当前的CPU资源,将它让给其他任务去占用CPU执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。
Thread.yield();//一个静态方法
获取当前线程
Thread.currentThread()
睡眠
Thread.sleep(1s);//不会释放对象锁
使睡眠中的线程停止睡眠可以interrupt进行中断