目录
1.继承Thread类
流程:
- 创建一个子类继承Thread类
- 在这个子类中重写Thread的run方法
- 创建子类的实例对象,通过调用该实例对象的 start() 方法 ,启动线程
模拟创建一个线程打印0-10的数。代码如下:
①创建子类MyThread继承Thread类
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<=10;i++){
System.out.println(i);
}
}
}
②创建一个测试类Test,创建子类的实例对象,通过调用start() 方法 ,启动线程
public class Test {
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.run();
}
}
③运行结果
2. 实现Runnable接口
流程:
-
创建一个子类实现 Runnable 接口。
-
子类中重写 Runnable 接口中的 run 方法。
-
通过 Thread 类含参构造器创建线程对象。
-
将Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造器中。
-
调用Thread 类的 start 方法:开启线程,调用 Runnable 子类接口的 run 方法。
模拟创建一个线程打印0-10的数。代码如下:
①创建一个子类实现Runnable接口 ,重写Runnable接口中的run方法。
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<=10;i++){
System.out.println(i);
}
}
}
②创建一个测试类Test,创建子类的实例对象,作为实际参数传递给Thread类的构造器中通过调用start() 方法 ,启动线程
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable=new MyRunnable();
Thread thread=new Thread(myRunnable);
thread.run();
}
}
③运行结果:
实现Runnable接口创建线程的好处:
- Java中只能进行单继承,使用实现Runnable接口创建线程可以避免单继承的局限性
-
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
3.实现Callable接口
流程:
- 创建一个子类实现Callable接口,需要注意的是我们需要确定方法的返回值类型
- 重写接口的call方法
- 由于Thread类的构造函数并不能传实现Callable接口的对象,所以我们需要使用FutureTask类,这个类实现了RunnableFuture接口,并且该类的有参构造器参数是实现Callable接口的对象
- 将实现Callable接口的对象以传参的方式传进FutureTask的有参构造器,再将FutureTask的对象传进Thread的有参构造器
- 使用FutureTask对象的ge()方法获取Callable的返回值
代码:
①创建子类实现Callable接口,确定返回值类型
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("Callable创建线程!");
return 10;
}
}
②创建测试类Test,打印call方法的返回值
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask=new FutureTask(new MyCallable());
Thread thread=new Thread(futureTask,"task");
thread.run();
int res= (int) futureTask.get();//通过get方法获得call方法的返回值;
System.out.println(res);
}
}
③运行结果
使用Callable接口创建线程可以满足前两种创建线程缺少的一项功能,就是当线程终止时(即run()完成时),我们无法使线程返回结果,通过FutureTask对象的get方法我们就可以拿到线程的返回值。
4.通过线程池创建线程
(1)使用线程池的好处
- 可以降低资源的消耗 ,避免对单个线程创建销毁造成的消耗
- 可以提高反应速度,任务可以不需要等到线程创建就能执行
- 可以提高线程的可管理性,使用线程池可以对线程进行统一分配调优和监控
(2)线程池的四种创建方式
Java通过Executors提供了四种线程池
①newCachedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
代码:
public class ThreadPoolTest01 {
public static void main(String[] args) {
//创建线程池,可重复利用
ExecutorService executorService= Executors.newCachedThreadPool();
//创建5个线程
for(int i=0;i<10;i++){
int num=i;
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":线程"+num+"已经执行");
}
});
}
}
}
运行结果:
②newFixedThreadPool:需要指定线程池的容量大小,可控制线程的最大并发数,超出的线程需要在队列中等待。
public class ThreadPoolTest02 {
public static void main(String[] args) {
//创建一个长度为5的线程池
ExecutorService executorService= Executors.newFixedThreadPool(5);
for(int i=0;i<10;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
运行结果:由于一共10个任务,但是设定了线程池的容量是5,所以一次只能执行5个任务,剩下的任务需要等待前五个任务执行结束之后运行。所以运行结果为先打印出5个线程的名字,两秒之后,打印出后五个线程的名字。
③newScheduledThreadPool:支持定时及周期性任务执行
代码:延迟三秒后执行
public class ThreadPoolTest03 {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(5);
for(int i=0;i<10;i++){
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},3,TimeUnit.SECONDS);
}
}
}
运行结果:运行三秒后
④newSingleThreadExecutor:单程化线程池,该线程池中只有一个线程,按照某种指定顺序去执行。
代码:
public class ThreadPoolTest04 {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
运行结果:由于只有一个线程,一共10个任务,所以这个线程会一次执行完这个任务,先睡眠两秒后打印出当前线程的名字,共执行10次,直到所有任务执行结束。