多线程是指机器支持在同一时间执行多个线程,能够提高cpu的利用率 ,提高程序的执行效率。
(1)继承Thread类
多线程可以通过继承Thread类并重新Thread的run方法来启动多线程。然后通过Thread的start方法来启动线程。上代码:
package com.wangx.thread.t1;
public class Demo1 extends Thread {
Demo1(String name) {
super(name);
}
@Override
public void run() {
while (!interrupted()) {
System.out.println("线程" + Thread.currentThread().getName() + "执行了。。。。");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Demo1 demo1 = new Demo1("one");
Demo1 demo2 = new Demo1("two");
demo1.start();
demo2.start();
demo1.interrupt();
}
}
这里也顺便用了线程的中断,当希望一个线程不再执行时,就是用interrupt()方法来进行中断,此时的的线程对象将不会再执行,interrupted()方法判断线程是否中断,返回boolean值,当当前线程被中断时返回false,使用interrupted()方法可以避免报InterruptedException异常。
(2)实现Runnable接口
Runnable接口中只有一个run方法,实现Runnable接口的run方法作为任务处理方法,将Runnable的实现类对象传入到Thread的构造中,创建线程,并使用Thread.start()启动线程。代码:
package com.wangx.thread.t1;
public class Demo2 implements Runnable {
/**
* 重写run方法
*/
@Override
public void run() {
System.out.println("执行了");
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
//将demo2传入到Thread中
Thread thread = new Thread(demo2);
Thread thread1 = new Thread(demo2);
thread.start();
thread1.start();
}
}
这里的Runnable接口其实是作为一个线程任务处理器,查看Thread中的run方法和构造方法可以看出,但传入的target(Runnable对象)不为空时,执行Runnable的run方法,所以通过启动线程时实际先执行的还是Thread中的run方法去调用target的run方法。Thread中的run()方法源码如下:
public void run() {
if (target != null) {
target.run();
}
}
(3)匿名内部类方法
匿名内部类其实跟继承Thread类并重写run方法原理一样,都是通过覆盖父类run方法,执行当前对象的run方法的方式来执行只需要处理的任务,只是写法上跟简洁,并且只会执行一次,如果任务只需要执行一次时并且减少代码量时可以使用,代码如下:
package com.wangx.thread.t1;
public class Demo3 {
public static void main(String[] args) {
//匿名内部类启动多线程
new Thread(){
@Override
public void run() {
System.out.println("Thread in running");
}
}.start();
}
}
还可以通过Runnable的匿名内部类来实现,原理与第二点一致,代码如下:
package com.wangx.thread.t1;
public class Demo3 {
public static void main(String[] args) {
//匿名内部类启动多线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable thread is running");
}
}).start();
}
}
在这里需要注意的时,当同时使用Thread的匿名内部类和Runnable接口的匿名内部类同时使用时,此时执行的是Thread的run方法,而不会执行Runnable的run方法,原因是根据源码可以看出此时run方法已经被重写,所以不会调用target.run语句,所以Runnable的run方法不会执行。代码:
package com.wangx.thread.t1;
public class Demo3 {
public static void main(String[] args) {
//Thread匿名内部类和Runnable匿名内部类同时存在时,打印sub is running
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Runnable");
}
}){
@Override
public void run() {
System.out.println("sub is running");
}
}.start();
}
}
(4)创建带返回值的线程
实现Callable<T>泛型方法,重写call方法,泛型传入什么类型的值,call就返回什么类型的值,并使用FutureTask接收Callable对象,FutureTask的泛型为Callable中传入的类型,将
FutrueTask对象传入到Thread中启动线程,并使用FutureTask的get方法获取到call方法的返回值,代码如下:
package com.wangx.thread.t1;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo4 implements Callable<Integer> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Demo4 demo4 = new Demo4();
//通过demo4创建task对象
FutureTask<Integer> task = new FutureTask<>(demo4);
//通过task创建并启动线程
Thread thread = new Thread(task);
thread.start();
Integer result = task.get();
System.out.println("计算结果为:" + result);
}
@Override
public Integer call() throws Exception {
System.out.println("正在进行紧张的计算....");
Thread.sleep(1000);
return 1;
}
}
Thread中可以接接收FutureTask是因为FutureTask是Runnable的实现类,所以也可以说FutureTask是Runnable的另类实现。
(5)线程池的方式
由于线程的创建和销毁都会消耗过多的内存资源,所以在jdk5之后JAVA新增了线程池的概念,ThreadPoolExecutor是线程池的核心类,它重载了很多的构造方法让我们构造一个线程池,在线程池中创建指定多个的线程,执行任务时,将从线程池中取出线程去执行任务,任务执行完成后将线程归还到线程池中。线程池不用频繁的创建和销毁线程池,减少了资源的消耗,提高了性能,可以直接通过new ThreadPoolExecutor()来指定自己创建线程池,也可以使用Executors中的静态方法来创建各种类型的线程池,这里先创建一个制定大小的线程池,代码如下:
package com.wangx.thread.t1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo6 {
public static void main(String[] args) {
//创建线程池大小为10的线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
//循环执行100次,打印线程名字,发现总是只用10个线程在执行
for (int i = 0; i < 100; i++){
executor.execute(()-> {
System.out.println(Thread.currentThread().getName());
});
}
executor.shutdown();
}
}
executor.execute()传入Runnable对象,执行任务,这里使用lambda表达式写法,其实就是一个Runnable匿名对象,打印当前线程名称。当任务100次循环之后我们发现程序并没有结束,
这是因为线程池仍然存活,此时调用executor.shutdown();方法关闭线程池,结束程序。因为Executors中的静态方法也是通过ThreadPoolExecutor来创建的,所以这里就不写直接创建
的案例了,关于ThreadPoolExecutor构造方法的个参数作用下一节将会详解。
(6)在spring3+中使用多线程
spring3之后的版本提供了对多线程的支持,这里案例是spring boot项目为例的,方便引入spring的依赖。
在spring boot中使用多线程需要使用@EnableAsync注解来开启异步执行的支持,然后在需要执行的方法上加上@Async标记该方法为异步方法,之后直接通过bean调用该方法可以发现该方法是
异步执行的,实例代码为:
package com.example.springthread;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
/**
*springboot启动类,这里也用做了配置类
*/
@SpringBootApplication
//开启注解
@EnableAsync
public class SpringthreadApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringthreadApplication.class, args);
DemoService demoService = context.getBean(DemoService.class);
demoService.a();
demoService.b();
}
}
/*******************异步方法所在的bean*************************/
package com.example.springthread;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
//标记方法为异步方法
@Async
public void a() {
while (true){
System.out.println("a is running");
}
}
@Async
public void b() {
while (true){
System.out.println("b is running");
}
}
}
启动springboot,调用DemoService的a/b方法,可以看到他们异步执行。这里只是为了方便引入spring的依赖,直接使用spring也是可以实现异步方法调用的spring也支持计划任务,使用@EnableScheduling开启计划任务,@Scheduled标记计划任务的方法,在注解中传入相应的执行时间和周期即可实现计划任务
(7)创建定时任务
在java.util包中提供了一个Timer类可以用来创建定时任务,可以指定某个时间开始执行,之后每隔多长时间执行一次。代码如下:
package com.wangx.thread.t1;
import java.util.Timer;
import java.util.TimerTask;
public class Demo5 {
public static void main(String[] args) {
Timer timer = new Timer();
// 创建1秒后开始中,没隔1秒执行一次的定时任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("timer task is running");
}
},1000, 1000);
}
}
Timer接收一个TimerTask为要执行的任务,其余参数为需要执行的时间方式,可以进入Timer的源码查看个参数的意义,构建自己想要的计划任务。
本章主要是为了回顾java生态中各种多线程的实现方式,并不涉及太多概念和原理问题,具体的原理将会在后面的学习中逐渐补上。