JAVA中的线程 2020-03-14 16:07 # 创建线程的三种方法 > 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 `Demo`和`Demo2`是两个多线程类,`Test`里面测试多线程执行 **只有调用start()方法才会创建新线程,直接调用run()方法是不会创建新线程的,只是在main线程中的一个方法顺序执行** **为什么?** 因为start方法内部执行start0方法,这个方法是native的,实际会去执行Java封装的C++写的方法,在C++方法中,调用操作系统,由操作系统创建一个线程并执行任务。 那为什么需要start方法呢?直接用run方法,一个方法得了。不行,因为我们需要一个方法供我们使用Java语言把我们线程要做的事情写进去,所以这个方法必须是我们使用者拿java语言写的。那真正调用C++,让操作系统创建线程的方法,就必须要有另外的方法。 所以,run方法只是普通的Java方法,start方法才是开启线程并执行的方法。也即要满足用户自定义线程这个需求必须有两个方法,A用来描述做什么事情,B用来开启线程做这个事情。 `Demo.java` ```java package MuliThread.one; public class Demo extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(800); } catch (InterruptedException e) { System.out.println("睡眠方法出错"); } System.out.println(this.getName()+":"+i); } } } ``` `Demo2.java` ```java package MuliThread.one; public class Demo2 extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("睡眠方法出错"); } System.out.println(this.getName()+":"+i); } } } ``` `Test.java` ```java package MuliThread; public class Test { public static void main(String[] args) { /* 创建线程的目的是什么? 是为了建立程序单独的执行路径,让多部分代码实现“同时”执行。也就是说线程创建并执行需要给定线程要执行的任务。 例如:导入功能。 */ Demo d1 = new Demo(); Demo2 d2 = new Demo2(); d1.setName("====="); d2.setName("+++++"); //两个线程并发执行 d1.start(); d2.start(); //当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。 //调用run方法无法开启线程,因为直接调用run方法是执行方法,还是在主线程种。而start()方法才会开启新线程。 //d1.run();//run执行完才会往下执行,所以不会在并发执行,而是顺序执行 //d2.start(); } } ``` 多线程并发执行:  **为什么要创建类继承`Thread`,而不是直接调用`Thread`的`start()`方法呢?** Thread类的run()方法没有做事,所以你需要重写run()方法,将你创建新线程需要执行的代码写进去。 ### 匿名内部类 另一种使用是直接创建Thread ```java package MuliThread.one; public class Test { public static void main(String[] args) { //可以改成lamdba表达式 new Thread(){ @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("==="+i); } } }.start(); new Thread(){ @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("+++"+i); } } }.start(); } } ``` 多线程并发执行:  # Runnable `Demo`是线程任务,实现`Runnable`接口,实现`run()`方法;创建`Thread`线程对象,传入`Demo`类,调用`start()`方法即可。 `Demo.java` ```java package MuliThread.two; //线程任务 public class Demo implements Runnable { private String name; public Demo(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(name+":"+i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("睡眠方法出错"); } } System.out.println("执行完毕"); } } ``` `Test.java` ```java package MuliThread.two; public class Test { public static void main(String[] args) { Demo demo1 = new Demo("hanxu");//线程任务 Thread thread1 = new Thread(demo1);//线程对象 Demo demo2 = new Demo("ctj"); Thread thread2 = new Thread(demo2); thread1.start(); thread2.start(); } } ``` 多线程并发执行:  使用`Runnable`的好处:将**线程任务**和**线程对象**解耦,更加符合面向对象。(还是不知道有什么实际的好处) 2021.8.27更新: 以我现在的理解,比起继承一个类而言,实现一个接口更加“轻量”,更加“直观”。接口就像一个个行为一样,实现这个接口,就意味着:“我要有这个能力”。一个具体的要做事情的类可以实现很多接口,当它想要扩展新的“能力”时,就去实现新的接口即可。 而继承一个类,通常是,我想要“拥有你的行为”,同时还可能会点别的行为,继承更像是扩展不足。一个类A继承其他类B,看起来就好像这个类A就是类B的一族一样。所以,如果想要获得某种能力,还是优先选择继承一个接口。 ### 匿名内部类 另一种方式是使用匿名内部类,不用新建类。 ```java package MuliThread.two; public class Test { public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("睡眠方法出错"); } System.out.println("========"+i); } } }; Runnable runnable2 = new Runnable() { @Override public void run() { for (int i = 0; i < 50; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("睡眠方法出错"); } System.out.println("++++++"+i); } } }; Thread thread = new Thread(runnable); Thread thread2 = new Thread(runnable2); thread.start(); thread2.start(); } } ``` 多线程并发执行:  # Callable  要了解Callable,必须了解JAVA的线程体系。 接口**Future**,**Runnable**。接口**RunnableFuture**继承上述两个接口。 类**FutureTask**实现接口**RunnableFuture**。 `Future` ```java public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; } ``` `Runnable` ```java @FunctionalInterface public interface Runnable { public abstract void run(); } ``` `Thread` ```java public class Thread implements Runnable { xxx } ``` `RunnableFuture` ```java public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); } ``` `FutureTask` ```java public class FutureTask<V> implements RunnableFuture<V> { xxx } ``` 类FutureTask有一构造函数,可接受参数Callable类型  `Callable` ```java @FunctionalInterface public interface Callable<V> { V call() throws Exception; } ``` FutureTask类实现了Runnable和Future接口,所以可以把FutureTask当作Runnable,作为参数,放到Thread里面。 所以使用Callable接口创建线程时是这样的步骤: 1、自定义类实现Callable,实现call()方法,里面写线程执行任务代码。 2、创建FutureTask实例,将上述自定义类的实例作为参数传入。 3、创建Thread实例,将上述FutureTask实例作为参数传入。 4、执行Thread的start(),即可创建新线程并执行任务。 代码实现: `Demo` ```java package MuliThread.three; import java.util.concurrent.Callable; public class Demo implements Callable { private Integer a; private Integer b; public Demo(Integer a, Integer b) { this.a = a; this.b = b; } @Override public Object call() throws Exception { for (int i = 0; i < 100; i++) { System.out.println(this.a + "===========" + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("睡眠方法出错"); } } return this.a + this.b; } } ``` `Demo2` ```java package MuliThread.three; import java.util.concurrent.Callable; public class Demo2 implements Callable { private Integer a; private Integer b; public Demo2(Integer a, Integer b) { this.a = a; this.b = b; } @Override public Object call() throws Exception { for (int i = 0; i < 100; i++) { System.out.println(this.a + "+++++++++++" + i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("睡眠方法出错"); } } return this.a + this.b; } } ``` `Test` ```java package MuliThread.three; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) { Callable demo = new Demo(1,2); Callable demo2 = new Demo2(3,4); /*try { //直接调用call()不会触发新线程的创建,顺序执行两个call() Object call = demo.call(); Object call2 = demo2.call(); System.out.println("结束语句"); } catch (Exception e) { e.printStackTrace(); }*/ FutureTask task = new FutureTask(demo); FutureTask task2 = new FutureTask(demo2); Thread thread = new Thread(task); Thread thread2 = new Thread(task2); thread.start(); thread2.start(); } } ``` 多线程并发执行任务:  ### FutureTask的应用: > FutureTask继承体系的核心是Future接口,Future的核心思想是:一个方法f,计算过程可能非常耗时,等待f返回,显然不明智。可以在调用f的时候,立马返回一个Future,可以通过Future这个数据结构去***控制***方法f的计算过程。 这里的***控制***包括: **get**方法:**获取计算结果**(如果还没计算完,也是必须等待的)【重载方法中,可以设置最多等待时间】 **cancel**方法:还没计算完,可以**取消计算**过程 **isDone**方法:判断**是否计算完** **isCancelled**方法:判断计算**是否被取消** ```java package MuliThread.three; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { Callable demo = new Demo(1,2); Callable demo2 = new Demo2(3,4); FutureTask task = new FutureTask(demo); FutureTask task2 = new FutureTask(demo2); Thread thread = new Thread(task); Thread thread2 = new Thread(task2); thread.start(); thread2.start(); System.out.println(thread.getName() + "线程是否已经计算出结果了?" + task.isDone()); System.out.println(thread2.getName() + "线程呢?" + task2.isDone()); System.out.println("那我等待他计算完成再获取结果吧(运行到此方法时会阻塞等待call()的结果噢)..." + task.get()); System.out.println("我也等待结果..." + task2.get()); } } ``` 多线程并发执行:  # ExecutorService > ExecutorService是一个线程池,他的submit方法,该方法重载了多个方法,分别可接受一个Runnable类型或Callable类型的参数。因此可以自定义类实现Callable或Runnable,然后将此类的实例传入submit,即可开启多线程,执行线程任务。 `MyRunnable` ```java package MuliThread.pool; import java.util.concurrent.Callable; public class MyCallable implements Callable { private String name; public MyCallable(String name) { this.name = name; } @Override public Object call() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(name+":"+i); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("睡眠方法出错"); } } System.out.println("执行完毕"); return null; } } ``` `MyRunnable` ```java package MuliThread.pool; public class MyRunnable implements Runnable { private String name; public MyRunnable(String name) { this.name = name; } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(name+":"+i); try { Thread.sleep(600); } catch (InterruptedException e) { System.out.println("睡眠方法出错"); } } System.out.println("执行完毕"); } } ``` `ThreadPoolDemo` ```java package MuliThread.pool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 线程池的使用: * 1、用 工厂 创建出线程池 * 2、自定义实现Callable或Runnable的类实例化对象 * 3、执行submit(),从 线程池 中拿到一个线程,绑定到上述对象上,并开始执行线程任务 * 4、关闭线程池 */ public class ThreadPoolDemo { public static void main(String[] args) { //得到线程池 ExecutorService pool = Executors.newFixedThreadPool(4); //创建线程任务 MyRunnable mR = new MyRunnable("线程池练练"); MyRunnable mR2 = new MyRunnable("线程池习习"); MyCallable mC = new MyCallable("CCC线程池练练"); MyCallable mC2 = new MyCallable("CCC线程池习习"); //从线程池中获取线程对象,然后执行mR的任务 pool.submit(mR); pool.submit(mR2); pool.submit(mC); pool.submit(mC2); //submit方法调用结束后,main线程并不会立即结束。 //因为线程池控制了线程的关闭,将使用完的线程归还到了线程池后还在维护这个线程池 pool.shutdown();//手动关闭线程池后,main程序没有了任务 } } ``` 多线程并发执行任务:  ## 线程池结合Fature `MyNumCallable` ```java package MuliThread.pool; import java.util.concurrent.Callable; public class MyNumCallable implements Callable { private Integer a; private Integer b; public MyNumCallable(Integer a, Integer b) { this.a = a; this.b = b; } @Override public Integer call() throws Exception { System.out.println("正在计算结果..."); try { Thread.sleep(3000); } catch (InterruptedException e) { System.out.println("睡眠方法出错"); } return this.a+this.b; } } ``` `Practice` ```java package MuliThread.pool; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; /** * 利用线程做任务:加法运算 * 通过Future实现有返回结果的线程 */ public class Practice { public static void main(String[] args) throws ExecutionException, InterruptedException { //得到线程池 ExecutorService pool = Executors.newFixedThreadPool(3); //自定义线程任务 MyNumCallable mNC = new MyNumCallable(6,8); MyCallable myC = new MyCallable("另一个任务"); //开启多线程执行任务 Future result = pool.submit(mNC); pool.submit(myC); //在计算结果的时候还能执行线程myC的任务 Integer i = (Integer) result.get(); System.out.println("结果是:" + i); //关闭线程池 pool.shutdown(); } } ``` 多线程并发执行:  # 线程常用方法 `Thread` ```java join() //等待该线程终止 //挂起线程的意思:线程会自动的释放掉占有的线程资源锁,以便于其他线程可以获取道线程资源执行他们的任务 //挂起,相当于让线程睡觉,不让他干活;唤醒,相当于叫醒他,让他继续执行任务。(也不一定是一唤醒就执行任务,还需要抢到线程资源) wait()//挂起线程 notify()/notifyAll()//唤醒线程 park()//挂起线程 unpark()//唤醒线程 sleep()//也能将线程挂起,睡眠时间过了后会自动唤醒线程 interrupt()//主动唤醒睡眠的线程 suspend()//已过时,会引发死锁 挂起线程 resume()//已过时,会引发死锁 唤醒线程 ``` # 总结 上述三种方式中,1、2两种方式run()均不能存在返回值,不能抛出异常。 由于1方式是直接继承Thread类,JAVA是单继承,继承此类后就不能继承其他类了,所以比较不灵活。 而2方式是实现接口,一个类实现此接口后也可实现其他接口。 而第三种方式代码没有1、2简洁。 而线程池(ExecutorService)的方式其实还是对其他方法的使用而已。 --END--
发表评论