Java并发编程系列07:多线程的实现方式:继承Thread类、实现Runnable接口或Callable接口

最近开始了解多线程,发现内容太多,那就一点一点来吧。先了解最基础的,多线程有几种实现方式?从网上了解到,多线程有3种实现方式。

 

一、多线程的实现方式有

1、继承Thread类、

2、实现Runnable接口

3、Callable接口

来个小例子,具体实现如下:

1、继承Thread类

通过继承 Thread 类,并重写它的 run 方法,我们就可以创建一个线程。

(线程创建后,使用start()方法才是启动一个新的线程,不能直接调用Thread子类中重写的方法run()。)

  • 首先定义一个类来继承 Thread 类,重写 run 方法。
  • 然后创建这个子类对象,并调用 start 方法启动线程。
// 定义一个继承了Thread的类
class MyThread extends Thread{
    private static int ticket =10;
    // 重写run方法,实现自己的业务逻辑
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (this.ticket > 0) {
               System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
            }
        }
    }
}
public class ThreadDemo{
    public static void main(String[] args){
        // 直接 new 一个Thread对象,调用Thread对象的 start 方法,注意不能调用 run 方法
        new MyThread().start();
    }
}

此处用的是1个线程来调用。

new MyThread().start();

Java并发编程系列07:多线程的实现方式:继承Thread类、实现Runnable接口或Callable接口_第1张图片

如果多个线程,那么多创建几个子类对象。

new MyThread().start();
new MyThread().start();
new MyThread().start();

2、实现Runnable接口

通过实现 Runnable ,并实现 run 方法,也可以创建一个线程。

  • 首先定义一个类实现 Runnable 接口,并实现 run 方法。
  • 然后创建 Runnable 实现类对象,并把它作为 target 传入 Thread 的构造函数中
  • 最后调用 Thread对象的 start 方法启动线程。
 // 定义一个实现了 Runnable 接口的类
class RunnableTest3  implements Runnable{
    // 重写run方法,实现自己的业务逻辑
    private int ticket =10;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (this.ticket > 0) {
                System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
            }
        }
    }
}

public class RunnableDemo3 {
    public static void main(String[] args){
        //创建Runnable 对象
        RunnableTest3 rt=new RunnableTest3();
        // 将一个 Runnable对象作为Thread的构造参数
        //调用Thread对象的 start 方法,注意不能调用 run 方法
        new Thread(rt).start();
    }
}

此处用的是1个线程来调用。

new Thread(new RunnableTest3()).start();

Java并发编程系列07:多线程的实现方式:继承Thread类、实现Runnable接口或Callable接口_第2张图片

 如果多个线程,那么多创建几个执行任务的线程(各干各的:每个线程各卖10张票)。

new Thread(new RunnableTest3()).start();
new Thread(new RunnableTest3()).start();
new Thread(new RunnableTest3()).start();

 如果多个线程,那么多创建几个执行任务的线程(一起干:3个线程一起卖10张票)。

RunnableTest3 rt=new RunnableTest3();
new Thread(rt).start();
new Thread(rt).start();
new Thread(rt).start();

Java并发编程系列07:多线程的实现方式:继承Thread类、实现Runnable接口或Callable接口_第3张图片

3、实现Callable接口

Runnable接口的run方法是没有返回值的。当我们需要子线程运行结束后提供一个返回值时,就需要用到Callable接口。Callable的返回值支持泛型,但因为它的返回值是异步返回的,因此无法直接在主线程中获取返回值,而是配合Future接口或FutureTask类来获取返回值。

3.1、实现Callable接口(使用Future获取Callable返回值)(结合线程池)

  • 首先定义一个 Callable 的实现类,并实现 call 方法。call 方法是带返回值的。
  • 创建线程池对象,使用线程池来提交一个Callable任务对象。
  • 通过submit方法向线程池提交Callable任务对象,submit方法返回的是Future对象
  • 通过future.get()获取线程的运行结果。
import java.util.concurrent.*;

// 定义一个实现了 Callable 接口的类,并指定返回值类型
class CallableTask implements Callable {
    // 实现 call 方法,并返回指定类型的值
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100000; i++) {
            sum += i;
        }
        return sum;
    }
}

