美文网首页
Java多线程基础——创建线程的方式及常用API

Java多线程基础——创建线程的方式及常用API

作者: 文景大大 | 来源:发表于2020-03-04 20:53 被阅读0次

一、继承Thread

继承Thread的方式确实很简单,但是缺点也是很明显的:

  • 线程类只能单继承,无法再继承别的业务类;
  • 无法使用线程池;
@Slf4j
public class FruitThread extends Thread {
    private String fruitName;

    public FruitThread(String fruitName){
        this.fruitName = fruitName;
    }

    @Override
    public void run() {
        log.info("get fruit: {}", fruitName);
    }
}
@Slf4j
public class FruitTest {

    public static void main(String[] args) {
        Thread appleThread = new FruitThread("apple");
        Thread bananaThread = new FruitThread("banana");
        Thread orangeThread = new FruitThread("orange");

        log.info("开始执行线程...");
        appleThread.start();
        bananaThread.start();
        orangeThread.start();
        log.info("结束执行线程...");
    }
}

输出结果中可以看到,每个线程打印日志的线程编号是不同的,执行事件确实一样的。

11:27:34.297 [main] INFO thread.FruitTest - 开始执行线程...
11:27:34.301 [main] INFO thread.FruitTest - 结束执行线程...
11:27:34.301 [Thread-2] INFO thread.FruitThread - get fruit: orange
11:27:34.301 [Thread-0] INFO thread.FruitThread - get fruit: apple
11:27:34.301 [Thread-1] INFO thread.FruitThread - get fruit: banana

二、实现Runnable

Runnable的优点:

  • 可以实现多个类;
  • 可以使用线程池;
@Slf4j
public class FruitThread implements Runnable {
    private String fruitName;

    public FruitThread(String fruitName){
        this.fruitName = fruitName;
    }

    @Override
    public void run() {
        log.info("get fruit: {}", fruitName);
    }
}
@Slf4j
public class FruitTest {

    public static void main(String[] args) {
        Runnable appleRunnable = new FruitThread("apple");
        Runnable bananaRunnable = new FruitThread("banana");

        Thread appleThread = new Thread(appleRunnable);
        // 可以使用同一个Runnable再次创建一个线程
        Thread appleThread2 = new Thread(appleRunnable);
        Thread bananaThread = new Thread(bananaRunnable);

        log.info("开始执行线程...");
        appleThread.start();
        bananaThread.start();
        appleThread2.start();
        log.info("结束执行线程...");
    }
}

输出结果如下:

16:07:32.527 [main] INFO thread.FruitTest - 开始执行线程...
16:07:32.529 [main] INFO thread.FruitTest - 结束执行线程...
16:07:32.530 [Thread-1] INFO thread.FruitThread - get fruit: apple
16:07:32.530 [Thread-0] INFO thread.FruitThread - get fruit: apple
16:07:32.530 [Thread-2] INFO thread.FruitThread - get fruit: banana

三、实现Callable

Callable除了具有Runnable的优点之外,还有如下优点:

  • 能够返回线程的执行结果;
  • 允许向上层抛出异常,而Runnable是不允许的;
@Slf4j
public class FruitThread implements Callable<String> {
    private String fruitName;

    public FruitThread(String fruitName){
        this.fruitName = fruitName;
    }

    @Override
    public String call() {
        log.info("get fruit: {}", fruitName);
        return fruitName + " end";
    }
}
@Slf4j
public class FruitTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> appleCallable = new FruitThread("apple");
        FutureTask<String> appleTask = new FutureTask<>(appleCallable);

        Callable<String> orangeCallable = new FruitThread("apple");
        FutureTask<String> orangeTask = new FutureTask<>(orangeCallable);

        Thread appleThread = new Thread(appleTask);
        Thread orangeThread = new Thread(orangeTask);

        log.info("开始执行线程...");
        appleThread.start();
        // 获取线程执行结果,会阻塞主线程
        log.info("apple return : {}", appleTask.get());
        orangeThread.start();
        log.info("orange return : {}", orangeTask.get());
        // 如果需要获取子线程的执行结果,那主线程就会就等待该执行结果处理完成才执行
        log.info("结束执行线程...");
    }
}

当然,正式的生产上都是不允许直接这样创建线程的,而是使用线程池的方式,后面的文章会详细讲解。

四、线程间变量的共享

  • 线程独享变量
@Slf4j
public class MyThread implements Runnable {
    // 线程间独享变量
    private int count = 200;

    @Override
    public void run() {
        count--;
        log.info("当前线程名称:{},计数器为:{}", Thread.currentThread().getName(), count);
    }
}
  • 线程共享变量
@Slf4j
public class MyThread implements Runnable {
    // 线程间共享变量
    private static int count = 200;

    @Override
    public void run() {
        count--;
        log.info("当前线程名称:{},计数器为:{}", Thread.currentThread().getName(), count);
    }
}
  • 线程安全地共享变量
