创建线程的三种方法

1、继承Thread类,重写run()方法,调用start()方法执行。

2、实现Runnable接口,实现run()方法,创建Thread对象时传入Runnable的子类对象,调用start()方法执行。

3、实现Callable接口,实现call()方法,可返回结果并允许此方法抛出异常。实例化FutureTask类时,将实现Callable接口的类的对象作为参数传入,然后实例化Thread类,将FutureTask对象传入,最后执行start()。

JAVA线程类结构的设计:

Callable和Runnable接口是为了那些实例可能被另一个线程执行的类设计的。

多线程执行过程

程序默认的线程执行在栈区,当创建一个新线程时,在栈区开辟一个新的内存空间,执行代码(变量的创建都在新的内存区中)。当线程的代码结束了,线程自动在栈内存中释放了,当所有线程都结束了,那么进程就结束了。

(main方法的线程和main方法中创建的新线程有主从关系吗?main线程会等“子线程”结束再结束吗?)

要注意多线程中的并发执行≠并行执行。(同一时间,多个事件间隔发生【时间片单位】;同一时间,多个事件同时发生。)并发是在多台处理器上同时处理多个任务。所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。

并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

下列Demo中线程任务执行的方法中加睡眠方法的原因是:让线程进入等待状态,方便其他线程抢占资源,这样可以方便的观察到多线程并发执行的情况。
sleep和wait的区别是:sleep只是当前线程卡在这,停止执行,它不需要锁,如果当前线程持有某个锁,它也不会因为卡在这而释放锁。而wait执行时,如果当前线程持有锁,那么执行wait会让当前线程卡在这,同时释放掉它持有的锁。
更简洁的一句话是:两个都会让当前线程卡那,wait会释放锁,sleep不会。

Thread

DemoDemo2是两个多线程类,Test里面测试多线程执行

只有调用start()方法才会创建新线程,直接调用run()方法是不会创建新线程的,只是在main线程中的一个方法顺序执行
为什么?
因为start方法内部执行start0方法,这个方法是native的,实际会去执行Java封装的C++写的方法,在C++方法中,调用操作系统,由操作系统创建一个线程并执行任务。
那为什么需要start方法呢?直接用run方法,一个方法得了。不行,因为我们需要一个方法供我们使用Java语言把我们线程要做的事情写进去,所以这个方法必须是我们使用者拿java语言写的。那真正调用C++,让操作系统创建线程的方法,就必须要有另外的方法。
所以,run方法只是普通的Java方法,start方法才是开启线程并执行的方法。也即要满足用户自定义线程这个需求必须有两个方法,A用来描述做什么事情,B用来开启线程做这个事情。

Demo.java

  1. package MuliThread.one;
  2. public class Demo extends Thread {
  3. @Override
  4. public void run() {
  5. for (int i = 0; i < 10; i++) {
  6. try {
  7. Thread.sleep(800);
  8. } catch (InterruptedException e) {
  9. System.out.println("睡眠方法出错");
  10. }
  11. System.out.println(this.getName()+":"+i);
  12. }
  13. }
  14. }

Demo2.java

  1. package MuliThread.one;
  2. public class Demo2 extends Thread {
  3. @Override
  4. public void run() {
  5. for (int i = 0; i < 10; i++) {
  6. try {
  7. Thread.sleep(500);
  8. } catch (InterruptedException e) {
  9. System.out.println("睡眠方法出错");
  10. }
  11. System.out.println(this.getName()+":"+i);
  12. }
  13. }
  14. }

