生活
不断规划与寻找自己的人生,想法把自己变重要;
Thread实例
线程是进程的最小执行单位,今天来看看JAVA中的Thread。
创建Thread,并且运行的方法大家都会了。咱们往深走。
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("这就是个傻逼线程");
}
public static void main(String[] args) {
new MyThread().start();
}
}
Thread生命周期
下面来看下线程的生命周期,非常重要:
新建:创建对象即进入新建状态
就绪:执行start()方法,即进入就绪状态。进入就绪状态并不是马上执行而是等待CPU调度,调度到就执行
执行:抢占到CPU时间片,执行run方法
阻塞:sleep wait阻塞【注意wait的阻塞会放弃CPU资源 sleep并不会放弃】
死亡:线程执行完毕或因异常退出
Thread关键成员
下面来了解一下Thread的关键成员:
//是否守护线程
private boolean daemon = false;
//jvm状态
private boolean stillborn = false;
//线程执行目标
private Runnable target;
//线程组
private ThreadGroup group;
//线程类加载器
private ClassLoader contextClassLoader;
//这两个着重在明天的ThreadLocal学习中研究
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//线程优先级
//最低
public final static int MIN_PRIORITY = 1;
//普通
public final static int NORM_PRIORITY = 5;
//最高
public final static int MAX_PRIORITY = 10;
//线程状态 0代表新建
private volatile int threadStatus = 0;
创建一个Thread
ok,下面来看下如何创建一个线程,核心方法在init,简单了解一下:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
//获取创建这个线程的当前线程{即这个线程的父线程}
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
//创建线程在初始化的操作里,会直接把父线程的线程状态(用户线程还是守护线程)、优先级 都拷贝过来
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;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (parent.inheritableThreadLocals != null)
//这一块非常重要,在明天的ThreadLocal里再说
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
start
来看下 当你执行start的时候做了什么,
public synchronized void start() {
//当不上新建状态就抛异常
//意味着一个Thread不能多次start
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
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 */
}
}
}
//实际调用到native方法,具体就是拿到cpu时间片,然后执行run方法,细节不去深入
private native void start0();
守护线程
JAVA中的线程分为两个:用户线程和守护线程。
守护线程会在所有用户线程执行完毕才会执行,然后销毁,
注意:
1、通过thread对象的setDeamon设置为true来设置为守护线程
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
2、通过上面的源码可知,不能再线程执行过程中设置为守护线程,也就是说setDaemon必须在线程执行前执行
3、有init方法可知在一个守护线程里创建一个线程,那它也是守护线程。
sleep /wait
1、sleep是Thread里的方法,使线程休眠一段时间后醒来,在休眠期间不会释放已经获得的对象锁。
2、wait是Object里的方法,必须放在synchronized中使用,因为他会对对象的锁标记进行使用。一个对象执行了wait方法后就会释放他获得的所有锁,进入等待队列,直到执行notify或者notifyAll方法才会被唤醒参与锁的竞争。
线程礼让 yield
public static native void yield();
由执行状态变成就绪状态,让出CPU给其他线程,随后又马上参与竞争。
线程加入 join
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//在B线程 执行A线程.join,那么B线程必须等待A线程执行完毕才能执行,可以看到这个就是一个while判断,当B线程还在执行的时候,一直会阻塞,
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
线程中断
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
线程的中断同样要调用native方法,在执行join,wait,sleep方法时执行了中断就会抛出异常。
可以通过下面两个方法获取到线程中断的状态,是否中断
//获取线程的中断状态并清除 也就是第二次查询的时候 不再是中断
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
//获取线程的中断状态
public boolean isInterrupted() {
return isInterrupted(false);
}
过时的方法
Thread里有三个过时方法,最好不要使用:
1、stop():关闭线程,本质上是不安全的,因为他会解锁已经锁定的监视器。之前由这些监视器保护的对象中可能有不一致的情况,现在都可以被其他线程看到。
那么如何停止?
可以用中断来代替停止。
2、suspend resume
suspend挂起一个线程,resume恢复一个线程。
suspend挂起线程时不会释放锁,直到执行resume方法,被挂起的线程才能继续执行,从而其他阻塞在这个锁的线程才能得以执行。
当先执行了resume 在执行suspend时,就会导致死锁。也可称之为冻结。
那么如何挂起和恢复一个线程?
wait/notify
后记
明天来学习ThreadLocal,睡了 真累