一、创建线程
Thread在使用时,一种方式是构造一个Thread的子类,通过覆盖run方法实现。
Thread t = new Thread() {
@Override
public void run() {
System.out.println("hello");
}
};
t.start();
还有是使用Runnable接口的方法
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};
Thread t = new Thread(runnable);
t.start();
java8中又为我们提供了lambda语法。
Runnable runnable = () -> {
System.out.println("hello");
};
Thread t = new Thread(runnable);
t.start();
lambda语法其实通过lambda工厂构造了一个Runnable实例。
为什么构造Thread类时传入Runnable实例,就会调用Runnable实例的方法呢?
在调用start方法后,会回调Thread实例的run方法,如果Runable实例不为null,就会调用Runable实例的run方法。
private Runnable target;
public Thread(Runnable target) {
//此处省略300字
this.target = target;
//此处省略300字
}
public void run() {
if (target != null) {
target.run();
}
}
二、线程中断(stop、interrupt、isInterrupted和interrupted)
1、stop方法
stop方法用于停止一个线程,但是这个方法已经被废弃。
为什么stop()方法被废弃而不被使用呢?
原因是stop()方法太过于暴力,会强行把执行一半的线程终止。这样会就不会保证线程的资源正确释放,通常是没有给与线程完成资源释放工作的机会,因此会导致程序工作在不确定的状态下。
我都使用过文件流,文件流打开后必须关闭。请看下面一个例子:
class FileRunnable implements Runnable {
@Override
public void run() {
BufferedOutputStream bos=null;
try {
bos =new BufferedOutputStream( new FileOutputStream("C:\\abc.txt"));
System.out.println("数据开始写入缓存");
Thread.sleep(1000);
bos.write(1);//停留1秒模拟大量数据需要写入缓存
System.out.println("数据写入缓存成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
flushAndClose(bos);
}
}
private void flushAndClose(OutputStream outputStream) {
if (outputStream != null) {
try {
Thread.sleep(1000);
outputStream.flush();//停留1秒模拟大量数据需要从缓存写入到文件
System.out.println("flush 成功");
outputStream.close();
System.out.println("close 成功");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
FileRunnable类的目的是写数据到一个文件,然后再关闭这个文件流,不关闭流是十分危险的。
public static void main(String args[]) throws Exception {
Thread td = new Thread(new FileRunnable());
td.start();
Thread.sleep(500);
td.stop();
}
结果:
结果分析:线程sleep 500毫秒后调用了stop方法,td线程应该正在写入数据到缓存,stop的出现打断线程,立即退出run方法,但是有由于try-catch-finally的缘故,退出之前必须执行finally语句块,我们在finally语句块中成功关闭了流,也就是释放了资源。
在这个例子中我们发现使用try-catch-finally语句块是有机会释放资源的。但是如果线程运行到finally语句,突然stop方法被调用又会出现什么情况呢?
public static void main(String args[]) throws Exception {
Thread td = new Thread(new FileRunnable());
td.start();
Thread.sleep(1500);
td.stop();
}
结果:
结果分析:线程sleep 1500毫秒后调用了stop方法,td线程应该正在将缓存中的数据写入到文件,stop的出现打断线程,立即退出run方法,这一次我们的流没有被关闭,资源没有被释放,这是非常危险的。stop像是一个幽灵,随时可能出现,不给你任何反应的时间。
2、interrupt方法
stop方法不好用,我们用什么方法替代stop方法呢?没错是interrupt方法。不像stop方法的暴力,interrupt方法更加柔和。interrupt是线程中断并不是终止,interrupt方法好像是告诉线程请你停下来,线程停还是不停完全是由线程自己决定。
public static void main(String args[]) throws Exception {
Thread td = new Thread(){
public void run() {
while (true){
System.out.println("1");
}
}
};
td.start();
Thread.sleep(10);
td.interrupt();
}
这个例子中即使调用了interrupt方法,td线程也不会停止,td不理会interrupt,会无限次打印1。
isInterrupted方法用来检测interrupt方法是否被调用过,如果被调用返回true,否则返回false。
public static void main(String args[]) throws Exception {
Thread td = new Thread(){
@Override
public void run() {
while (!this.isInterrupted()){
System.out.println("1");
}
System.out.println("interrupt 方法被调用");
}
};
td.start();
Thread.sleep(10);
td.interrupt();
}
结果:
当在一个被阻塞的线程(调用sleep或wait)上调用interrupt方法时,sleep或wait 会抛出interrupted Exception异常,出现了这种异常说明interrupt方法被调用,异常抛出后线程的中断状态将会被重置。
实例:
public static void main(String args[]) throws Exception {
Thread td = new Thread() {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("InterruptedException被捕获");
System.out.println("isInterrupted:"+this.isInterrupted());
}
}
};
td.start();
Thread.sleep(10);
td.interrupt();
}
结果:
sleep抛出异常后,线程状态被重置,isInterrupted会返回false。
3、interrupted方法
前面我们学习了interrupt方法和isinterrupted方法,这两个方法都是对象方法,但是interrupted方法时静态方法。其实isinterrupted方法和interrupted方法都是用来判断当前线程是否为中断状态,不同的是isinterrupted返回线程状态后会做其他操作,而interrupted方法如果返回true,也就是说线程处于中断状态,会重置线程状态。
public boolean isInterrupted() {
return isInterrupted(false);
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
private native boolean isInterrupted(boolean ClearInterrupted);
实例:
public static void main(String args[]) throws Exception {
Thread td = new Thread() {
@Override
public void run() {
int i=0;
while (i<1000){i++;}//耽误点时间
System.out.println(this.isInterrupted());//true
System.out.println(this.isInterrupted());//true
System.out.println(Thread.interrupted());//true
System.out.println(Thread.interrupted());//false
System.out.println(this.isInterrupted());//false
}
};
td.start();
td.interrupt();
}
结果:
三、currentThread方法
currentThread方法用于获取当前线程对象,上面的例子中我们都是使用this获取当前线程对象的。有this为什么还要currentThread方法呢?
在使用Thread子类覆盖父类run方法时,这时的this和Thread.currentThread返回值时相等的,都是指向Thread子类对象。
使用Runnable接口的方法时,我们先是构造了一个Runnable的
public static void main(String args[]) throws Exception {
new App().test();
}
public void test() {
//情形1
Thread td1 = new Thread() {
@Override
public void run() {
System.out.println(this.equals(Thread.currentThread()));//true
}
};
td1.start();
//情形2
Thread td2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(this.equals(Thread.currentThread()));//false
}
});
td2.start();
//情形3
Thread td3 = new Thread(() -> {
System.out.println(this.equals(Thread.currentThread()));//false
});
td3.start();
}
四、join方法
Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。具体看代码:
public static void main(String args[]) throws Exception {
Thread t1 = new Thread() {
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
System.out.println("t1");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
for (int i = 0; i < 3; i++) {
System.out.println("t2");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
t1.join();
System.out.println("t1执行完毕");
t2.start();
t2.join();
System.out.println("t2执行完毕");
}
结果:
程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会。
上面注释也大概说明了join方法的作用:在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。注意,这里调用的join方法是没有传参的,join方法其实也可以传递一个参数给它的。
join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。
join方法实现原理
有了上面的例子,我们大概知道join方法的作用了,那么,join方法实现的原理是什么呢?其实,join方法是通过调用线程的wait方法来达到同步的目的的。例如,A线程中调用了B线程的join方法,则相当于A线程调用了B线程的wait方法,在调用了B线程的wait方法后,A线程就会进入阻塞状态,具体看下面的源码:
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()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
从源码中可以看到:join方法的原理就是调用相应线程的wait方法进行等待操作的,例如A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法,当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。
五、获取线程状态(getState)
一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、计时等待(time_waiting)、等待(waiting)、消亡(dead / terminated)。一共7种状态。
getState方法返回线程当前状态,实际上是返回一枚举类State。
public enum State {
NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;
}
State状态一共有6种:创建(new)、就绪(runnable)、阻塞(blocked)、计时等待(time_waiting)、等待(waiting)、消亡(terminated)。有没有发现少了"运行(running)"状态,为什么缺少这种状态呢?这里的就绪(runnable)被看成一个复合状态,它包括两个子状态:runnable和running。
六、yield方法
Thread.yield()方法也是一个静态方法。作用是:让当前运行线程回到可运行状态,于是处于可运行状态的线程又争夺cpu的控制权,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
七、守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:守护线程是为用户线程提供服务的,如果用户线程全部停止(死掉),那么守护线程也就丢去了存在的意义,所以守护线程会随着虚拟机的退出而被自动杀死。守护线程由于被外力强制杀死,如果守护线程正在执行一些任务,那么该任务可能无法完成。
用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on);但是有几点需要注意:
1)、thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
2)、 在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)
3)、不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。
实例:
public static void main(String args[]) {
final Thread main = Thread.currentThread();
final Thread userThread = new Thread() {
public void run() {
while (true) ;//第一个while(true)
}
};
final Thread daemonThread = new Thread() {
@Override
public void run() {
while (true) {
System.out.print("main线程isAlive:" + main.isAlive());
System.out.print(" userThread线程isAlive:" + userThread.isAlive());
System.out.print(" daemonThread线程isAlive:" + this.isAlive());
System.out.println("");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
userThread.start();
daemonThread.setDaemon(true);
daemonThread.start();
while (true) ;//第二个while(true)
}
这个例子中有三个线程,分别是:main函数所在线程为主线程,main函数又创建了userThread和daemonThread 两个线程。
该例子中有两个“while(true);”
1)、如果两个“while(true);”都未注释掉,main线程和userThread线程都会结束:
无限次打印……
2)、如果main线程的“while(true);”被注释掉,main线程会很快结束,而userThread线程会一直运行:
无限次打印……
3)、如果userThread线程的“while(true);”被注释掉,userThread线程会很快结束,而mian线程会一直运行:
无限次打印……
4)、如果“while(true);”都被注释掉,main和userThread线程会很快结束,这两个线程结束后,daemoneThread就被kill掉:
这里只打印了一次。