第1章多线程入门
1.1 线程入门
1.1.1 什么是多线程
单任务与多任务
并发编程即多线程编程,理解什么是并发编程首先要知道什么是线程,以及它的优势,下边我们从单任务与多任务说起。
1)单任务操作系统
我们玩电脑玩手机都离不开操作系统,计算机硬件有了操作系统才能方便用户去使用,操作系统是管理计算机硬件和软件的程序,比如:Android、Windows、Mac OS、Linux、Harmony OS等。
很早之前,计算机用的是单用户单任务操作系统,也就是说一台计算机同时只能由一个用户使用,一次只能运行一个任务。常用的单用户单任务操作系统有:MS-DOS、Windows 3.1等。
单任务操作系统无法 满足同时运行多个任务的需求,比如:我们在录制预见猿份的视频教程时,Windows上需要同时运行JVM虚拟机和视频录制程序,一边编写程序,一边录制视频,这样的需求在单任务操作系统上是无法满足的。
2)多任务操作系统
现在的计算机基本上都是多用户多任务,比如:win7、win10、Mac等操作系统,它允许多个用户同时使用计算机,并且允许同时运行多个任务,比如:一边微信聊天,一边看预见猿份的教学视频,同时打开记事本去记笔记。
多任务的好处:
1、满足人类更多的需求。
比如:一边写程序一边听歌。
2、充分利用计算机资源。
比如:充分利用多核CPU,同时进行多个计算任务。
打印的同时CPU闲置,同时去作计算任务。
3、提高工作效率。
比如:烧水的同时去找茶叶。
操作系统的进步满足了人类更多的使用计算机的需求,计算机硬件的发展也是操作系统发展的基础条件,最关键的就是CPU的进步,从早期的单个单核心CPU发展到单个多核心以及多个多核心 CPU,增加了CPU个数及核心数则计算机的运算能力就会大大提升。
多进程模式
操作系统如何同时执行多个任务呢?操作系统是通过进程来执行多个任务。
什么是进程?
进程是操作系统资源调度的基本单位,执行一个程序就会至少启动一个进程,操作系统会给其分配资源。比如:在windows操作系统上启动记事本程序即启动一个记事本进程,运行一个Java程序即启动JVM进程,操作系统给进程分配CPU、内存等资源,通过Windows的任务管理器可以看到当前正在运行的进程,如下图:

一个进程代表一个任务,运行多个进程即同时运行多个任务,从任务管理器就可以看出当前有多个进程在同时运行。
进程的特点:
1、每个进程拥有自己独立的资源(CPU、内存等),进程之间互不影响。
2、进程稳定性高,一个进程崩溃不影响其它进程。
多线程模式
1、什么是多线程模式
除了多进程运行多任务,多线程模式也可以运行多任务,在进程中开启多个线程去运行多个任务,多线程可以充分利用进程所分配的资源,从而加快程序的运行,提高计算机的效率。
从任务管理器可以看出一个进程中包括了多个线程。

多线程模式在实际应用中是非常广泛的:
比如:打开手机存储,为了加快速度计算空间使用情况,启动多个线程进行计算,如下图:

