前面介绍了线程的基本用法,按理说足够一般的场合使用了,只是每次开辟新线程,都得单独定义专门的线程类,着实开销不小。注意到新线程内部真正需要开发者重写的仅有run方法,其实就是一段代码块,分线程启动之后也单单执行该代码段而已。因而完全可以把这段代码抽出来,把它定义为类似方法的一串任务代码,这样能够像调用公共方法一样多次调用这段代码,也就无需另外定义新的线程类,只需命令已有的Thread去执行该代码段就好了。

在Java中定义某个代码段,则要借助于接口Runnable,它是个函数式接口,唯一需要实现的只有run方法。之所以定义成函数式接口的形式,是因为要给任务方法套上面向对象的壳,这样才好由外部去调用封装好的任务对象。现在有个阶乘运算的任务,希望开个分线程计算式子“10!”的结果,那便定义一个实现了Runnable接口的任务类FactorialTask,并重写run方法补充求解“10!”的代码逻辑。编写完成的FactorialTask类代码示例如下:

// 定义一个求阶乘的任务

private static class FactorialTask implements Runnable {

@Override

public void run() {

int product = 1;

for (int i=1; i<=10; i++) {

product *= i;

}

PrintUtils.print(Thread.currentThread().getName(), "阶乘结果="+product);

}

}

接着创建FactorialTask类的任务对象,并通过线程类的构造方法传入该任务,这就实现了在分线程中启动阶乘任务的功能。下面是外部给阶乘任务开启新线程的代码例子:

// 通过Runnable创建线程的第一种方式:传入普通实例

FactorialTask task = new FactorialTask();

new Thread(task).start(); // 创建并启动线程

鉴于阶乘任务的实现代码很短,似无必要定义专门的任务类,不妨循着比较器Comparator的旧例,采取匿名内部类的方式书写更为便捷。于是可在线程类Thread的构造方法中直接填入实现后的Runnable任务代码,具体的调用代码如下所示:

// 通过Runnable创建线程的第二种方式:传入匿名内部类的实例

new Thread(new Runnable() {

@Override

public void run() {

int product = 1;

for (int i=1; i<=10; i++) {

product *= i;

}

PrintUtils.print(Thread.currentThread().getName(), "阶乘结果="+product);

}

}).start(); // 创建并启动线程

由于Runnable是函数式接口,因此完全可以使用Lambda表达式加以简化,下面便是利用Lambda表达式取代匿名内部类的任务线程代码:

// 通过Runnable创建线程的第三种方式:使用Lambda表达式

new Thread(() -> {

int product = 1;

for (int i=1; i<=10; i++) {

product *= i;

}

PrintUtils.print(Thread.currentThread().getName(), "阶乘结果="+product);

}).start(); // 创建并启动线程

虽说Runnable接口的花样会比直接从Thread派生的多一些,但Runnable方式依旧要求实现run方法,看起来像是换汤不换药,感觉即使没有Runnable也不影响线程的运用,最多在编码上有点繁琐罢了。可事情没这么简单,要知道引入线程的目的是为了加快处理速度,多个线程同时运行的话,必然涉及到资源共享及其合理分配。比如火车站卖动车票,只有一个售票窗口卖票的话,明显卖得慢,肯定要多开几个售票窗口,一起卖票才卖得快。假设目前还剩一百张动车票,此时开了三个售票窗口,这样等同于启动了三个售票线程,每个线程都在卖剩下的一百张票。倘若不采取Runnable接口,而是直接定义新线程的话,售票线程的定义代码应该类似下面这般:

// 单独定义一个售票线程

private static class TicketThread extends Thread {

private int ticketCount = 100; // 可出售的车票数量

public TicketThread(String name) {

setName(name); // 设置当前线程的名称

}

@Override

public void run() {

while (ticketCount > 0) { // 还有余票可供出售

ticketCount--; // 余票数量减一

// 以下打印售票日志,包括售票时间、售票线程、当前余票等信息

String left = String.format("当前余票为%d张", ticketCount);

PrintUtils.print(Thread.currentThread().getName(), left);

}

}

}

然后分别创建并启动三个售票线程,就像以下代码所示的那样:

//创建多个线程分别启动,三个线程每个各卖100张,总共卖了300张票

new TicketThread("售票线程A").start();

new TicketThread("售票线程B").start();

new TicketThread("售票线程C").start();

猜猜看,上面三个售票线程总共卖了多少张票,实地运行测试代码后发现,这三个线程竟然卖掉了三百张票,而不是期望的一百张余票。究其原因,乃是各线程售卖的车票为专享而非共享,每个线程只认可自己掌握的车票,不认可其它线程的车票,结果导致三个线程各卖各的,加起来一共卖了三百张票。所以单独定义的线程类处理独立的事务倒还凑合,要是处理共享的事务就难办了。

如果采用Runnable接口来定义售票任务,就可以很方便地进行资源共享,只要命令三个线程同时执行售票任务即可。下面是开启三个线程运行售票任务的代码例子:

//只创建一个售票任务,并启动三个线程一起执行售票任务,总共卖了100张票

Runnable seller = new Runnable() {

private int ticketCount = 100; // 可出售的车票数量

@Override

public void run() {

while (ticketCount > 0) { // 还有余票可供出售

ticketCount--; // 余票数量减一

// 以下打印售票日志,包括售票时间、售票线程、当前余票等信息

String left = String.format("当前余票为%d张", ticketCount);

PrintUtils.print(Thread.currentThread().getName(), left);

}

}

};

new Thread(seller, "售票线程A").start(); // 启动售票线程A

new Thread(seller, "售票线程B").start(); // 启动售票线程B

