欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java多线程创建方式和使用记录

程序员文章站 2022-05-05 10:06:12
...

前言

项目中涉及一些线程使用的改造,在使用修改线程的过程中,踩了不少坑.在此归纳总结一下,方便日后避开这些坑.

java使用线程的方法

1.继承Thread类
2.实现Runnable接口
3.使用ExecutorService、Callable、Future实现

其中 1和 2两种方式创建的线程 执行结束都没有返回值,
执行 run方法不可以抛出异常
3是创建线程 有返回值、可以抛出异常.

1.继承Thread类

代码示例:

package com.fl.thread;

public class TestThread  extends Thread{
    public void run(){
        super.run();
        System.out.println("子线程执行了--------");
    }
}

public class Test {
    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        testThread.start();
        System.out.println("主线程执行----------");
    }
}

这种线程的使用方式 本质上其实 也是实现Runnable接口的
一个实例,它代表一个线程的实例,启动线程的唯一方法是
通过Thread 类的start 方法,start 方法时一个原生方法,
它会启动一个新线程,并且执行run()方法. 这种的局限性是 使用继承 Thread 类的方式创建线程时,是不支持多继承.
为了实现多继承要实现Runnable 接口.
执行的结果:
Java多线程创建方式和使用记录
注意: 在使用多线程时,运行结果与调用顺序是无关的(这里的结果不代表线程的执行顺序,线程是并发执行的,如果多运行几次,打印顺序可能会不一样。多线程的运行过程中,CPU是以不确定的方式去执行线程的,故运行结果与代码的执行顺序或者调用顺序无关)。
调用run()方法 知识普通的方法调用(main方法中应该调用的是myThread的start方法,而不是run()方法。调用start()方法是告诉CPU此线程已经准备就绪可以执行,进而系统有时间就会来执行其run()方法。而直接调用run()方法,则不是异步执行,而是等同于调用函数般按顺序同步执行,这就失去了多线程的意义了。),不会启动线程.如果多次
调用 start 方法,会抛出 IllegalThreadStateException 异常.

2.实现Runnable 接口

这种方法用的比较多,就是把继承Thread类改为实现Runnable接口.
代码示例:

package com.fl.thread;

public class MyRunnable  implements Runnable{
    public void run() {
        System.out.println("执行子线程------");
    }
}

//测试
public class TestRunnable {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        System.out.println("主线程运行结束--------");
    }
}

运行结果:
Java多线程创建方式和使用记录
这里我们可看出 main中可以看到真正创建新线程还是通过Thread创建:
Thread thread = new Thread(runnable);
这一步Thread类的作用就是把run()方法包装成线程执行体,然后依然通过start去告诉系统这个线程已经准备好了可以安排执行。

3.使用ExecutorService、Callable、Future实现

ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类. 要了解这框架 大家可以看下并发编程 的书籍.
实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread.

Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕.

代码示例:

package com.fl.thread;

import java.util.concurrent.Callable;

public class MyCallable  implements Callable<Integer> {
    public Integer call() throws Exception {
        System.out.println("获取当前线程的名称"+Thread.currentThread().getName()+
                "当前线程的Id"+Thread.currentThread().getName());
        int count = 0;
        for (int i = 0; i <= 200000; i++) {
            count += i;
        }
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId()+"end---");
        return count;
    }
}

//测试
public class TestMyCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 将Callable包装成FutureTask,FutureTask也是一种Runnable
        MyCallable callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
        new Thread(futureTask).start();

        // get方法会阻塞调用的线程
        Integer sum = futureTask.get();
        System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
    }
}

Callable它也是一种函数式接口:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

上边测试中用到了 FutureTask

它的用法

public class FutureTask<V> implements RunnableFuture<V> {
	// 构造函数
	public FutureTask(Callable<V> callable);
	
	// 取消线程
	public boolean cancel(boolean mayInterruptIfRunning);
	// 判断线程
	public boolean isDone();
	// 获取线程执行结果
	public V get() throws InterruptedException, ExecutionException;
}

FutureTask 实现 RunnableFuture

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

比较 总结一下 这三种方式

Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
Runnable: 实现接口,比Thread类更加灵活,
没有单继承的限制
Callable: Thread和Runnable都是重写的run()方法
并且没有返回值,Callable是重写的call()方法并且
有返回值并可以借助FutureTask类来判断线程是否已经
执行完毕或者取消线程执行
当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到
Thread类中,一般通过Thread类来启动线程
Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,
RunnableFuture继承Runnable,所以Callable
也算是一种Runnable,
所以三种实现方式本质上都是Runnable实现

关系 ExecutorService 用法
ExecutorService是Java提供的线程池,也就是说,每次我们需要使用线程的时候,可以通过ExecutorService获得线程。它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能,也不用使用TimerTask了。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

所有的线程池 最终都是通过这方法来创建的.
详细的可见
https://blog.csdn.net/weixin_43975771/article/details/108034443

相关标签: 多线程 高并发