@Slf4j
public class MyThread implements Runnable {
    // 线程间共享变量
    private static int count = 200;

    @Override
    public synchronized void run() {
        count--;
        log.info("当前线程名称:{},计数器为:{}", Thread.currentThread().getName(), count);
    }
}

五、停止线程

  • 等待线程执行完毕,正常停止;
  • stop,已经废弃了,不建议使用,强制使用可能会造成不可预料的结果;
  • interrupt,建议使用。

关于interrupt的使用,需要做下详细介绍。

当我们在主线程中调用interrupt方法想停止子线程的时候,子线程并不是就立即停止了,而仅仅是给子线程打上了一个停止的标记。

@Slf4j
public class MyThread implements Runnable {
    private int count = 0;

    @Override
    public void run() {
        while(true){
            count++;
            log.info("当前线程名称:{},计数器为:{}", Thread.currentThread().getName(), count);
        }
    }
}
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyThread());
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
    }

我们运行main方法后发现,2秒后interrupt并没有使得子线程停止,还是在一直运行中。

那么如何使用interrupt才能停止一个线程呢?

我们使用interrupted或者isInterrupted来判断当前线程的状态,从而决定线程是否停止运行。

@Slf4j
public class MyThread implements Runnable {
    private int count = 0;

    @Override
    public void run() {
        while(true){
            // 此处也可以使用Thread.interrupted()
            if(Thread.currentThread().isInterrupted()){
                log.info("线程终止执行!");
                // 终止线程的执行
                return;
            }

            count++;
            log.info("当前线程名称:{},计数器为:{}", Thread.currentThread().getName(), count);
        }
    }
}

此时再次执行main方法,就能在2秒后停止线程的运行了。

需要改进的是,我们应该在检测到线程终止时抛出异常,而不是简单地return,这样的话,线程catch异常之后可以继续向外部抛出,供调用者进一步处理。

六、暂停和恢复线程

suspend和resume已经被废弃使用了。主要是因为如下两个原因:

  • 独占锁问题

    suspend方法暂停线程时,如果有锁的情况就不会释放锁,容易造成其他线程无法持有锁,从而就无法进入run方法。

  • 两个方法无法同步数据

    suspend方法和resume方法不是同步方法,可能造成脏读。

七、yield

将当前线程的CPU时间片让给其它线程,回到就绪状态等待分配CPU时间片继续执行,则个等待的时间是不可确定的。

八、线程优先级

线程的优先级总共分为1~10个级别,还有三个常量代表常用的优先级:

  • MIN_PRIORITY = 1
  • NORM_PRIORITY = 5
  • MAX_PRIORITY = 10

线程的优先级具有如下特性:

  • 继承性,子线程的优先级继承自父线程的优先级;
  • 规则性,优先级高的线程会大概率被CPU优先执行;
  • 随机性,优先级低的线程并不一定比优先级高的线程晚执行结束。

九、守护线程

可以通过setDaemon(true)来设置守护线程,当所有的用户线程都运行结束后,守护线程就会开始自动销毁。

相关文章

  • Java多线程基础——创建线程的方式及常用API

    一、继承Thread 继承Thread的方式确实很简单,但是缺点也是很明显的: 线程类只能单继承,无法再继承别的业...

  • Java后端知识体系

    基础重点(必须扎实) Java语言 语言基础 《Java核心技术》基础语法面向对象常用API异常处理集合IO多线程...

  • Java面试题总结(上)

    多线程、并发及线程的基础问题 1)Java 中能创建 volatile 数组吗?能,Java 中可以创建 vola...

  • Java 面试题及答案

    多线程、并发及线程的基础问题 1)Java 中能创建 Volatile 数组吗? 能,Java 中可以创建 vol...

  • 2016java面试题总结

    多线程、并发及线程的基础问题: 1)Java 中能创建 volatile 数组吗? 能,Java 中可以创建 vo...

  • Java程序员不得不会的124道面试题(含答案)

    多线程、并发及线程的基础问题 1)Java 中能创建 volatile 数组吗? 能,Java 中可以创建 vol...

  • 133道 Java 面试题及答案

    多线程、并发及线程的基础问题 1)Java 中能创建 volatile 数组吗? 能,Java 中可以创建 vol...

  • Java常见问题

    一、多线程、并发及线程的基础问题 1)Java 中能创建 Volatile 数组吗? 能,Java 中可以创建 v...

  • 【java基础】线程

    java基础 线程 参考详细:Java之多线程 一、创建线程 创建线程常见有两种方式,另外有两种新增方式 1.Th...

  • JAVA面试题 1

    1. 多线程、并发及线程的基础问题 1)Java 中能创建 volatile 数组吗?能,Java 中可以创建 v...

网友评论

      本文标题:Java多线程基础——创建线程的方式及常用API

      本文链接:https://www.haomeiwen.com/subject/iseblhtx.html