多线程
文章目录
前言
Thread类是JVM用来管理线程的一个类,每个线程都有唯一的一个Thread对象与之对应。
一、Thread 类及常见方法
Thread 类的构造方法
Thread类提供的构造方法有许多,目前我们需要了解的主要有:
- Thread ( ) 创建线程对象
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run() {
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
}
- Thread (Runnable) 使用Runnable对象创建对象
public class Test2 {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用Thread t=new Thread(new Runnable())创建线程对象");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
- Thread (String name) 创建线程对象并命名
public static void main(String[] args) {
Thread t=new Thread("线程1"){
@Override
public void run() {
while (true){
System.out.println("线程名为“线程1”的线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
}
这里使用一个死循环保持程序运行不退出,然后在jconsole.exe处查看线程情况:
- Thread (Runnable String) 使用Runnable 对象创建线程对象,并命名;
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println("使用Runnable创建对象并命名为“线程2”");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"线程2");
t.start();
}
同样查看线程情况:
Thread 的几个常见属性
public static void main(String[] args) {
Thread t=new Thread(()->{
System.out.println("thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程1");
t.start();
System.out.println(t.getId());
System.out.println(t.getName());
System.out.println(t.getState());
System.out.println(t.getPriority());
System.out.println(t.isDaemon());
System.out.println(t.isAlive());
System.out.println(t.isInterrupted());
}
运行结果:
getId( )获取到的是线程在JVM中的身份标识;
getName( )获取到的是我们再构造方法创建的名字;
getState( )这里获取到的状态是JVM内部设立的状态体系,是一瞬间的状态,相比操作系统内置的状态要更加丰富;
getPriority()优先级越高越容易被操作系统调度到;
isDaemon( )方法中,daemon称为“守护线程”或“后台线程”;线程可以分为前台线程和后台线程,一个线程创建成功以后默认是前台线程,只有所有的前台线程都执行完毕,进程才会退出,而并不关注后台线程是否执行完毕,因此前台线程会阻止进程结束,后台线程不会;
可以通过setDaemon( )将线程设置为后台线程;
isAlive()可以简单理解为run()方法是否执行结束;
二、启动一个线程
通过重写run( )方法,可以创建一个线程对象,想要线程真正开始启动,就需要借助start( )方法;
run()方法与start ( )方法的区别:
- 重写run()只是指明了线程需要做的事并没有创建新的线程;
- start( )则是真正的创建了一个新线程,这个新线程调用了run( )方法;
调用 start 方法, 才真的在操作系统的底层创建出一个线程;
三、中断线程
手动设置标志位
public class Test6 {
//设置一个布尔变量(成员变量)来表示线程是否要结束
private static boolean isQuit=false; //初始默认不结束
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while (!isQuit){
System.out.println("线程运行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程执行结束");
});
t.start();
Thread.sleep(3000);
System.out.println("控制新线程退出");
isQuit=true;
}
}
使用Thread自带的标志位
Thread.currentThread().isInterrupted()
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while (!Thread.currentThread().isInterrupted()){
System.out.println("线程运行中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
Thread.sleep(5000);
System.out.println("控制新线程退出");
t.interrupt();
}
}
这里的currentThread()是一个静态方法,可以获取到当前线程的实例;
isInterrupted()就是用来判定内置情况的标志位,当它为true时,表示线程要被中断;
interrupt()方法用来中断Thread对象关联的线程,当这个线程没有处在阻塞状态时,该方法就会修改内置的标志位;当该线程正处在阻塞状态时,这个方法就会让线程内部产生阻塞的方法抛出异常;
因此关于上面代码的运行结果:使用isInterrupted()方法判断了内置的标志位为false,线程不被中断,进入了循环,打印了一系列的 “线程运行中…” ,之后由于sleep()的使用,使线程处在了阻塞状态, 之后interrupt()方法的调用让sleep()方法抛出了异常,我们使用e.printStackTrace()打印了异常的信息,而这样的一系列操作下来,其内置的标志位并没有被修改,因此异常抛出以后程序依旧在运行。
而正是这样的异常捕获操作,使得程序员可以自行控制线程的退出行为:
- 直接退出
public class Test8 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
System.out.println("线程运行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//打印异常信息之后直接退出
break;
}
}
});
t.start();
Thread.sleep(5000);
System.out.println("控制线程退出");
t.interrupt();
}
}
- 稍作等待,然后退出
public class Test8 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
System.out.println("线程运行中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//e.printStackTrace();
System.out.println("线程即将退出");
try{
Thread.sleep(4000);
}catch (InterruptedException e1){
e1.printStackTrace();
}
break;
}
}
System.out.println("线程已经退出");
});
t.start();
Thread.sleep(5000);
System.out.println("控制线程退出");
t.interrupt();
}
}
- 忽略异常,不退出
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
while (!Thread.currentThread().isInterrupted()){
System.out.println("线程运行中");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程即将退出");
}
}
});
t.start();
Thread.sleep(5000);
System.out.println("控制新线程退出");
t.interrupt();
}
}
这样,通过这3种方式,当主线程宣布退出的时候,新线程就可以自己来决定用哪种方式退出;
Thread.currentThread().isInterrupted()是"不清除标志位":
public class Test9 {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().isInterrupted());
}
}
});
t.start();
t.interrupt();
}
}
标志位一直都是true,因为标志位没有被清;
Thread.interrupted()
Thread.isInterrupted()与Thread.currentThread().isInterrupted()的区别就在于:前者是清除标志位,以开关举例,就是会自动恢复的开关;
public class Test9 {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(Thread.interrupted());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
t.interrupt();
}
}
只有一开始是 true,后边都是 false,因为标志位被清;
四、等待线程-join
有时候,我们需要等待一个线程结束以后再进行下一步的操作,等待一个线程结束就需要使用 join( );
/*
* main等待t2结束,t2等待t1结束
* */
public class Test1 {
private static Thread t1=null;
private static Thread t2=null;
public static void main(String[] args) throws InterruptedException {
System.out.println("main begin");
t1=new Thread(()->{
System.out.println("t1.begin");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1.end");
});
t1.start();
t2=new Thread(()->{
System.out.println("t2.begin");
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2.end");
});
t2.start();
t2.join();
System.out.println("main end");
}
}
/*
* 先执行t1,t1执行完再执行t2
* */
public class Test2 {
private static Thread t1=null;
private static Thread t2=null;
public static void main(String[] args) throws InterruptedException {
System.out.println("main begin");
t1=new Thread(()->{
System.out.println("t1 begin");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 end");
});
t1.start();
t1.join();
t2=new Thread(()->{
System.out.println("t2 begin");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 end");
});
t2.start();
t2.join();
System.out.println("main end");
}
}
线程之间的执行顺序是随机的,因此我们无法决定哪个线程先执行,但是使用join( )我们可以控制哪个线程先结束;
join( )这种方式的等待类似于死等,即调用者没有结束时将会一直等待下去;
join(long millis) 这种等待方式是设置了一个时间,若在规定的时间内没有结束,将不再继续等待;
join(long millis, int nanos) 这种与上面设置时间的等待类似,但是时间的设置可以更加精确;
五、获取线程实例
很多时候,为了对线程进行自然就需要获取到线程的实例,我们使用currentThread( )来获取当前线程的引用;
六、休眠线程
sleep( )即让线程休眠一定的时间;
当一个线程调用sleep( ),就是把这个线程从就绪队列调到了阻塞队列;
而就绪队列的特点是这里的线程可以随时上CPU运行;
阻塞队列中的线程则是暂时不参与操作系统的调用,不上cpu执行;
因此 t.sleep(1000)就代表着,线程 t 从就绪队列调到了阻塞队列,在1000ms之后再调回到就绪队列;
因此sleep(1000)表示休眠时间不小于1000ms,一般情况下是略大于1000ms的,因为在就绪队列的线程也不一定就会马上调用,还是依据操作系统的调用情况;
over!