并发编程学习笔记(1)----多线程几种实现方式
多线程是指机器支持在同一时间执行多个线程,能够提高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生态中各种多线程的实现方式,并不涉及太多概念和原理问题,具体的原理将会在后面的学习中逐渐补上。