Java Runnable and Thread

本文详细介绍了Java线程机制的基本概念、线程的实质及线程接口Runnable与Thread的使用方法,探讨了线程调度、优先级设置以及线程池Executor的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

I. 简介

线程机制是开发中非常重要、也是非常复杂的一个环节. 那么为什么需要线程?
首先,在程序的执行中,不可避免地会遇到一些需要等待的任务,比如从数据库请求数据、做一些耗时操作等. 而Java 语言本身的任务处理机制是顺序控制流,也就是说,把所有任务排成一个队列,只有第一个任务执行完毕,第二个任务才能执行,第三个任务则要等第二个任务完成…
如果没有多线程的加入,那用户在使用程序时,就只能在某些时候等待一些操作完成,这势必会影响用户体验. 因此,线程的加入,大大地提升了程序的实用性,但同时也增加了程序设计的难度.

II. 线程实质

关于线程需要注意的一点是,即使是在多核处理器上运行,线程机制其实是在单个处理器上交替着完成不同的任务. 比如现在有Task A, Task B 和Task C;这三个任务现在用Java 的多线程机制来解决,情况将会是这样:CPU 给Task A分配了一个小时间块来执行Task A,如果Task A没有完成,那么CPU 现在也会转移到处理Task B上,并给Task B分配同样长度的时间块,并定时切换到处理Task C 去,然后再分给Task C 的时间结束后,再次返回处理Task A,如此循环,直到处理完所有的任务. 但因为CPU 自身的速度够快,会给人一种多个任务在同时执行的错觉.

III. Runnable

介绍完了线程内部的运作方法,下面首先会讲解Runnable 接口.
Runnable 接口自身是不参与线程分配和调度的,它自身的存在目的其实是规定某个线程内的任务要如何执行.

public class Runnables implements Runnable {
    private int countdown = 5;
    private int id;
    public Runnables(int id) {
        this.id = id;
    }

    public String status() {
        return id + " (" + (countdown > 0 ? countdown: "finished") 
                        + "), ";
    }
    @Override
    public void run() {
        while (--countdown > 0) {
            System.out.print(status());
        }
    }

    public static void main(String [] args) {
        for (int i = 0; i < 5; i++) {
            Runnables runnables = new Runnables(i);
            runnables.run();
            System.out.println();
        }
    }
}

在这个例子中,Runnables 执行了Runnable 接口,并重写了run()方法;某个task 需要执行的操作,便是写在run()的.
但由于Runnable 接口本身不具备分发和调用线程的能力,所以所使用的都是同一个线程,即系统分发给main()函数的线程,并且每个任务都是等上一个任务执行完后再执行的.
而这一点也可以从返回值中看出.

output://
0(4), 0(3), 0(2), 0(1), 
1(4), 1(3), 1(2), 1(1), 
2(4), 2(3), 2(2), 2(1), 
3(4), 3(3), 3(2), 3(1), 
4(4), 4(3), 4(2), 4(1), 

IV. Thread

如果说Runnable 接口是用来规定一个线程中的任务,那么Thread 类便是分发、调度线程的工具了.
在下面的示例中,使用了Thread 来执行 Runnables 类.

    public static void main(String [] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnables(i));
            thread.start();
            System.out.println();
        }
    }

在新建了一个Thread 类对象后,调用start()方法开始线程. 而每一个线程将会在什么时候运行是无法知道的. 打印台的输出便很好的说明了这一点.

output://
0(4), 0(3), 0(2), 0(1), 

1(4), 1(3), 1(2), 1(1), 2(4), 2(3), 2(2), 2(1), 
3(4), 3(3), 3(2), 3(1), 
4(4), 4(3), 4(2), 4(1), 

Executor

Executor 能代替开发者管理Thread 对象,从而简化开发过程. 它是客户端与任务执行之间的一个简介层.

    public static void main(String [] args) {
        ExecutorService ex = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) 
            ex.execute(new Runnables(i));
        ex.shutdown();
    }

通过创建Executor 的对象,并调用execute() 方法,就可以启动一个线程了. 而shutdown()在调用后,将不可通过ex这个对象创建新的线程了.
同时,这里获得ExecutorService 对象的,是Executors 的方法. 通常会使用newCachedThreadPool(),但如果需要规定线程数量,可调用newFixedThreadPool()并传入参数指定线程数,或者调用newSingleThreadExecutor() 返回只能启动一条线程的Executor.

V. Callable

如果需要任务在完成后,返回一个值,就需要使用Callable 而不是Runnable 接口了. Callable 接口需要执行call()方法,并返回一个值.