Test.java

  1. package MuliThread;
  2. public class Test {
  3. public static void main(String[] args) {
  4. /*
  5. 创建线程的目的是什么?
  6. 是为了建立程序单独的执行路径,让多部分代码实现“同时”执行。也就是说线程创建并执行需要给定线程要执行的任务。
  7. 例如:导入功能。
  8. */
  9. Demo d1 = new Demo();
  10. Demo2 d2 = new Demo2();
  11. d1.setName("=====");
  12. d2.setName("+++++");
  13. //两个线程并发执行
  14. d1.start();
  15. d2.start();
  16. //当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。
  17. //调用run方法无法开启线程,因为直接调用run方法是执行方法,还是在主线程种。而start()方法才会开启新线程。
  18. //d1.run();//run执行完才会往下执行,所以不会在并发执行,而是顺序执行
  19. //d2.start();
  20. }
  21. }

多线程并发执行:

为什么要创建类继承Thread,而不是直接调用Threadstart()方法呢?

Thread类的run()方法没有做事,所以你需要重写run()方法,将你创建新线程需要执行的代码写进去。

匿名内部类

另一种使用是直接创建Thread

  1. package MuliThread.one;
  2. public class Test {
  3. public static void main(String[] args) {
  4. //可以改成lamdba表达式
  5. new Thread(){
  6. @Override
  7. public void run() {
  8. for (int i = 0; i < 50; i++) {
  9. System.out.println("==="+i);
  10. }
  11. }
  12. }.start();
  13. new Thread(){
  14. @Override
  15. public void run() {
  16. for (int i = 0; i < 50; i++) {
  17. System.out.println("+++"+i);
  18. }
  19. }
  20. }.start();
  21. }
  22. }

多线程并发执行:

Runnable

Demo是线程任务,实现Runnable接口,实现run()方法;创建Thread线程对象,传入Demo类,调用start()方法即可。