new Thread(seller, "售票线程C").start(); // 启动售票线程C

因为100张余票位于同一个售票任务seller里面,所以这些车票理应为执行任务的线程们所共享。运行上述的任务测试代码,观察到如下的线程工作日志:

16:27:21.077 售票线程C 当前余票为98张

16:27:21.083 售票线程A 当前余票为96张

16:27:21.083 售票线程C 当前余票为95张

16:27:21.077 售票线程B 当前余票为97张

………………………这里省略中间的日志……………………

16:27:21.118 售票线程B 当前余票为2张

16:27:21.118 售票线程A 当前余票为1张

16:27:21.118 售票线程C 当前余票为4张

16:27:21.118 售票线程B 当前余票为0张

可见此时三个售票线程一共卖掉了100张车票,才符合多窗口同时售票的预期功能。

java runnable 启动_Java开发笔记(九十七)利用Runnable启动线程相关推荐

  1. java文本输入框_Java开发笔记(一百三十九)JavaFX的输入框

    循着Swing的旧例,JavaFX仍然提供了三种文本输入框,分别是单行输入框TextField.密码输入框PasswordField.多行输入框TextArea.这些输入框都由抽象类TextInput ...

  2. java swing输入框_Java开发笔记(一百二十九)Swing的输入框

    Swing的输入框仍然分成两类:单行输入框和多行输入框,但与AWT的同类控件相比,它们在若干细节上有所调整.首先说单行输入框,AWT的单行输入框名叫TextField,平时输入什么字符它便显示什么字符 ...

  3. java 流式_Java开发笔记(七十二)Java8新增的流式处理

    通过前面几篇文章的学习,大家应能掌握几种容器类型的常见用法,对于简单的增删改和遍历操作,各容器实例都提供了相应的处理方法,对于实际开发中频繁使用的清单List,还能利用Arrays工具的asList方 ...

  4. java 文件缓冲区_Java开发笔记(八十六)通过缓冲区读写文件

    前面介绍了利用文件写入器和文件读取器来读写文件,因为FileWriter与FileReader读写的数据以字符为单位,所以这种读写文件的方式被称作"字符流I/O",其中字母I代表输 ...

  5. JAVA翻译官_Java开发笔记(三)Java帝国的特种官吏

    上一篇文章介绍了Java工程的帝国区划,末尾给出了一段Java代码例子,这个代码虽然勉强能看懂,但是有些细节令人不甚了了.比如说"// 参观朱雀台"为何能够直接跟在当前行后面?&q ...

  6. java swing对话框_Java开发笔记(一百三十五)Swing的文件对话框

    除了常规的提示对话框,还有一种对话框也很常见,它叫做文件对话框.文件对话框又分为两小类:打开文件的对话框.保存文件的对话框,但在Swing中它们都用类型JFileChooser来表达.下面是JFile ...

  7. java 日历工具_Java开发笔记(四十二)日历工具的常见应用

    前面介绍了日历工具Calendar的基本用法,乍看起来Calendar与Date两个半斤八两,似乎没有多大区别,那又何苦庸人自扰鼓捣一个新玩意呢?显然这样小瞧了Calendar,其实它的作用大着呢,接 ...

  8. java 类型检查_Java开发笔记(五十二)对象的类型检查

    前面介绍了类的多态性,来自于鸡类的实例chicken,既能用来表达公鸡实例,也能用来表达母鸡实例.可是这导致了一个问题,假如在call方法内部需要手工判断输入参数属于公鸡实例还是母鸡实例,那该如何是好 ...

  9. Android开发笔记(十七)GIF动画的实现GifAnimation

    GIF在Windows上是常见的图片格式,主要用来播放短小的动画.但在手机上由于系统资源紧张,所以Android并没有直接支持GIF格式,如果在ImageView中放入一张gif文件,你会发现显示出来 ...

最新文章

  1. 利用Unity3D制作简易2D计算器
  2. 20060911: 新机器·流氓软件
  3. WebMessenger完善后将作为一个开源项目
  4. __attribute__((packed))
  5. 个人发卡网搭建源码_免费建设一个个人网站到底能不能实现
  6. jquery 视觉特效(幻灯片效果)
  7. 1.7-06编程基础之字符串 字符翻转
  8. STL源码剖析---迭代器失效小结
  9. paraview用户指南
  10. eaxsinbx_高等数学导数与微分练习题
  11. mysql查询历史时刻数据_跨平台实时数据库查询历史数据的方法介绍
  12. 计算机操做系统(十二):进程同步和互斥
  13. 文献阅读总结--合成生物学工程促进大肠杆菌中莽草酸的高水平积累
  14. 基于51单片机的温度监测控制系统仿真程序原理图设计
  15. sql的介绍——SQL Server数据库管理系统
  16. Vue动态面包屑功能的实现方法
  17. 名词解释atm网络_计算机网络名词解释大全
  18. pycharm运行后不显示run的结果
  19. C#——飞行棋流程图(很详细)
  20. r7 7735h和r5 5560U差距 锐龙r77735h和r55560U对比

热门文章

  1. 开放式神经网络交换-ONNX(下)
  2. 2021年大数据常用语言Scala(二十四):函数式编程 过滤  filter
  3. 2021年大数据常用语言Scala(十七):基础语法学习 Set
  4. 2021年大数据Flink(二十二):Time与Watermaker
  5. Django Request对象3.3
  6. python时区转换_在python 不同时区之间的差值与转换方法
  7. RxJava 过滤操作符 throttleFirst 与 throttleLast 以及 sample
  8. 微信小程序获取验证码倒计时
  9. ARouter::Compiler No module name, for more information, look at gradle log
  10. meson 中调用shell script