public class Callabel implements Callable<String> {
    private static int count = 0;
    private int id = ++count;

    public Callabel() {}

    @Override
    public String call() throws Exception {
        return Integer.toString(id);
    }

    public static void main(String [] args) {
        ExecutorService ex = Executors.newCachedThreadPool();
        ArrayList<Future<String>> resutls = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            resutls.add(ex.submit(new Callabel()));
        }
        for (Future<String> result: resutls) {
            try {
                // get() blocks until completion
                System.out.println(result.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            finally {
                ex.shutdown();
            }
        }
    }
}

这里返回了一个String 对象,并通过类型为Future< String >的数组把结果保存下来了. 也就是说,如果想要获得Callable 返回的数值,需要一个Future 对象来保存,并通过get()方法来获取值.

VI. Thread 其他方法

TimeUnit sleep() 休眠

使用TimeUnit 的sleep() 方法可以让线程休眠,时间取决于参入的参数.

public class Sleep implements Runnable{
    private static int count = 0;
    private final int id = ++count;

    @Override
    public void run() {
        try {
            System.out.print(id + "a  ");
            TimeUnit.SECONDS.sleep(2);
            System.out.print(id + "b  ");
            System.out.println();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String [] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executor.execute(new Sleep());
        }
    }
}

此处,每个线程会休眠2秒.

Thread.yield() 让步

让步的意思是说:我的任务执行完了,现在我将CPU让步给其他线程.

public class Yield implements Runnable{
    private static int count = 0;
    private final int id = ++count;

    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
            if (sum > 10) {
                System.out.println("id:" + id + " ==> " + sum);
                Thread.yield();
            }
        }
    }

    public static void main(String [] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executor.execute(new Yield());
        }
    }
}

output://
id:1 ==> 15
id:3 ==> 15
id:2 ==> 15
id:4 ==> 15
id:5 ==> 15
id:4 ==> 21
id:3 ==> 21
id:1 ==> 21
id:4 ==> 28
id:5 ==> 21
id:2 ==> 21
id:5 ==> 28
id:2 ==> 28
id:5 ==> 36
id:4 ==> 36
id:1 ==> 28
id:3 ==> 28
id:4 ==> 45
id:5 ==> 45
id:2 ==> 36
id:3 ==> 36
id:1 ==> 36
id:3 ==> 45
id:1 ==> 45
id:2 ==> 45

每个线程在启动后,开始执行加法运算. 当sum大于10时,线程会暂停当前任务,将CPU 让给下一个线程. 所以从前5个输出中可以看到,线程都是在sum 到达15后暂停的进程.

Thread setPriority() 优先级

尽管各个线程的执行是随机的,但CPU 总会被优先分配给优先级最高的线程执行任务.

public class Priority implements Runnable {
    private static int count = 0;
    private final int id = ++count;

    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
            if (sum > 10) {
                System.out.println("id:" + id + " ==> " + sum);
                Thread.yield();
            }
        }
    }

    public static void main(String [] args) {
        for (int i = 1; i <= 10; i++) {
            Thread thread = new Thread(new Priority());
            thread.setPriority(i);
            thread.start();
        }
    }
}

output://
id:1 ==> 15
id:4 ==> 15
id:1 ==> 21
id:6 ==> 15
id:3 ==> 15
id:2 ==> 15
id:6 ==> 21
id:7 ==> 15
id:1 ==> 28
id:4 ==> 21
id:10 ==> 15
id:5 ==> 15
id:10 ==> 21
id:4 ==> 28
id:1 ==> 36
id:7 ==> 21
id:6 ==> 28
id:9 ==> 15
id:3 ==> 21
id:2 ==> 21
id:8 ==> 15
id:2 ==> 28
id:9 ==> 21
id:3 ==> 28
id:6 ==> 36
id:7 ==> 28
id:1 ==> 45
id:4 ==> 36
id:10 ==> 28
id:5 ==> 21
id:4 ==> 45
id:7 ==> 36
id:6 ==> 45
id:3 ==> 36
id:9 ==> 28
id:2 ==> 36
id:8 ==> 21
id:9 ==> 36
id:3 ==> 45
id:7 ==> 45
id:5 ==> 28
id:10 ==> 36
id:5 ==> 36
id:9 ==> 45
id:8 ==> 28
id:2 ==> 45
id:8 ==> 36
id:5 ==> 45
id:10 ==> 45
id:8 ==> 45

线程优先级按照1 到10排列(与平台有关),1 的优先级最高.
通常在操作平台未知的情况下,会使用Thread.MAX_PRIORITY, Thread.MIN_PRIORITY, 和 Thread.NORM_PRIORITY.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值