public class CallableTest {
    public static void main(String[] args) {
        // Callable实现类不能直接作为Thread构造参数传入,这里使用线程池来提交一个Callable任务
        ExecutorService exs = Executors.newSingleThreadExecutor();
        CallableTask ca=new CallableTask();
        // 通过submit方法向线程池提交Callable任务,submit方法返回的是Future对象
        Future future = exs.submit(ca);
        // 线程池不再接收新的任务
        exs.shutdown();

        try {
            // future.get()获取子线程的运行结果,如果子线程此时尚未运行结束,则主线程在该步骤会等待直到子线程结束返回结果
            System.out.println("和为:"+future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

和为:705082704

Future接口是用来获取目标线程执行结果的接口,通常和Callable一起通过线程池来使用。

3.2、实现Callable接口(使用FutureTask获取Callable返回值)(结合Thread)

 一、实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
 二、执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。  FutureTask 是  Future 接口的实现类(注意:FutureTask实现了Runnable接口和Future接口)

步骤:

  • 首先定义一个 Callable 的实现类,并实现 call 方法。call 方法是带返回值的。
  • 创建Callable子类的实例化对象
  • 创建FutureTask对象,并将Callable对象传入FutureTask的构造方法中
  • 实例化Thread对象,并在构造方法中传入FurureTask对象
  • 通过 FutureTask 的 get 方法获取线程的执行结果。

示例一:

import java.util.concurrent.*;

// 定义一个实现了 Callable 接口的类,并指定返回值类型
class CallableTask implements Callable {
   // 实现 call 方法,并返回指定类型的值
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100000; i++) {
            sum += i;
        }
        return sum;
    }
}
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Callable实现类不能直接作为Thread构造参数传入,而是需要包装一层FutureTask将其转为Runnable接口
        FutureTask task=new FutureTask<>(new CallableTask());
        Thread t = new Thread(task);
        t.start();
        try {
            // 在主线程中获取子线程执行结束后返回的结果,这里是LocalDateTime类型的时间戳。
            // 要注意的是,如果子线程此时尚未运行结束,则主线程执行futureTask.get()时会等待,一直到子线程结束返回结果。
            System.out.println("和为:"+task.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

FutureTask是一个实现类,它实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口和Future接口。所以FutureTask能够作为Thread的构造参数,同时也可以用来获取目标线程执行结果。Future本身只是接口,要实现它的方法比较复杂,而有了FutureTask就降低了使用Future的难度。Future要结合线程池来使用,而FutureTask既可以与线程池配合使用,也可以直接作为Thread的构造参数使用,更加方便。

运行结果:

和为:705082704

示例二(3个线程卖10张票)

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

// 定义一个实现了 Callable 接口的类,并指定返回值类型
class CallableTask2 implements Callable {
    private int ticket =10;
    // 实现 call 方法,并返回指定类型的值
    @Override
    public String call(){
        for (int i = 0; i < 10; i++) {
            if (this.ticket > 0) {
                System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
            }
        }
        return "票已经卖完"+Thread.currentThread().getName();
    }
}

public class CallableTest2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建Callable接口子类的对象
        CallableTask2 ct=new CallableTask2();
        // Callable实现类不能直接作为Thread构造参数传入,而是需要包装一层FutureTask将其转为Runnable接口
        FutureTask task1=new FutureTask<>(ct);
        FutureTask task2=new FutureTask<>(ct);
        FutureTask task3=new FutureTask<>(ct);
        //FutureTask对象作为Thread的构造参数,并调用Start()启动线程,同时也可以用来获取目标线程执行结果。
        new Thread(task1).start();
        new Thread(task2).start();
        new Thread(task3).start();
        System.out.println(task1.get());
        System.out.println(task2.get());
        System.out.println(task3.get());
    }
}

执行结果(3个线程一起执行卖10张票的任务。)

Java并发编程系列07:多线程的实现方式:继承Thread类、实现Runnable接口或Callable接口_第4张图片

3.3、实现Callable接口(使用FutureTask获取Callable返回值)(结合线程池例子1)

(3个线程各卖各的,每个卖10张)

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

// 定义一个实现了 Callable 接口的类,并指定返回值类型
class CallableTask2 implements Callable {
    private int ticket =10;

    private int id;
    public CallableTask2(int id){this.id =id;}

    // 实现 call 方法,并返回指定类型的值
    @Override
    public String call(){
        for (int i = 0; i < 10; i++) {
            if (this.ticket > 0) {
                System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
            }
        }
        return "这是Callable返回值--->票已经卖完,ticket:"+ticket+","+Thread.currentThread().getName();
    }
}

public class CallableTest2 {
    public static void main(String[] args) throws ExecutionException, 
        //结合线程池
        int threadNum=3;
        //创建一个拥有固定线程数的线程池对象
        ExecutorService exs = Executors.newFixedThreadPool(threadNum);

        List resultList = new ArrayList<>();//创建3个任务并执行
        for (int i = 0; i < threadNum; i++){
            //创建Callable接口子类的对象
            CallableTask2 ct=new CallableTask2(i);
            FutureTask future= new FutureTask<>(ct);
            //通过 ExecutorService 对象的 submit 方法执行任务
            exs.submit(future);
            //将任务执行结果存储到List中
            resultList.add(future);
        }
        exs.shutdown();

        for (FutureTask future:resultList){
            System.out.println(future.get());
        }
    }

}

执行结果:(3个线程各卖各的,每个卖10张)

Java并发编程系列07:多线程的实现方式:继承Thread类、实现Runnable接口或Callable接口_第5张图片

3.4、实现Callable接口(使用FutureTask获取Callable返回值)(结合线程池例子2)

(10张票3个线程一起卖)

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

// 定义一个实现了 Callable 接口的类,并指定返回值类型
class CallableTask2 implements Callable {
    private int ticket =10;
    // 实现 call 方法,并返回指定类型的值
    @Override
    public String call(){
        for (int i = 0; i < 10; i++) {
            if (this.ticket > 0) {
                System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
            }
        }
        return "这是Callable返回值--->票已经卖完,ticket:"+ticket+","+Thread.currentThread().getName();
    }
}

public class CallableTest2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //结合线程池
        //创建Callable接口子类的对象
        CallableTask2 ct2=new CallableTask2();
        ExecutorService exs = Executors.newFixedThreadPool(3);
        //
        FutureTask future1=new FutureTask<>(ct2);
        FutureTask future2=new FutureTask<>(ct2);
        FutureTask future3=new FutureTask<>(ct2);
        exs.submit(future1);
        exs.submit(future2);
        exs.submit(future3);
        System.out.println(future1.get());
        System.out.println(future2.get());
        System.out.println(future3.get());
        exs.shutdown();


    }
}