2、线程有什么特点
1)一个线程只能属于一个进程,一个进程包括多个线程,至少有一个线程。
2)一个进程中的多个线程共享进程的资源(CPU、内存等)。
3)线程稳定性差,一个线程崩溃可能会导致整个进程崩溃。
3、线程与进程的区别
1)进程之间互相隔离,线程之间共享资源。
2)进程稳定性好,线程稳定性差。
3)进程间的通信效率低,一般采用网络通信;线程之间通信效率高,因为它们共享资源,所以可通过共享资源来通信。
4)进程比线程创建的开销要大。
4、多线程比多进程运行多任务更有挑战性:
多线程共用进程的内存空间,线程运行的稳定性影响进程的稳定性;多线程编程容易出现线程安全问题。
5、多线程的优点
虽然多线程模式比多进程更有挑战性,多线程有很多优点致使多线程一直被使用。
多线程交互比进程间交互更高效更简单。进程间的通信效率低,一般采用网络通信;线程之间通信效率高,因为它们共享资源,所以可通过共享资源来通信。
资源利用率更高。一个进程内的多线程共用该进程的资源,多线程方式运行程序可以充分利用进程的资源,提高计算机的效率。
多线程程序响应更快,比如一个单线程程序,它必须等处理完成才能给用户提示处理结果,如果是多线程程序可以专门有一个线程给用户提示处理结果,即使该处理还没有完成。
6、多进程模式与多线程模式同时应用
打开任务管理器,同时运行多个进程,每个进程内运行了多个线程。
1.1.2 什么是并行与并发
什么是并行
多线程的好处是在一个进程中同时运行多个任务,“同时”一词包括并行和并发两个概念,多线程的工作模式有并行和并发两种。
一个程序有很多指令组成,这些指令最终由CPU去执行,假如这些指令一部分是任务1的指令,一部分是任务2的指令,CPU如何去执行这些指令呢?
现在的计算机一般都是多核CPU,所以可以启动两个线程,并且每个线程分配一个CPU核心去执行指令,如下图:

并行是利用多核CPU,每个线程都分配独立核心的CPU去运算,多个线程在同一个时刻同时运行
并行就是在同一时刻做不同的事,生活中并行的例子很多,比如:一边写程序,一边听音乐,写程序和听音乐就是并行。
什么是并发
我们要运行的任务数量一定会比CPU核心数量多,按一个CPU核心分给一个线程来算,CPU核心是不够分给每个任务的,那怎么办?
CPU内核的工作频率叫时钟频率,它用若干次周期每秒来度量,单位是赫兹(Hz),现在的CPU已达到GHz, 单位换算关系是:1GHz = 1000MHz,1MHz = 1000KHz,1KHz = 1000Hz,那么1GHz表示CPU在1秒内运算10亿次,一般的家用电脑每秒可运算约50亿次,企业用的工作电脑运算次数更多。
既然CPU有这么强的运算能力,完全可以把一个CPU核心分配给多个线程,CPU就可以为每个线程分配一点时间去运算,这个分配的时间叫时间片,如下图:


因为CPU的执行速度非常快,CPU在众多线程间切换也会让你感觉多个线程在同时运行,其实多线程是在串行执行,这种多线程在轮流使用CPU的现象叫并发,并发是串行执行,在某个时刻CPU只会给某一个线程用。
现实生活中并发的例子很多,比如:你在写Java程序,写完程序去运行测试,发现有bug去修改程序,完成后再去运行测试,你一个人在交替完成写程序和测试程序两件事,这就是并发。
并行与并发同时存在
在多核CPU下多线程运行一般是并行与并发同时存在,如下图:

不同核心CPU之间是并行运行,同一个核心CPU在多个线程之间为并发运行。
1.1.3 学习并发编程的意义
并发编程是什么
Java语言提供对多线程的支持,运行一个Java程序的main方法就是启动一个JVM进程,main方法就是主线程,在main方法中还可以启动更多的线程来执行多任务。如下图:

并发编程就是使用Java语言所提供的多线程特性去编程可以执行多任务的程序,利用多核CPU的优势编写多线程程序去运行多任务,提高程序的执行效率,保证程序执行结果没有问题。
学习并发编程的意义
很多程序员工作几年发现没有用到多线程技术,为什么呢?
为什么工作中没有使用并发编程知识?
1、面向应用开发的程序员更多的是业务逻辑的开发。
2、基于现成的框架开发,多线程技术被隐藏。
3、你当前的职位等级是编码员。
学习并发编程有什么意义呢?
1、Java作为服务端编程语言,它在网络编程、数据库编程、WEB编程方面应用非常广泛,多线程技术正是这些领域的基础,比如:一些Web容器、中间件等底层都是采用并发编程技术实现,所以学好并发编程有助于我们去学习这些服务端技术的底层原理。
2、学好并发编程是你晋升高级程序员及架构师的必须要具备的技能。
一定要用多线程吗?
我们学习并发编程是不是就意味着我们的程序一定要用多线程实现呢?
不是,多线程相比单线程要复杂并且存在上下文切换开销,一切要实际情况实际分析。
1)多线程程序设计复杂
多线程程序的设计一般都比较复杂,因为设计多线程程序要考虑很多问题,比如:如何控制共享资源的访问;如何进行多线程之间的协作等等。
2)上下文切换开销
CPU在线程之间切换需要记录每个线程的上下文,上下文就是当前线程的运行环境,比如:当前线程的状态、栈信息等,比如当线程A用完时间片需要切换到线程B,线程A的上下文信息会保存下来,待线程A再获得时间片时需要重新回复线程A的上下文,这个过程叫线程的上下文切换。
上下文切换需要系统开销,所以并不是线程越多程序运行的就越快,比如一个单核的CPU运行多线程去执行多个任务并不能提高运行速度。
3)能用单线程就不要用多线程
如果单线程就能满足需求就不要用多线程技术去开发,因为多线程程序要比单线程程序复杂的多。
1.1.4 学习准备
前置知识
学习本教程需要具备Java基础语法编程基础。
准备环境
1、开发工具
IDEA,JDK17
2、日志环境
在调试多线程运行时通常需要观察当前线程的相关信息,为了方便输出日志我们需要配置日志环境。
本教程使用一个简易的日志封装类,后边的教程会详细学习项目中的日志模块。
1)第一步在src下创建logging.properties文件,内容如下:
.level= ALL
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter2)第二步在自己的学习工程下创建下边的类,代码可直接拷贝,如下:
package com.yjoffer.javase.config;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.logging.*;
/**
* 日志简易封装类
* @author 预见猿份(yjoffer.com)
* @version 1.0
**/
public class Logger {
private static LogManager logManager = LogManager.getLogManager();
static {
InputStream inputStream = null;
try {
inputStream = Logger.class.getClassLoader().getResourceAsStream("logging.properties");
logManager.readConfiguration(inputStream);
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private java.util.logging.Logger logger;
public static Logger getLogger(Class clzz){
return new Logger(clzz.getName());
}
private Logger(String name){
logger = java.util.logging.Logger.getLogger(name);
ConsoleHandler handler = new ConsoleHandler();
handler.setLevel(Level.ALL);
handler.setFormatter(new CustomFormat());
logger.addHandler(handler);
logger.setLevel(Level.ALL);
logManager.addLogger(logger);
}
/**
* 调试信息
* @param msg
*/
public void debug(String msg){
logger.fine(msg);
}
/**
* 一般信息
* @param msg
*/
public void info(String msg){
logger.info(msg);
}
/**
* 警告信息
* @param msg
*/
public void warn(String msg){
logger.warning(msg);
System.out.println();
}
class CustomFormat extends Formatter {
@Override
public String format(LogRecord log) {
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss.S");
return format.format(log.getMillis())+" "+log.getLevel()+" "+log.getLoggerName()+"["+Thread.currentThread().getName()+"] - " +log.getMessage()+"\n";
}
}
}3)测试日志环境
在每个要打印日志的类中首先定义静态变量logger,在方法中调用logger的方法输出日志信息即可。
测试代码如下:
package com.yjoffer.javase.thread.basic;
import com.yjoffer.javase.config.Logger;
import java.util.Random;
/**
* 第一个多线程程序
* @author 预见猿份(yjoffer.com)
*/
public class ThreadFirst {
private static Logger logger = Logger.getLogger(ThreadFirst.class);
public static void main(String[] args) {
logger.info("普通信息");
logger.debug("调试信息");
logger.warn("警告信息");
}
}运行程序,输出:
16:07:25.465 INFO com.yjoffer.javase.thread.basic.ThreadFirst[main] - 普通信息
16:07:25.490 FINE com.yjoffer.javase.thread.basic.ThreadFirst[main] - 调试信息
16:07:25.490 WARNING com.yjoffer.javase.thread.basic.ThreadFirst[main] - 警告信息日志包括如下部分:
1)时间,精确到毫秒。
2)日志级别
3)类路径
4)线程名
5)日志信息
1.1.5 第一个多线程程序
任务与线程的关系
使用多线程的目的是执行多任务,为了更好的学习并发编程的知识,有必要先解释两个易于混淆的概念:任务和线程。
任务,是计算机要做的工作,拥有具体的工作内容,具有一定的目的性,比如:一个整型数组的排序任务、一个视频处理任务、一个文件打印任务,我们先前学习的面向对象编程,对象的每个方法都可以当成一个任务,执行一个方法就是运行一个任务,任务有具体的代码指令。
线程,操作系统调度的最小单位,通过分配CPU、内存等资源来运行一个任务,线程的作用是用来执行任务的,就好比你编写了很多方法,需要通过main方法这个入口方可去执行一样,进程的主线程会执行main方法,一个任务没有线程的驱动是无法执行的。
定义一个任务
下边编写第一个多线程程序,程序的功能是输出10个随机整数。
输出随机数使用java.util.Random,查看API文件如下:
构造方法:
创建一个新的随机数生成器。
Random()
使用单个 long种子创建一个新的随机数生成器,指定种子每次生成的随机数都是固定的。
Random(long seed)
生成随机数方法:
返回伪随机的 int值介于0(含)和指定值(不包括)之间。
伪随机数就是用固定的算法计算随机数序列,并不是真正的随机,但具有类似于随机数的统计特征,如均匀性、独立性等。
int nextInt(int bound)定义任务,任务类需要实现java.lang.Runnable接口,查看API它有一个run方法,如下:
void run()
实现java.lang.Runnable接口的对象被用来创建一个线程,启动线程使对象的 run方法在线程中被调用。下边实现Runnable接口创建一个任务类:
package com.yjoffer.javase.thread.basic;
import java.util.Random;
/**
* 生成10个随机数任务
* @author 预见猿份(yjoffer.com)
*
*/
public class RandomGenerator implements Runnable {
private static Logger logger = Logger.getLogger(RandomGenerator.class);
public void run() {
Random random = new Random();
for (int i = 0; i < 10; i++) {
int nextInt = random.nextInt(10);
logger.info(String.valueOf(nextInt));
}
}
}启动一个线程
要想使用多线程运行RandomGenerator任务类的run方法则需要定义Thread线程对象,查看API,java.lang.Thread 的构造方法如下:
Thread()
分配一个新的 Thread对象。
Thread(Runnable target)
分配一个新的 Thread对象。
Thread(Runnable target, String name)
分配一个新的 Thread对象,并指定线程名称。
...从API可以看到Thread可以接收java.lang.Runnable类型的构造参数,这里使用Thread(Runnable target) 此构造方法,代码如下:
package com.yjoffer.javase.thread.basic;
import java.util.Random;
/**
* 第一个多线程程序
* @author 预见猿份(yjoffer.com)
*/
public class ThreadFirst {
//启动一个线程
public static void test1() {
//创建任务对象
RandomGenerator randomGenerator = new RandomGenerator();
//创建线程对象
Thread thread1 = new Thread(randomGenerator);
//启动线程
logger.debug("启动一个线程");
thread1.start();
}
public static void main(String[] args) {
test1();
}
}运行程序,在控制台输出10个随机数。(测试时注意:随机数每次生成的不一样)
16:18:29.833 FINE com.yjoffer.javase.thread.basic.ThreadFirst[main] - 启动一个线程
16:18:29.855 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 3
16:18:29.856 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 9
16:18:29.856 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 6
16:18:29.856 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 4
16:18:29.856 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 1
16:18:29.856 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 5
16:18:29.856 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 8
16:18:29.856 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 4
16:18:29.856 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 4
16:18:29.856 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 2通过测试我们知道启动一个线程执行的步骤是:
1、创建一个Thread对象,并向它传入任务对象。
2、执行thread对象的start();启动线程,新线程将调用任务对象的run方法 。
从上边的输出可以看出新线程的名称是“Thread-1”,正确输出了10个随机数。
启动多个线程
根据上边的测试,下边启动两个线程执行任务,代码如下:
//启动两个线程
public static void test1_1() {
//创建任务对象
RandomGenerator randomGenerator = new RandomGenerator();
//创建线程对象,通过Thread(Runnable target, String name) 构造方法在第二个构造参数定义线程名称
Thread thread1 = new Thread(randomGenerator,"t1");
Thread thread2 = new Thread(randomGenerator,"t2");
//启动一个线程
logger.debug("启动第一个线程");
thread1.start();
//启动一个线程
logger.debug("启动第二个线程");
thread2.start();
}代码中将一个任务对象分别传入thread1和thread2,注意:多个Thread对象可以对应与同一个任务对象的,也可以分别对应不同的任务对象。
运行test1_1()方法,在控制台由两个线程分别输出10个随机数。
输出如下:
16:20:25.44 FINE com.yjoffer.javase.thread.basic.ThreadFirst[main] - 启动第一个线程
16:20:25.65 FINE com.yjoffer.javase.thread.basic.ThreadFirst[main] - 启动第二个线程
16:20:25.65 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t1] - 5
16:20:25.65 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t2] - 9
16:20:25.66 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t1] - 6
16:20:25.66 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t2] - 9
16:20:25.66 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t1] - 2
16:20:25.66 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t2] - 6
16:20:25.66 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t1] - 4
16:20:25.66 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t2] - 9
16:20:25.66 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t1] - 7
16:20:25.67 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t2] - 3
16:20:25.67 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t1] - 5
16:20:25.67 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t2] - 8
16:20:25.67 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t1] - 4
16:20:25.67 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t2] - 1
16:20:25.67 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t1] - 3
16:20:25.67 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t2] - 2
16:20:25.67 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t1] - 4
16:20:25.67 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t2] - 2
16:20:25.67 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t1] - 6
16:20:25.68 INFO com.yjoffer.javase.thread.basic.RandomGenerator[t2] - 0从输出内容可以看出共启动了两个线程:t1和t2,它们分别输出的10个随机数。
1.1.6 start()与run()
run()方法
有多初学者搞不清run()方法和start()方法的区别,它们都可以启动一个线程去执行一个任务吗?
java.lang.Runnable接口的方法是run()方法,thread类中也有一个run()方法。
是否可以直接调用thread对象的run()方法去执行一个任务呢?
查看Thread类的run()方法的源代码,如下:
@Override
public void run() {
if (target != null) {
target.run();
}
}target即构造Thread对象时通过构造方法设置的任务对象,如果任务对象不为空则调用任务对象的run()方法,如果直接调用run()方法去执行任务可以吗?如下代码:
//由主线程直接调用run()方法
public static void test1_2() {
//创建任务对象
RandomGenerator randomGenerator = new RandomGenerator();
//创建线程对象
Thread thread1 = new Thread(randomGenerator);
//执行任务
thread1.run();
}输出 :
16:23:55.840 FINE com.yjoffer.javase.thread.basic.ThreadFirst[main] - 执行任务
16:23:55.861 INFO com.yjoffer.javase.thread.basic.RandomGenerator[main] - 1
16:23:55.861 INFO com.yjoffer.javase.thread.basic.RandomGenerator[main] - 8
16:23:55.862 INFO com.yjoffer.javase.thread.basic.RandomGenerator[main] - 2
16:23:55.862 INFO com.yjoffer.javase.thread.basic.RandomGenerator[main] - 5
16:23:55.862 INFO com.yjoffer.javase.thread.basic.RandomGenerator[main] - 4
16:23:55.862 INFO com.yjoffer.javase.thread.basic.RandomGenerator[main] - 8
16:23:55.862 INFO com.yjoffer.javase.thread.basic.RandomGenerator[main] - 6
16:23:55.862 INFO com.yjoffer.javase.thread.basic.RandomGenerator[main] - 1
16:23:55.862 INFO com.yjoffer.javase.thread.basic.RandomGenerator[main] - 5
16:23:55.862 INFO com.yjoffer.javase.thread.basic.RandomGenerator[main] - 5从输出内容可以看出输出随机数是在main主线程中执行的,调用run()方法可以执行任务但是并没有启动一个新线程。
start()方法
查看Thread类的start()方法的源代码,如下:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();上边的start()方法会调用一个start0()本地方法,由c++实现,通过操作系统创建并启动线程。
所以,启动一个新线程调用的是start()方法而不是run()方法!
通过运行入门程序test1()方法可以看出与test1_2()方法的区别,代码如下:
//启动一个线程
public static void test1() {
//创建任务对象
RandomGenerator randomGenerator = new RandomGenerator();
//创建线程对象
Thread thread1 = new Thread(randomGenerator);
//启动线程
logger.debug("启动一个线程");
thread1.start();
}运行程序,输出如下:
16:30:31.102 FINE com.yjoffer.javase.thread.basic.ThreadFirst[main] - 启动一个线程
16:30:31.123 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 8
16:30:31.124 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 2
16:30:31.124 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 6
16:30:31.124 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 3
16:30:31.124 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 2
16:30:31.124 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 5
16:30:31.124 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 2
16:30:31.124 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 6
16:30:31.124 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 0
16:30:31.125 INFO com.yjoffer.javase.thread.basic.RandomGenerator[Thread-1] - 7可以看出新线程的名称为"Thread-1",新线程输出了10个随机数。
1.1.7 创建任务的基本方式
创建任务的四种方法
创建一个任务有四种方法:
1、实现java.lang.Runnable接口。
2、Lambda实现java.lang.Runnable接口。
3、定义子类继承thread类。
4、定义内部类继承thread类。
代码演示
package com.yjoffer.javase.thread.basic;
import com.yjoffer.javase.config.Logger;
import java.util.Random;
/**
* 第一个多线程程序
* @author 预见猿份(yjoffer.com)
*/
public class ThreadFirst {
private static Logger logger = Logger.getLogger(ThreadFirst.class);
//创建任务的四种方法
//方式一:实现Runnable接口
static class Task1 implements Runnable{
@Override
public void run() {
logger.debug("执行任务");
}
}
//方式三:继承Thread类
static class Task3 extends Thread{
@Override
public void run() {
logger.debug("执行任务");
}
}
public static void test_createTask(){
//方式二:Lambda
Runnable task2 = ()->{
logger.debug("执行任务");
};
//启动线程执行任务
new Thread(new Task1(),"t1").start();
new Thread(task2,"t2").start();
//Lambda方式通常这样写
new Thread(()->{
logger.debug("执行任务");
},"t3").start();
new Thread(new Task3(),"t4").start();
//方式四:内部类继承Thread类
new Thread(new Thread(){
public void run() {
logger.debug("执行任务");
}
},"t5").start();
}
public static void main(String[] args) {
test_createTask();
}
}小结
至此我们学习四种创建任务的方式,总体分为两类:
1、继承Thread类这种创建方式
由于Java是单继承所以此方法 扩展性比较差,不推荐使用。
2、实现Runnable接口方式
扩展性比较强,推荐使用Runnable接口方式创建任务对象,实现时建议使用Lambda。
