今天我们继续分析并发编程知识,今天聊得是Thread(java.lang.Thread
)线程。
先看看Thread有些什么东东:
如何获取当前线程名称
public class ThreadMethodsDemo {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("当前线程名称:"+Thread.currentThread().getName());
}
},"Java后端技术全栈").start();
}
}
输出:
当前线程名称:Java后端技术全栈
上面只是举例一种方式,还有其他方式也可以获取。我们还是要搞清楚什么地方能设置线程的名称。
注意Thread中有个属性name,这就是线程名称:
其实能设置线程名称的两个地方:
第一个,Thread中构造方法中能设置name都,最后都是调用这个init方法进行name设置的。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
//设置name的值,线程的名称
this.name = name;
//....省略
}
第二个,也就是setName(String name);
方法
public final synchronized void setName(String name) {
checkAccess();
if (name == null) {
throw new NullPointerException("name cannot be null");
}
//设置线程name
this.name = name;
if (threadStatus != 0) {
setNativeName(name);
}
}
使用
public class ThreadMethodsDemo {
public static void main(String[] args) {
test1();
test2();
}
private static void test1(){
new Thread(new Runnable() {
@Override
public void run() {
//输出:当前线程名称:Java后端技术全栈
System.out.println("当前线程名称:"+Thread.currentThread().getName());
}
},"Java后端技术全栈").start();
}
private static void test2(){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//输出:当前线程名称:老田
System.out.println("当前线程名称:"+Thread.currentThread().getName());
}
});
thread.setName("老田");
thread.start();
}
}
建议:我们在使用的时候建议设置线程名称,当出问题的时候可以更好地辨别属于哪个线程,加速问题排除效率。
Thread.sleep(times)使当前线程从Running状态放弃处理器进入Block状态,休眠times毫秒,再返回Runnable状态。
private static void test3(){
new Thread(new Runnable() {
@Override
public void run() {
SimpleDateFormat format=new SimpleDateFormat("hh:mm:ss");
//输出系统时间的时分秒。每2秒显示一次。可能会出现跳秒的情况,因为阻塞1秒过后进入runnable状态,
//等待分配时间片进入running状态后还需要一点时间
while(true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(format.format(new Date()));
}
}
}).start();
}
运行
注意:当一个线程处于睡眠阻塞时,若被其他线程调用.interrupt方法中断,则sleep方法会抛出InterruptedException异常。从上面的try--catch就能看到捕获的是
InterruptedException
中断异常区。
看下面这段有意思的代码,启动两个子线程,A线程先睡会,B线程去执行,等B线程执行玩了后中断A线程;
private static void test4() {
/*
* 表演者:处于睡眠阻塞的线程。
* 当一个方法中的局部内部类中需要引用该方法的其他局部变量,那么这个变量必须是final的
*/
final Thread lin = new Thread() {
public void run() {
System.out.println("林:刚美完容,睡觉吧!");
try {
// 当一个线程处于睡眠阻塞时,若被其他线程调用interrupt()方法中断,
// 则sleep()方法会抛出 InterruptedException异常
Thread.sleep(100000000);
} catch (InterruptedException e) {
System.out.println("林:干嘛呢!都破了相了!");
}
}
};
/* 表演者:中断睡眠阻塞的线程*/
Thread huang = new Thread() {
public void run() {
System.out.println("黄:开始砸墙!");
for (int i = 0; i < 5; i++) {
System.out.println("黄:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("咣当!");
System.out.println("黄:搞定!");
// 中断lin的睡眠阻塞
lin.interrupt();
}
};
lin.start();
huang.start();
}
运行结果
在Thread类中有个属性、三个常量、setPriority()方法
//优先级
private int priority;
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
//newPriority检查,小于1大于10都不行
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
优先级被划分为1-10,1最低10最高。优先级越高的线程被分配时间片的机会越多,那么被CPU执行的机会就越多。
看下面这段代码
private static void setPriority() {
// 最高优先级的线程
Thread max = new Thread() {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("max "+Thread.currentThread().getName());
}
}
};
max.setName("max的线程");
// 最低优先级的线程
Thread min = new Thread() {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("min "+Thread.currentThread().getName());
}
}
};
min.setName("min的线程");
// 默认优先级的线程
Thread norm = new Thread() {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("norm "+Thread.currentThread().getName());
}
}
};
norm.setName("norm的线程");
// 设置了优先级也不能100%控制线程调度。
// 只是最大程度的告知线程调度以更多的几率分配时间片给线程优先级高的线程
max.setPriority(Thread.MAX_PRIORITY);
min.setPriority(Thread.MIN_PRIORITY);
// 这项设置可以省略,默认情况下就是该值
norm.setPriority(Thread.NORM_PRIORITY);
min.start();
norm.start();
max.start();
}
运行结果就不贴了,因为每次运行的结果不一样。因为这家伙是个不靠谱的家伙。
Java线程可以有优先级的设定,高优先级的线程比低优先级的线程有更高的几率得到执行(不完全正确,请参考下面的“线程优先级的问题”)。
对于线程优先级,我们需要注意:
join()是Thread类的一个方法,根据jdk文档的定义,join()方法的作用,是等待这个线程结束,即当前线程等待另一个调用join()方法的线程执行结束后再往下执行。通常用于在main主线程内,等待其它调用join()方法的线程执行结束再继续执行main主线程。
在Thread类中有三个join方法,可能很多人也就只用过无参数的那个方法,其实Thread里是有三个方法的
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis, int nanos) throws InterruptedException {
//...
join(millis);
}
//最终的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");
}
if (millis == 0) {
while (isAlive()) {
//等待0, Object中wait方法,会释放对象的锁
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
//等待delay, Object中wait方法,会释放对象的锁
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
/**
* Tests if this thread is alive. A thread is alive if it has
* been started and has not yet died.
*
* @return <code>true</code> if this thread is alive;
* <code>false</code> otherwise.
*/
public final native boolean isAlive();//一看注释便知道
都知道这个方法使用了同步锁修饰,然后用到了wait方法。那就必然会想到notify和notifyAll方法。那什么时候执行唤醒这个操作呢?
下面是JVM层面的代码
//一个c++函数:
void JavaThread::exit(bool destroy_vm, ExitType exit_type) ;
//里面有一个贼不起眼的一行代码
ensure_join(this);
static void ensure_join(JavaThread* thread) {
Handle threadObj(thread, thread->threadObj());
ObjectLocker lock(threadObj, thread);
thread->clear_pending_exception();
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
java_lang_Thread::set_thread(threadObj(), NULL);
//同志们看到了没,别的不用看,就看这一句
//这里就是唤醒操作
lock.notify_all(thread);
thread->clear_pending_exception();
}
先来个案例演示一下:
图片下载和图片显示,图片显示需要等待图片下载完成后才可以显示
public class JoinDemo {
// 判断照片是否下载完成
private static boolean isFinish = false;
public static void main(String[] args) {
// 下载图片的线程
final Thread download = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("download:开始下载图片");
for (int i = 0; i <= 100; ) {
i = i + 10;//为了演示
System.out.println("download:已完成" + i + "%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("download:图片下载完毕");
isFinish = true;
}
});
download.start();
// 用于显示图片的线程
Thread showImg = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("show:准备显示图片");
// 等待下载线程工作结束后,再执行下面的代码,
try {
// 此时显示图片的线程就进入阻塞状态,等待download线程运行结束,
// 才能执行下面的代码。注意千万不要在永远也死不了的线程上等待
download.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!isFinish) {
throw new RuntimeException("show:图片还没有下载完");
}
System.out.println("show:图片显示完成!");
}
});
showImg.start();
}
}
最后输出
都知道线程的启动是调用start()方法,继续说说这个方法,在Thread中start方法
public synchronized void start() {
//这里private volatile int threadStatus = 0;初始化的时候就是0
//如果这里不为0的话就抛异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
//把当前线程加入到线程组中
//private ThreadGroup group;就是这么个东西
group.add(this);
//初始化标记位未启动
boolean started = false;
try {
//下面
start0();
//标识为启动状态
started = true;
} finally {
try {
//如果没开启,标识为启动失败
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
//这是虚拟机里的start0方法
private native void start0();
private Runnable target;
//建议重写此方法或者实现使用Runabble的run方法
//如果重写了Thread的run方法,那么start0方法后面会调用到子类重写的run方法
//如果是实现Runnable接口中的run方法那么就会执行下面的target,run()
//否则啥都不干
@Override
public void run() {
if (target != null) {
//调用我们通常实现的业务代码的run方法
target.run();
}
}
start0方法是虚拟机提供的,最后会调用run方法,最后执行我们在run方法里的业务代码。这个虚拟机里面的源码写出来篇幅会很大,这里在哪买就不贴了。知道这么回事就ok了。
参考:
https://www.cnblogs.com/duanxz/p/5226109.html
https://www.cnblogs.com/qin-derella/p/6588421.html