执行结果(10张票3个线程一起卖)

Java并发编程系列07:多线程的实现方式:继承Thread类、实现Runnable接口或Callable接口_第6张图片

二、通过线程池创建线程(Runnable接口+Executors结合)

  • 首先,定义一个 Runnable接口的实现类,重写 run 方法。
  • 创建1个任务对象
  • 然后创建一个拥有固定线程数的线程池对象。
  • 最后通过 ExecutorService 对象的 execute 方法将任务添加到线程去执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//定义一个 Runnable接口的实现类
class RunnableTest  implements Runnable{
    private int ticket =10;
    // 重写run方法,实现自己的业务逻辑
    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            if (this.ticket > 0) {
                System.out.println("票号:" + ticket-- + ",已被售卖,售卖窗口:" + Thread.currentThread().getName());
            }
        }

    }
}

public class RunnableDemo {
    public static void main(String[] args){
        //创建1个任务对象
        RunnableTest rt=new RunnableTest();
        int threadNum=3;
        //创建一个拥有固定线程数的线程池对象
        ExecutorService exs = Executors.newFixedThreadPool(threadNum);
        for(int i=0;i

执行结果(3个线程一起执行卖10张票的任务。)

此处存在线程安全问题,有2张票两个线程同时卖...线程安全的锁控制,之后学习参考:

1.Java(75):多线程学习08-->CyclicBarrier+Runnable),实现线程安全

2.

Java并发编程系列07:多线程的实现方式:继承Thread类、实现Runnable接口或Callable接口_第7张图片

总结:
创建线程有下面几种方式:
1.继承Thread类:定义子类继承Thread类,创建这个子类对象,子类对象调用start(),本质上也是调用Thread的的 start 方法来启动线程。
2.实现 Runnable 接口:调用的是 Thread 本类的start 方法,而 start 方法最终会调用 run 方法,会把创建的 Runnable 实现类对象赋值给 target ,并运行 target 的 run 方法()
3. 实现Callable接口结合 Future 和 FutureTask 的方式:Future方式使用线程池,FutureTask 也是通过 new Thread(task) 的方式构造 Thread 类,调用start()启动线程。
4. 线程池创建线程:把创建和管理线程的任务都交给了线程池。而创建线程是通过线程工厂类 DefaultThreadFactory 来创建的(也可以自定义工厂类),最后也是通过 new Thread() 的方式来创建线程。

学习以下博客

1、Java(72):多线程学习05-->解决多线程安全—synchronized

https://blog.csdn.net/fen_fen/article/details/121466128

2、Java(73):多线程学习06-->解决多线程安全-Lock

https://blog.csdn.net/fen_fen/article/details/121470551

3、Java(76)多线程学习07:Lock之ReentrantLock(1)

https://blog.csdn.net/fen_fen/article/details/121497940

 4、Java(75):多线程学习08-->了解CyclicBarrier

https://blog.csdn.net/fen_fen/article/details/121473092

参考:Java多线程梳理之一_多线程开发入门

https://zhuanlan.zhihu.com/p/350440753

多线程在面试中基本上已经是必问项,你准备好了吗?

https://zhuanlan.zhihu.com/p/268337270

你可能感兴趣的:(java相关,#,JAVA多线程编程,git,java,github)