多线程指的是一个程序中包含两个或者两个以上的线程,多线程的提出是为提高代码的执行效率,这就好比工厂中的流水线,只有一条称为单线程,有多条流水线就称为多线程。多线程提高效率的同时由于并发执行的不确定性,导致出现的结果很多是我们不想要的,所以为了得到我们想要的结果就会在编写多线程的时候加入各种锁,其中最重要的就是synchronized和volatile。在认识它们之前先从多线程最基本的开始。
目录
一:线程的创建
1.基于类继承Thread创建线程
2.基于实现Runnable接口的方式创建线程
3.基于匿名内部类继承Thread创建线程
4.基于匿名内部类实现Runnable接口创建线程
5.基于lambda表达式创建Runnable子类对象创建线程
6.基于线程池的方式创建多线程
7.基于实现Callable接口的方式创建线程
多线程中Thread的常见方法
多线程的特点
二:多线程的中断
中断一个线程有两种方式
通过共享的boolean类型的变量来中断线程
通过调用Thread里面的interrupt()方法来中断线程
interrupt()的两个行为
三:多线程的等待
join方法
join的行为
join(long millis)方法
四:多线程的休眠
sleep的本质
五:获取多线程实例
一:线程的创建
这里有一个面试题:线程的创建方式有几种,答案一共是7中。下面讲现阶段能接触到的创建方式
1.基于类继承Thread创建线程
public class Thread01 {public static void main(String[] args) {Mythod mythod = new Mythod();Thread thread = new Thread(mythod);thread.start();}
}
class Mythod extends Thread{@Overridepublic void run() {while(true){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("你好");}}
}
2.基于实现Runnable接口的方式创建线程
public class Thread02 {public static void main(String[] args) {t t = new t();Thread thread = new Thread(t);thread.start();}
}
class t implements Runnable{@Overridepublic void run() {while(true){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("再将");}}
}
3.基于匿名内部类继承Thread创建线程
public class Thread03 {public static void main(String[] args) {Thread thread = new Thread() {@Overridepublic void run() {while(true){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("背景");}}};thread.start();}
}
4.基于匿名内部类实现Runnable接口创建线程
public class Thread04 {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {while(true){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("李华");}}}).start();}
}
5.基于lambda表达式创建Runnable子类对象创建线程
public class Thread05 {public static void main(String[] args) {new Thread(()->{while(true){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("韩梅梅");}}).start();}
}
6.基于线程池的方式创建多线程
public class Thread28 {public static void main(String[] args) {//使用以下标准库中的线程池//先创建一个线程池的实例ExecutorService service = Executors.newFixedThreadPool(10);//给实例里面加入一些任务for (int i = 0; i < 10; i++) {service.submit(new Runnable() {@Overridepublic void run() {System.out.println("你好");}});}}
}
7.基于实现Callable接口的方式创建线程
public class Callable2 {public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 0; i < 5000; i++) {sum++;}return sum;}};FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread t = new Thread(futureTask);t.start();int ret = futureTask.get();System.out.println(ret);}
}
多线程中Thread的常见方法
getName() 获得线程的名字(在创建线程的时候可以给线程起名字)
isDaemon() 判断线程是否为守护线程(判断是否为后台线程)
isInterrupted 判断线程是否被中断
public class Thread06 {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {}}, "我的线程");thread.start();System.out.println(thread.getName());System.out.println(thread.isDaemon());System.out.println(thread.isInterrupted());}
}
setDaemon() 将线程设置为后台线程
一个线程被创建出来默认就是前台线程,前台线程会阻止进程的结束,而后台线程则不会,后台线程一结束进程就结束了,因此可以将前台线程设置为后台线程,从而提高程序的执行速度。
public class Thread06 {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while(true){System.out.println("你好");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});thread.start();System.out.println("程序已经结束");}
}
这里先出的“程序已经结束”再输出的“你好”体现出来多线程并发执行的不确定性,这个不确定性和操作系统的实现相关。可以看到进程并没有结束,原因是应为thread是一个前台线程,进程只有等待所有前台线程都结束以后才会结束。为了结束进程,只需要在开启thread线程之前将它设置为后台线程即可。
thread.setDaemon(true);
多线程的特点
1.每个线程都是一个单独的执行流
2.每个线程之间是“并发”执行的,每个线程之间的执行顺序和操作系统的具体实现有关
3.多个线程执行的时间不是一个线程的1/n的原因是因为:创建线程自身也是有开销的,多个线程cpu上不一定是纯并行,也可能并发执行
面试题:请分别从方法及运行结果说明Thread类中run和start的区别
- 方法的区别:直接调用run方法,并没有创建新的线程,而只是之前的线程中(主线程),执行了run里面的内容;使用start则是创建了新的线程,新的线程里面调用的run方法。
- 运行结果的区别:如果直接运行run方法,程序会等待run方法(方法里面写了个死循环)执行完毕以后才会执行其他线程,但是运行start则是线程之间的并发执行,输出的结果也是交替出现的。
- run方法可以多次调用,而start方法只能被调用一次
class MyThread extends Thread{@Overridepublic void run() {while(true){System.out.println("再见");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class Thread01 {public static void main(String[] args) {MyThread thread = new MyThread();thread.start();while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("你好");}}
}
运行的结果:
class MyThread extends Thread{@Overridepublic void run() {while(true){System.out.println("再见");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class Thread01 {public static void main(String[] args) {MyThread thread = new MyThread();thread.run();while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("你好");}}
}
运行结果:
二:多线程的中断
中断一个线程有两种方式
通过共享的boolean类型的变量来中断线程
中断线程就是让线程尽快将入口方法执行结束(入口方法:继承Thread重写run、实现Runnable接口重写run)
public class Thread07 {private static boolean flag = true;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(){@Overridepublic void run() {while(flag){try {System.out.println("你好");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};thread.start();Thread.sleep(3000);flag = false;System.out.println("线程已经被中断");}
}
通过调用Thread里面的interrupt()方法来中断线程
public class Thread08 {public static void main(String args[]) throws InterruptedException {Thread thread = new Thread(){@Overridepublic void run(){while(!Thread.currentThread().isInterrupted()){try {System.out.println("你好");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};thread.start();Thread.sleep(3000);thread.interrupt();System.out.println("线程被中断");}
}
在分析结果之前,先明白下面d的方法:
Thread.currentThread().isInterrupted()方法
上面的这个方法是Thread类里面的静态方法,Thread.currentThread()表示获取当前线程的实例也就是线程对象。isInterrupted()方法为修改线程内置的标志位,它默认的结果是false,因此在while里面使用时注意取反。
interrupt()的两个行为
1. 如果thread线程没有处于阻塞状态,此时的interrupt就会修改内置的标志位
2.如果thread线程正处于阻塞状态,此时的interrupt就让线程内部产生阻塞的方法,在这个例子中就是sleep方法抛出异常
因此我们看到的结果首先是thread里面执行了三秒,然后调用interrupt方法,此时thread线程刚好处于阻塞状态,因此就让sleep方法抛出异常。但是由于catch语句里面并没有退出的逻辑,所以thread还会一直执行
为了看到interrupt()方法的打断效果,有两种方式:
第一种就是不要interrupt()方法前面的休眠操作,因为多线程是抢占式的执行,一旦让interrupt()方法成功将while里面的标志位变为false则thread就退出了。
public class Thread {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while(!Thread.currentThread().isInterrupted()){//isInterrupted()本身是falseSystem.out.println("线程运行中....");}}});thread.start();System.out.println("线程中断");thread.interrupt();}
}
第二种就是在catch代码块中写退出的逻辑
第二种方式是我们主要使用的,因为我们可以在catch代码块中写相应的逻辑语句,可以让thread线程立即退出,也可以让它待会退出或者是永远不退出。
三:多线程的等待
因为多线程的执行顺序是不确定的,为了让某一个线程有明确的的执行顺序,这里可以使用线程等待机制。
join方法
join方法主要用来等待线程结束
现在要求t1线程先执行,然后t2线程在执行:看如下代码
public class Thread09 {public static void main(String[] args) {System.out.println("main_beg");Thread t1 = new Thread(()->{System.out.println("t1_beg");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t1_end");});Thread t2 = new Thread(()->{System.out.println("t2_beg");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2_end");});t1.start();t2.start();System.out.println("main_end");}
}
可以看出代码的执行顺序是乱的,这样的结果往往是我们在编程中不想要的,我们作为编程者,需要的就是确定的结果。
为了获得准确的结果就要引入join,方法就是分别在t1.start()和t2.start()下面加入t1.join()、t2.join()
join的行为
1.如果被等待的线程还没执行完就阻塞等待
2.如果被等待的线程已经执行完了,直接就返回
3.main的阻塞等待时间是所有线程执行时间的总和
join(long millis)方法
和join一样,只不过这是有时间的等待,等不到就直接走了。
四:多线程的休眠
这个也已经在前面多次看到了,方法就就不讲了,主要讲一下sleep的本质。
sleep的本质
和sleep一样的让线程等待的方法还有wait/join/等待锁...它们的本质是把线程在PCB中的队列从就绪状态移到阻塞状态。时间到了或者出发了中断就会有回到就绪队列。
五:获取多线程实例
Thread.currentThread获得当前线程的实例
可以应用到下面三种形式的线程中:
public class Thread10 {public static void main(String[] args) {Thread thread = new Thread(){@Overridepublic void run(){System.out.println(Thread.currentThread().getId());System.out.println(this.getId());}};thread.start();}
}
结果都是12。如果是继承Thread方式创建的继承,Thread.currentThread获得的对象实例就是当前的对象thread,因此可以用this来代替
但是利用实现Runnable接口和创建lamdba的方式创建线程就不能用this,this得到的不是当前对象的实例。