多线程之线程创建
一 ,线程创建
1.创建方式一
Java为开发者提供了一个类叫做Thread,此类的对象用来表示线程。创建线程并执行线程的步骤如下:
1.定义一个子类继承Thread类
2.再重写父类的run方法
3.创建Thread的子类对象
4.调用start方法启动线程(启动线程后,会自动执行run方法中的代码)
代码如下:
//1.定义一个子类继承thread类
public class MyThread extends Thread{
//2.重写父类的run方法
@Override
public void run() {
//描述线程的执行任务
for (int i = 1; i <= 5; i++) {
System.out.println("子线程MyThread输出" + i);
}
}
//再定义一个测试类
public class ThreadTest1 {
// main方法是由一条默认的主线程负责执行。
public static void main(String[] args) {
//3.创建MyThread的对象代表一个线程
Thread t = new MyThread();
//4.启动线程(自动调用run方法)
t.start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程Thread输出:" + i);
}
}
}
打印结果如下:
注意事项:
1.主线程和子线程是在抢占CPU的执行权,每次打印结果都会有所不同,因为主线程和子线程谁先执行是我们无法控制的。
2.启用线程必须是调用start方法而不是调用run方法,因为直接调用run方法会当成普通方法执行,此时还是相当于单线程执行,只有调用start方法才是启动一个新的线程执行。
此图为调用run方法的执行结果
3.不要把主线程任务放在子线程任务之前,若是这样主线程一直是先跑完的,也相当于单线程的执行效果。
此图为主线程任务放在子线程任务之前的执行结果
创建方式一的优缺点:
1.优点:编码便捷
2.缺点:线程类已经继承Thread,无法再继承其他的类了,不利于功能的扩展。
2.创建方式二
Java为开发者提供了一个Runnable接口,该接口中只有一个run方法,意思就是通过Runnable接口的实现类对象专门来表示线程要执行的任务。具体步骤如下:
1.定义一个线程任务类MyRunnable实现Runnable接口,
2.重写run方法(这里面就是线程要执行的代码)
2.再创建一个Runnable任务对象
3.把MyRunnable任务对象交给Thread处理
4.调用Thread对象的start()方法启动线程(启动后会自动执行Runnable里面的run方法)
代码如下:
//1.定义一个任务类实现Runnable接口
public class MyRunnable implements Runnable{
//2.重写Runnable接口的run方法
@Override
public void run() {
//线程要执行的任务
for (int i = 1; i <= 5; i++) {
System.out.println("子线程输出===》" + i);
}
}
}
//再定义一个测试类
public class ThreadTest2 {
public static void main(String[] args) {
//3.创建任务类的对象
Runnable target = new MyRunnable();
//4.把任务对象交给线程对象处理,然后再启动线程
// public Thread(Runnable target),因为有构造方法,可以直接传递target
new Thread(target).start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程输出===》" + i);
}
}
打印结果如下:
创建方式二的优缺点:
1.优点:线程任务类实现了接口,便于功能的扩展。
2.缺点:如果线程有执行结果是不能直接返回的。
3.创建方式二改进—匿名内部类
可以直接创建Runnable接口的匿名内部类对象,传递给Thread对象。
代码如下:
public class ThreadTest3 {
public static void main(String[] args) {
//直接创建 Runnable接口的匿名内部类形式(任务对象)
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程1输出===》" + i);
}
}
};
new Thread(target).start();
//简化形式1
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程2输出===》" + i);
}
}
}).start();
//简化形式2,因为Runnable接口是函数式接口,故可用lambda表达式简化
new Thread(()-> {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程3输出===》" + i);
}
}).start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程main输出===》" + i);
}
}
}
打印结果如下:
4.创建方式三
前面两种线程创建方式虽然简单便捷,但都有一种局限性,就是线程执行执行完毕后如果有数据需要返回,前面两种方式都返回不了,因为重写的run方法是无返回值类型。所以Java在JDK5中提供了Callable接口和FutureTask类来创建线程,它最大的优点就是有返回值。在Callable接口中有一个call方法,重写call方法就是线程要执行的代码,它是有返回值的。步骤如下:
1、创建任务对象
定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。
把Callable类型的对象封装成FutureTask(线程任务对象)。
2、把线程任务对象交给Thread对象。
3、调用Thread对象的start方法启动线程。
4、线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。
代码如下:
//1.定义一个类实现Callable接口
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n){
this.n = n;
}
//2.重写call方法
@Override
public String call() throws Exception {
//描述线程的任务,返回线程执行后的结果
//求1-n的和返回
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += i;
}
return "线程求出1-" + n + "的结果是:" + sum + "";
}
//再定义一个测试类
public class ThreadTest4 {
public static void main(String[] args) throws Exception{
//3.创建一个Callable的对象
Callable<String> callable = new MyCallable(100);
//4.把Callable的对象封装成一个FutureTask对象(任务对象)
//未来任务对象的作用?
//(1):是一个任务对象,实现了Runnable接口
//(2):可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。
FutureTask<String> futureTask = new FutureTask<>(callable);
//5.把任务对象交给一个Thread对象
new Thread(futureTask).start();
Callable<String> callable1 = new MyCallable(200);
FutureTask<String> futureTask1 = new FutureTask<>(callable1);
new Thread(futureTask1).start();
//6.获取线程执行完毕后返回的结果
//注意:如果执行到这儿,上面的线程还没执行完毕
//这里的代码会暂停,等待上面的线程执行完毕后才会获取结果
String s = futureTask.get();
System.out.println(s);
String s1 = futureTask1.get();
System.out.println(s1);
}
}
打印结果如下: