目录
引言
多线程的创建
方式一:继承Thread类
方式二: 实现Runnable接口
方式三:实现Callable接口
总结
常用方法
Thread获取和设置线程名称
Thread类的线程休眠方法
引言
经过前面的学习,我们已经了解到了IO流的知识,那么今天我们就将要开始学习Java中的线程,首先我们应该理解“线程”是什么意思?线程(thread)是一个程序内部的一条执行路径,我们所熟悉的main方法其实就是一条单独执行路径,若程序中只有一条执行路径那么这个程序就是单线程程序;既然有单线程,那么也相对的会有多线程,字面意思可以理解为“相对单线程从软硬件上多条执行流程的技术”,多线程的好处是提高CPU的利用率。 在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,大大提高程序的效率。好了,废话不多说开始今天的学习吧!
多线程的创建
方式一:继承Thread类
方式一创建过程:
定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法;创建MyThread类的对象;调用线程对象的start()方法启动线程(启动后仍然执行run()方法);public class ThreadDemo01 { public static void main(String[] args) { MyThread myThread1 = new MyThread(); myThread1.start(); for (int i = 0; i < 3; i++) { System.out.println("主线程正在执行~~"); } }}class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println("子线程正在执行~~"); } }}//输出结果(不唯一)://主线程正在执行~~//主线程正在执行~~//主线程正在执行~~//子线程正在执行~~//子线程正在执行~~//子线程正在执行~~
在以上代码中共有两个线程在执行,分别是main方法的主线程和线程对象mythread调用start()启动的子线程。不过输出结果为什么会不唯一的呢?原因是因为两个线程在执行过程中会发生CPU的抢占,谁先抢到谁将优先执行。
那么我们为什么不直接使用线程对象调用run()方法呢?若直接调用run()则只是普通的调用方法,即单线程,而start()方法则是用来启动的子线程的,由此才能出现多线程。
方式一优缺点:
优点:编码简单;缺点:线程类已经继承Thread,无法继承其他类,不利于扩展;方式二: 实现Runnable接口
方式二创建过程:
1、定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法;
2、创建MyRunnable对象;
3、把MyRunnable任务对象交给Thread处理;
4、调用线程对象的start()方法启动线程;
Thread构造器 | 方法 |
public Thread (String name) | 可以为当前线程指定名称 |
public Thread (Runnable target) | 封装Runnable对象成为线程对象 |
public Thread (Runnable target ,String name) | 封装Runnable对象成为线程对象,并指定线程名称 |
public class ThreadDemo02 { public static void main(String[] args) { MyRunnable target = new MyRunnable(); Thread thread = new Thread(target); thread.start(); for (int i = 0; i < 3; i++) { System.out.println("主线程正在执行~~"); } }}class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println("子线程正在执行~~"); } }}//输出结果(不唯一)://主线程正在执行~~//子线程正在执行~~//子线程正在执行~~//子线程正在执行~~//主线程正在执行~~//主线程正在执行~~
该代码与方式一的不同之处在于需要将MyRunnable任务对象封装在Thread中,其他的地方是基本上是没有变化的。
方式二优缺点:
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。
接下来我们同样使用实现Runnable接口(匿名内部类形式)来实现多线程的创建:
1、创建Runnable匿名内部类对象;
2、交给Thread处理;
3、调用线程对象的start()启动线程;
//正常版:public class ThreadDemo01 { public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println("子线程正在执行~~"); } } }); thread.start(); for (int i = 0; i < 3; i++) { System.out.println("主线程正在执行~~"); } }}//lambda简化版:new Thread(()-> { for (int i = 0; i < 3; i++) { System.out.println("子线程正在执行~~"); } }).start();
该种方法从本质上其实并没有太大的区别只不过是一个需要创建线程对象,而另一个则是通过匿名内部类实现的多线程。并且该块代码也可以通过lambda表达式进行精简,不知道大家是否还对这个知识点有印象呢?若忘记了可以看一下这篇文章:Java中的lambda表达式如何理解——精简
方式三:实现Callable接口
在学习过前面两种创建多线程的方式以后,我们会发现存在一个问题:1、重写的run()方法不能直接返回结果;2、不适合需要返回线程执行结果的业务场景。因此,我们需要第三种方式来解决这些问题。
方式三创建过程:
1、定义类实现Callable接口,重写call()方法,封装要做的事情;
2、用FutureTask把Callable对象封装成线程任务对象;
3、把线程任务对象交给Thread处理;
4、调用Thread的start()方法启动线程,执行任务;
5、线程执行完毕后,通过FutureTask的get()方法获取任务执行的结果。
方法名称 | 说明 |
public FutureTask<>(Callable call) | 把Callable对象封装成FutureTask对象 |
public V get() throws Exception | 获取线程执行call方法返回的结果 |
public class ThreadDemo03 { public static void main(String[] args) throws Exception { MyCallable myCallable = new MyCallable(); FutureTask<String> futureTask = new FutureTask<>(myCallable); Thread thread = new Thread(futureTask); thread.start(); int sum= 0; for (int i = 0; i < 3; i++) { sum+=i; } System.out.println(sum); String s =futureTask.get(); System.out.println(s); }}class MyCallable implements Callable<String > { @Override public String call(){ int sum=0; for (int i = 0; i < 3; i++) { sum+=i; } return "子线程计算结果:"+sum; }}//输出结果://3//子线程计算结果:3
方式三优缺点:
优点:
线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;
可以在线程执行完毕后去获取 线程执行的结果;
缺点:
编码复杂一点;
总结
方式 | 优点 | 缺点 |
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 扩展性较差,不能再继承其他的类,不能返回线程执行的结果 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能返回线程执行的结果 |
实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他的类,可以得到线程的执行结果 | 编程相对复杂 |
常用方法
Thread获取和设置线程名称
方法名称 | 说明 |
String getName() | 获取当前线程的名称,默认线程名称是Thread-索引 |
void setName(String name) | 将此线程更改为指定的名称,通过构造器也可以设置线程名称 |
简单地通过一段代码让大家能够清晰地了解这个代码该如何使用:
public class ThreadDemo04 { public static void main(String[] args) throws Exception { thread thread1 = new thread(); thread1.setName("1号子线程"); thread1.start(); thread thread2 = new thread(); thread2.setName("2号子线程"); thread2.start(); }}class thread extends Thread { @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(this.getName()+"正在执行任务"+i); } }}//输出结果://2号子线程正在执行任务0//1号子线程正在执行任务0//2号子线程正在执行任务1//1号子线程正在执行任务1//2号子线程正在执行任务2//1号子线程正在执行任务2
Thread类的线程休眠方法
方法名称 | 说明 |
public static void sleep(long time) | 让当前线程休眠指定的时间后再继续执行,单位为毫秒 |
public class ThreadDemo05 { public static void main(String[] args) throws Exception { for (int i = 0; i < 5; i++) { System.out.println(i); if (i==3){ Thread.sleep(5000); } } }}//输出结果://1//2//3//在输出过3以后,等待5秒之后再进行输出//4
创作不易,给个三连