线程(Thread)
在一个java程序运行时,就有线程被调用。如果没有在程序中建立线程,那么在运行main() 方法的时候,JVM 就会启用一条线程来执行该 Java 程序,这条被唤醒执行 main() 方法的线程就是主线程。
线程的创建和运行
在使用之前一定要明白:在Java中说有的线程都是通过 java.lang.Thread 来创建并被 Thread 类所控制。通过调用 Thread 类的构造函数就可以在主线程之外创建一条新线程。
Thread t = new Thread();
在通过调用 Thread 类中的 start() 方法来启动这条线程,即:t.start();
java.lang.Thread
如果仅仅只是按照上面的方法来创建一个新的线程,那么这条线程什么都做不了。这样完全没有任何意义,所以我们可以通过继承的方式来实现创建一个新的Thread class来完成我们的自定义任务。
public class MyThread extends Thread {
// 我们把要执行的任务代码写在 run() 方法中
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 我是普通的用户线程");
System.out.println("Hello world!");
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + " 我是主线程");
System.out.println("创建线程");
Thread thread = new MyThread();
System.out.println("开始线程");
thread.start();
}
}
Thread.currentThread() 返回当前的线程;getName() 返回线程名称
// 输出
main 我是主线程
创建线程
开始线程
Thread-0 我是普通的用户线程
Hello world!
java.lang.Runnable
通过源码我们能够发现 Thread 也是调用 Runnable 接口来实现 run() 方法
// 源码
// public class Thread implements Runnable
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
那为什么在查资料时,看见使用Runnable重构 run 方法的要比 直接调用Thread还要多呢?
因为,Thread是一个类,而在 Java 中只允许单继承,这样就很有局限性:如新建了一个类,既想要继承其他的类,又想实现多线程操作,而父类只能有一个,在这时候就可以通过 implements 接口的方式来实现对一个类的拓展。(在Runnable接口中,只有一个 run() 方法)。 实现如下:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": 我是普通的用户线程");
}
public static void main(String[] args) {
System.out.println("创建线程");
Thread thread = new Thread(new MyRunnable());
System.out.println("开始线程");
thread.start();
}
}
线程的中断
线程的结束与否由该线程自己决定。
过去常用 stop 方法来结束线程,即线程从外部 kill 的话。这样操作容易导致线程执行中所占用的资源没有释放,从而形成线程垃圾。这些线程垃圾无法回收。
简单的说:在Java中通过给一个线程打上中断标记,告知这个中断你可以中断。之后,在该线程中通过try–catch方法捕捉线程出发的异常信号InterruptedException
来进行中断。
其中给线程打上中断标记就是就是设置,线程的的中断状态为ture
; 线程正常执行时为执行态,无法被中断。当线程调用一些中断方法时,线程会进入阻塞态,并放出InterruptedException
。此时线程可以中断,中断响应后,重置中断状态为 false
。
(中断方法:Object.wait()
, Object.wait(long)
, Object.wait(long, int)
, Thread.sleep(long)
, Thread.interrupt()
, Thread.interrupted()
, Serialized Form
)
public class MyRunnable implements Runnable {
// 我们把要执行的任务代码写在 run() 方法中
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": 我是普通的用户线程");
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
System.out.println(Thread.currentThread().getName() + ": 检测到中断信号");
return; // 停止该线程
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
thread.interrupt();
}
}
// 输出
main: 我照常运行
Thread-0: 我是普通的用户线程
Thread-0: 检测到中断信号
如果在检测到线程中断异常之后没有 通过 return;
来终止该线程,线程就会继续往下运行。
在代码运行中,可以使用 Thread.currentThread().isInterrupted();
来判断线程的中断状态来。
守护线程
用户线程:我们直接创建的线程为用户线程,当一个进程不包含任何存活的用户线程时,进程结束。
守护线程:守护线程是一个具有低优先级且运行在后台的线程,如:垃圾回收线程。当最后一个线程结束时,守护线程结束。
java中我们只需要调用 Thread.setDaemon();
来将创建的线程设置为守护进线程。这个方法一定要在线程 start 之前使用。
Thread t = new Thread(new MyRunnable());
t.setDaemon();
t.start();
线程池
为了最大化的压榨计算机的资源和运算能立,在运行项目的时候会大量的使用多线程模式进行操作。但是频繁的创建线程和销毁线程,会占用大量的cpu和内存,同时也会让gc承担巨大的压力。与之相比,运行线程所使用的资源显得微乎其微。因此因此频繁的创建和销毁线程的行为并不可取,也因此就产生了线程池。
线程池可以简单的分为四类:
ExecutorService pool;
pool = Executors.newCachedThreadPool();
pool = Executors.newFixedThreadPool(3);
pool = Executors.newSingleThreadExecutor();
pool = Executors.newSingleThreadScheduledExecutor();
//pool = Executors.newWorkStealingPool();
例如:
public static void main(String[] args) {
// Thread thread = new Thread(new Test());
// thread.start();
// System.out.println(Thread.currentThread().getName() + ": 我照常运行");
// thread.interrupt();
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
try{
Thread.sleep(3000);
} catch(InterruptedException e){}
}
});
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
try{
Thread.sleep(5000);
} catch(InterruptedException e){}
}
});
pool.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
}
// 输出
pool-1-thread-1
pool-1-thread-2
// 3秒后
pool-1-thread-1