多线程指的是一个程序中包含两个或者两个以上的线程,多线程的提出是为提高代码的执行效率,这就好比工厂中的流水线,只有一条称为单线程,有多条流水线就称为多线程。多线程提高效率的同时由于并发执行的不确定性,导致出现的结果很多是我们不想要的,所以为了得到我们想要的结果就会在编写多线程的时候加入各种锁,其中最重要的就是synchronized和volatile。在认识它们之前先从多线程最基本的开始。
目录
5.基于lambda表达式创建Runnable子类对象创建线程
通过调用Thread里面的interrupt()方法来中断线程
一:线程的创建
这里有一个面试题:线程的创建方式有几种,答案一共是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{
@Override
public 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{
@Override
public 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() {
@Override
public 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() {
@Override
public 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() {
@Override
public 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>() {
@Override
public 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() {
@Override
public 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() {
@Override
public 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{
@Override
public 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{
@Override
public 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(){
@Override
public 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(){
@Override
public 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() {
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){//isInterrupted()本身是false
System.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(){
@Override
public 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得到的不是当前对象的实例。