Demo.java

  1. package MuliThread.two;
  2. //线程任务
  3. public class Demo implements Runnable {
  4. private String name;
  5. public Demo(String name) {
  6. this.name = name;
  7. }
  8. public String getName() {
  9. return name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. @Override
  15. public void run() {
  16. for (int i = 0; i < 100; i++) {
  17. System.out.println(name+":"+i);
  18. try {
  19. Thread.sleep(500);
  20. } catch (InterruptedException e) {
  21. System.out.println("睡眠方法出错");
  22. }
  23. }
  24. System.out.println("执行完毕");
  25. }
  26. }

Test.java

  1. package MuliThread.two;
  2. public class Test {
  3. public static void main(String[] args) {
  4. Demo demo1 = new Demo("hanxu");//线程任务
  5. Thread thread1 = new Thread(demo1);//线程对象
  6. Demo demo2 = new Demo("ctj");
  7. Thread thread2 = new Thread(demo2);
  8. thread1.start();
  9. thread2.start();
  10. }
  11. }

多线程并发执行:

使用Runnable的好处:将线程任务线程对象解耦,更加符合面向对象。(还是不知道有什么实际的好处)

2021.8.27更新:
以我现在的理解,比起继承一个类而言,实现一个接口更加“轻量”,更加“直观”。接口就像一个个行为一样,实现这个接口,就意味着:“我要有这个能力”。一个具体的要做事情的类可以实现很多接口,当它想要扩展新的“能力”时,就去实现新的接口即可。
而继承一个类,通常是,我想要“拥有你的行为”,同时还可能会点别的行为,继承更像是扩展不足。一个类A继承其他类B,看起来就好像这个类A就是类B的一族一样。所以,如果想要获得某种能力,还是优先选择继承一个接口。

匿名内部类

另一种方式是使用匿名内部类,不用新建类。

  1. package MuliThread.two;
  2. public class Test {
  3. public static void main(String[] args) {
  4. Runnable runnable = new Runnable() {
  5. @Override
  6. public void run() {
  7. for (int i = 0; i < 50; i++) {
  8. try {
  9. Thread.sleep(500);
  10. } catch (InterruptedException e) {
  11. System.out.println("睡眠方法出错");
  12. }
  13. System.out.println("========"+i);
  14. }
  15. }
  16. };
  17. Runnable runnable2 = new Runnable() {
  18. @Override
  19. public void run() {
  20. for (int i = 0; i < 50; i++) {
  21. try {
  22. Thread.sleep(500);
  23. } catch (InterruptedException e) {
  24. System.out.println("睡眠方法出错");
  25. }
  26. System.out.println("++++++"+i);
  27. }
  28. }
  29. };
  30. Thread thread = new Thread(runnable);
  31. Thread thread2 = new Thread(runnable2);
  32. thread.start();
  33. thread2.start();
  34. }
  35. }

多线程并发执行:

Callable

要了解Callable,必须了解JAVA的线程体系。

接口FutureRunnable。接口RunnableFuture继承上述两个接口。

FutureTask实现接口RunnableFuture

Future

  1. public interface Future<V> {
  2. boolean cancel(boolean mayInterruptIfRunning);
  3. boolean isCancelled();
  4. boolean isDone();
  5. V get() throws InterruptedException, ExecutionException;
  6. V get(long timeout, TimeUnit unit)
  7. throws InterruptedException, ExecutionException, TimeoutException;
  8. }

Runnable

  1. @FunctionalInterface
  2. public interface Runnable {
  3. public abstract void run();
  4. }

Thread

  1. public class Thread implements Runnable {
  2. xxx
  3. }

RunnableFuture

  1. public interface RunnableFuture<V> extends Runnable, Future<V> {
  2. void run();
  3. }

FutureTask

  1. public class FutureTask<V> implements RunnableFuture<V> {
  2. xxx
  3. }

类FutureTask有一构造函数,可接受参数Callable类型

Callable

  1. @FunctionalInterface
  2. public interface Callable<V> {
  3. V call() throws Exception;
  4. }

FutureTask类实现了Runnable和Future接口,所以可以把FutureTask当作Runnable,作为参数,放到Thread里面。

所以使用Callable接口创建线程时是这样的步骤:

1、自定义类实现Callable,实现call()方法,里面写线程执行任务代码。

2、创建FutureTask实例,将上述自定义类的实例作为参数传入。

3、创建Thread实例,将上述FutureTask实例作为参数传入。

4、执行Thread的start(),即可创建新线程并执行任务。

代码实现:

Demo

  1. package MuliThread.three;
  2. import java.util.concurrent.Callable;
  3. public class Demo implements Callable {
  4. private Integer a;
  5. private Integer b;
  6. public Demo(Integer a, Integer b) {
  7. this.a = a;
  8. this.b = b;
  9. }
  10. @Override
  11. public Object call() throws Exception {
  12. for (int i = 0; i < 100; i++) {
  13. System.out.println(this.a + "===========" + i);
  14. try {
  15. Thread.sleep(500);
  16. } catch (InterruptedException e) {
  17. System.out.println("睡眠方法出错");
  18. }
  19. }
  20. return this.a + this.b;
  21. }
  22. }

Demo2

  1. package MuliThread.three;
  2. import java.util.concurrent.Callable;
  3. public class Demo2 implements Callable {
  4. private Integer a;
  5. private Integer b;
  6. public Demo2(Integer a, Integer b) {
  7. this.a = a;
  8. this.b = b;
  9. }
  10. @Override
  11. public Object call() throws Exception {
  12. for (int i = 0; i < 100; i++) {
  13. System.out.println(this.a + "+++++++++++" + i);
  14. try {
  15. Thread.sleep(500);
  16. } catch (InterruptedException e) {
  17. System.out.println("睡眠方法出错");
  18. }
  19. }
  20. return this.a + this.b;
  21. }
  22. }

Test

  1. package MuliThread.three;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.FutureTask;
  4. public class Test {
  5. public static void main(String[] args) {
  6. Callable demo = new Demo(1,2);
  7. Callable demo2 = new Demo2(3,4);
  8. /*try {
  9. //直接调用call()不会触发新线程的创建,顺序执行两个call()
  10. Object call = demo.call();
  11. Object call2 = demo2.call();
  12. System.out.println("结束语句");
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }*/
  16. FutureTask task = new FutureTask(demo);
  17. FutureTask task2 = new FutureTask(demo2);
  18. Thread thread = new Thread(task);
  19. Thread thread2 = new Thread(task2);
  20. thread.start();
  21. thread2.start();
  22. }
  23. }

多线程并发执行任务:

FutureTask的应用:

FutureTask继承体系的核心是Future接口,Future的核心思想是:一个方法f,计算过程可能非常耗时,等待f返回,显然不明智。可以在调用f的时候,立马返回一个Future,可以通过Future这个数据结构去控制方法f的计算过程。

这里的控制包括:

get方法:获取计算结果(如果还没计算完,也是必须等待的)【重载方法中,可以设置最多等待时间】

cancel方法:还没计算完,可以取消计算过程

isDone方法:判断是否计算完

isCancelled方法:判断计算是否被取消

  1. package MuliThread.three;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.ExecutionException;
  4. import java.util.concurrent.FutureTask;
  5. public class Test {
  6. public static void main(String[] args) throws ExecutionException, InterruptedException {
  7. Callable demo = new Demo(1,2);
  8. Callable demo2 = new Demo2(3,4);
  9. FutureTask task = new FutureTask(demo);
  10. FutureTask task2 = new FutureTask(demo2);
  11. Thread thread = new Thread(task);
  12. Thread thread2 = new Thread(task2);
  13. thread.start();
  14. thread2.start();
  15. System.out.println(thread.getName() + "线程是否已经计算出结果了?" + task.isDone());
  16. System.out.println(thread2.getName() + "线程呢?" + task2.isDone());
  17. System.out.println("那我等待他计算完成再获取结果吧(运行到此方法时会阻塞等待call()的结果噢)..." + task.get());
  18. System.out.println("我也等待结果..." + task2.get());
  19. }
  20. }

多线程并发执行:

ExecutorService

ExecutorService是一个线程池,他的submit方法,该方法重载了多个方法,分别可接受一个Runnable类型或Callable类型的参数。因此可以自定义类实现Callable或Runnable,然后将此类的实例传入submit,即可开启多线程,执行线程任务。

MyRunnable

  1. package MuliThread.pool;
  2. import java.util.concurrent.Callable;
  3. public class MyCallable implements Callable {
  4. private String name;
  5. public MyCallable(String name) {
  6. this.name = name;
  7. }
  8. @Override
  9. public Object call() throws Exception {
  10. for (int i = 0; i < 10; i++) {
  11. System.out.println(name+":"+i);
  12. try {
  13. Thread.sleep(500);
  14. } catch (InterruptedException e) {
  15. System.out.println("睡眠方法出错");
  16. }
  17. }
  18. System.out.println("执行完毕");
  19. return null;
  20. }
  21. }

MyRunnable

  1. package MuliThread.pool;
  2. public class MyRunnable implements Runnable {
  3. private String name;
  4. public MyRunnable(String name) {
  5. this.name = name;
  6. }
  7. @Override
  8. public void run() {
  9. for (int i = 0; i < 10; i++) {
  10. System.out.println(name+":"+i);
  11. try {
  12. Thread.sleep(600);
  13. } catch (InterruptedException e) {
  14. System.out.println("睡眠方法出错");
  15. }
  16. }
  17. System.out.println("执行完毕");
  18. }
  19. }

ThreadPoolDemo

  1. package MuliThread.pool;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. /**
  5. * 线程池的使用:
  6. * 1、用 工厂 创建出线程池
  7. * 2、自定义实现Callable或Runnable的类实例化对象
  8. * 3、执行submit(),从 线程池 中拿到一个线程,绑定到上述对象上,并开始执行线程任务
  9. * 4、关闭线程池
  10. */
  11. public class ThreadPoolDemo {
  12. public static void main(String[] args) {
  13. //得到线程池
  14. ExecutorService pool = Executors.newFixedThreadPool(4);
  15. //创建线程任务
  16. MyRunnable mR = new MyRunnable("线程池练练");
  17. MyRunnable mR2 = new MyRunnable("线程池习习");
  18. MyCallable mC = new MyCallable("CCC线程池练练");
  19. MyCallable mC2 = new MyCallable("CCC线程池习习");
  20. //从线程池中获取线程对象,然后执行mR的任务
  21. pool.submit(mR);
  22. pool.submit(mR2);
  23. pool.submit(mC);
  24. pool.submit(mC2);
  25. //submit方法调用结束后,main线程并不会立即结束。
  26. //因为线程池控制了线程的关闭,将使用完的线程归还到了线程池后还在维护这个线程池
  27. pool.shutdown();//手动关闭线程池后,main程序没有了任务
  28. }
  29. }

多线程并发执行任务:

线程池结合Fature

MyNumCallable

  1. package MuliThread.pool;
  2. import java.util.concurrent.Callable;
  3. public class MyNumCallable implements Callable {
  4. private Integer a;
  5. private Integer b;
  6. public MyNumCallable(Integer a, Integer b) {
  7. this.a = a;
  8. this.b = b;
  9. }
  10. @Override
  11. public Integer call() throws Exception {
  12. System.out.println("正在计算结果...");
  13. try {
  14. Thread.sleep(3000);
  15. } catch (InterruptedException e) {
  16. System.out.println("睡眠方法出错");
  17. }
  18. return this.a+this.b;
  19. }
  20. }

Practice

  1. package MuliThread.pool;
  2. import java.util.concurrent.ExecutionException;
  3. import java.util.concurrent.ExecutorService;
  4. import java.util.concurrent.Executors;
  5. import java.util.concurrent.Future;
  6. /**
  7. * 利用线程做任务:加法运算
  8. * 通过Future实现有返回结果的线程
  9. */
  10. public class Practice {
  11. public static void main(String[] args) throws ExecutionException, InterruptedException {
  12. //得到线程池
  13. ExecutorService pool = Executors.newFixedThreadPool(3);
  14. //自定义线程任务
  15. MyNumCallable mNC = new MyNumCallable(6,8);
  16. MyCallable myC = new MyCallable("另一个任务");
  17. //开启多线程执行任务
  18. Future result = pool.submit(mNC);
  19. pool.submit(myC);
  20. //在计算结果的时候还能执行线程myC的任务
  21. Integer i = (Integer) result.get();
  22. System.out.println("结果是:" + i);
  23. //关闭线程池
  24. pool.shutdown();
  25. }
  26. }

多线程并发执行:

线程常用方法

Thread

  1. join() //等待该线程终止
  2. //挂起线程的意思:线程会自动的释放掉占有的线程资源锁,以便于其他线程可以获取道线程资源执行他们的任务
  3. //挂起,相当于让线程睡觉,不让他干活;唤醒,相当于叫醒他,让他继续执行任务。(也不一定是一唤醒就执行任务,还需要抢到线程资源)
  4. wait()//挂起线程
  5. notify()/notifyAll()//唤醒线程
  6. park()//挂起线程
  7. unpark()//唤醒线程
  8. sleep()//也能将线程挂起,睡眠时间过了后会自动唤醒线程
  9. interrupt()//主动唤醒睡眠的线程
  10. suspend()//已过时,会引发死锁 挂起线程
  11. resume()//已过时,会引发死锁 唤醒线程

总结

上述三种方式中,1、2两种方式run()均不能存在返回值,不能抛出异常。

由于1方式是直接继承Thread类,JAVA是单继承,继承此类后就不能继承其他类了,所以比较不灵活。

而2方式是实现接口,一个类实现此接口后也可实现其他接口。

而第三种方式代码没有1、2简洁。

而线程池(ExecutorService)的方式其实还是对其他方法的使用而已。