Android 线程基础 二 线程创建和中断

tech2024-04-20  109

线程基础 二

一、创建线程

继承Thread类,重写run()方法

Thread本质上也是实现Runnable接口的一个实例。

定义Thread的子类,重写该方法的run方法,该run方法的方法体就代表了线程要完成的任务。因此,run()方法被称作方法体创建Thread子类的实例对象调用线程对象的start()方法启动 public class TestThread extends Thread{ public void run(){ super.run(); String name = this.getName(); for(int i = 0 ; i < 10 ; i++){ if(i == 5){ try{ //抛出异常 Thread.sleep(1000*6); }catch(InterruptedException e){ e.printStackTrace(); } } System.out.println(name + " " + i); } } public static void main(String[] args) { Thread thread1 = new TestThread(); thread1.start(); Thread thread2 = new TestThread(); thread2.start(); } } 结果: Thread-0 0 Thread-1 0 Thread-0 1 Thread-1 1 Thread-0 2 Thread-1 2 Thread-0 3 Thread-1 3 Thread-0 4 Thread-1 4 Thread-1 5 Thread-0 5 Thread-1 6 Thread-0 6 Thread-1 7 Thread-0 7 Thread-1 8 Thread-0 8 Thread-1 9 Thread-0 9

两个子线程抢占CPU执行,每次执行的顺序不一样的,充分表现了CPU调度。

实现Runnable接口,并实现该接口的run()方法

自定义类并实现Runnable接口,实现run()方法创建Thread子类的实例,用实现Runnable接口的对象作为参数实例化该Thread对象调用Thread的start()方法启动该线程 public class TestRunnable implements Runnable{ private int tickets = 20; private Object object = new Object(); @Override public void run(){ while(true){ synchronized(object){ if(tickets < 1){ break; } try { Thread.sleep(100); } catch (Exception e) { //TODO: handle exception e.printStackTrace(); } String name = Thread.currentThread().getName(); System.out.println(name + "正在卖第" + tickets + "张票"); tickets--; } } } public static void main(String[] args) { TestRunnable mRunnable = new TestRunnable(); Thread mThread1 = new Thread(mRunnable,"窗口1"); Thread mThread2 = new Thread(mRunnable,"窗口2"); Thread mThread3 = new Thread(mRunnable,"窗口3"); mThread1.start(); mThread2.start(); mThread3.start(); } } 结果: 窗口1正在卖第20张票 窗口1正在卖第19张票 窗口1正在卖第18张票 窗口3正在卖第17张票 窗口3正在卖第16张票 窗口3正在卖第15张票 窗口3正在卖第14张票 窗口3正在卖第13张票 窗口3正在卖第12张票 窗口2正在卖第11张票 窗口2正在卖第10张票 窗口3正在卖第9张票 窗口3正在卖第8张票 窗口3正在卖第7张票 窗口3正在卖第6张票 窗口3正在卖第5张票 窗口3正在卖第4张票 窗口3正在卖第3张票 窗口1正在卖第2张票 窗口3正在卖第1张票

多个线程使用同一个加锁对象,处理同一份资源。

实现Callable接口,重写call()方法

Callable接口实际是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要表现在以下3点:

Callable可以在任务接受后提供一个返回值,Runnable无法提供这个功能。

Callable中的call()方法可以抛出异常,而Runnable的run()方法不能抛出异常。

运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下就可以使用Future来监视目标线程调用call()方法的情况。但调用Future的get()方法以获取结果时,当前线程就会阻塞,知道call()方法返回结果。

public class TestCallable { //创建线程类 public static class MyTestCallable implements Callable<Integer>{ @Override public Integer call() throws Exception { int i = 0; for(;i < 30; i++){ System.out.println(Thread.currentThread().getName() + " " + i); } return i; } } public static void main(String[] args) { MyTestCallable myTestCallable = new MyTestCallable(); FutureTask<Integer> futureTask = new FutureTask<>(myTestCallable); for(int i = 0; i < 30; i++){ System.out.println(Thread.currentThread().getName() + "循环变量i的值" + i); if(i == 20){ new Thread(futureTask,"有返回值的线程").start(); } } try { System.out.println("子线程的返回值" + futureTask.get()); } catch (InterruptedException e) { e.printStackTrace(); }catch(ExecutionException e){ e.printStackTrace(); } } } 结果: main循环变量i的值0 main循环变量i的值1 main循环变量i的值2 main循环变量i的值3 main循环变量i的值4 main循环变量i的值5 main循环变量i的值6 main循环变量i的值7 main循环变量i的值8 main循环变量i的值9 main循环变量i的值10 main循环变量i的值11 main循环变量i的值12 main循环变量i的值13 main循环变量i的值14 main循环变量i的值15 main循环变量i的值16 main循环变量i的值17 main循环变量i的值18 main循环变量i的值19 main循环变量i的值20 main循环变量i的值21 main循环变量i的值22 main循环变量i的值23 main循环变量i的值24 main循环变量i的值25 main循环变量i的值26 main循环变量i的值27 main循环变量i的值28 main循环变量i的值29 有返回值的线程 0 有返回值的线程 1 有返回值的线程 2 有返回值的线程 3 有返回值的线程 4 有返回值的线程 5 有返回值的线程 6 有返回值的线程 7 有返回值的线程 8 有返回值的线程 9 有返回值的线程 10 有返回值的线程 11 有返回值的线程 12 有返回值的线程 13 有返回值的线程 14 有返回值的线程 15 有返回值的线程 16 有返回值的线程 17 有返回值的线程 18 有返回值的线程 19 有返回值的线程 20 有返回值的线程 21 有返回值的线程 22 有返回值的线程 23 有返回值的线程 24 有返回值的线程 25 有返回值的线程 26 有返回值的线程 27 有返回值的线程 28 有返回值的线程 29 子线程的返回值30

实现Callabe接口,多线程共享同一个对象,处理同一份资源。

三种方式对比

采用实现Runnable、Callable接口的方式创见多线程时

优势是:

线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是:

编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

使用继承Thread类的方式创建多线程时

优势是:

编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势是:

线程类已经继承了Thread类,所以不能再继承其他父类。

二、理解中断

首先为什么要中断,是为了安全地终止线程。

​ 当线程的run方法执行完毕,或者在方法中出现没有捕获的异常的时候,线程进入终止。但是,有些时候,比如用户关闭了当前页面了,但是耗时活动获取数据还没完成,所以,这时候就要终止线程,避免内存泄漏,否则用户觉得好玩,一直打开关闭打开关闭,一直榨干内存。

​ 所以,这时候就要提供给程序员一种主动终止线程的方法,本来在早期版本是有一个stop的方法的,但是这个方法现在已经被弃用了,这里分析以下为什么stop方法会被弃用了。

stop方法弃用

官方解释,我的是JDK1.8中Thread的解释

// Android-changed: Throws UnsupportedOperationException. /** * Throws {@code UnsupportedOperationException}. * * @deprecated This method was originally designed to force a thread to stop * and throw a {@code ThreadDeath} as an exception. It was inherently unsafe. * Stopping a thread with * Thread.stop causes it to unlock all of the monitors that it * has locked (as a natural consequence of the unchecked * <code>ThreadDeath</code> exception propagating up the stack). If * any of the objects previously protected by these monitors were in * an inconsistent state, the damaged objects become visible to * other threads, potentially resulting in arbitrary behavior. Many * uses of <code>stop</code> should be replaced by code that simply * modifies some variable to indicate that the target thread should * stop running. The target thread should check this variable * regularly, and return from its run method in an orderly fashion * if the variable indicates that it is to stop running. If the * target thread waits for long periods (on a condition variable, * for example), the <code>interrupt</code> method should be used to * interrupt the wait. * For more information, see * <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why * are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.

stop的.java文件也被修改了,只抛出一个错误了,原本实现已经注释了。

@Deprecated public final void stop() { /* SecurityManager security = System.getSecurityManager(); if (security != null) { checkAccess(); if (this != Thread.currentThread()) { security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION); } } // A zero status value corresponds to "NEW", it can't change to // not-NEW because we hold the lock. if (threadStatus != 0) { resume(); // Wake up thread if it was suspended; no-op otherwise } // The VM can handle all thread states stop0(new ThreadDeath()); */ throw new UnsupportedOperationException(); }

​ 这段话的意思是这个stop天生是不安全的,停掉当前线程的时候,会导致所有已经锁定的监听器被解锁(因为当threaddeath异常在堆栈中传播的时候,监听器被解锁),这就导致可能出现任何不可知的后果。

并且官方给出的网页说明了不能捕获**ThreadDeath**异常并修复对象的原因: 1. 一个线程几乎可以在任何地方抛出一个`ThreadDeath`异常。考虑到这一点,所有同步的方法和块都必须详细研究。 2. 一个线程可以抛出第二个`ThreadDeath`异常,同时从第一个线程清除(在catch或finally子句中)。清理将不得不重复,直到它成功。确保这一点的代码将非常复杂。 所以捕获`ThreadDeath`异常是不可取的。

​ 总而言之,其实,按照项目中实际使用,stop一旦调用可能出现的不安全如下:

当调用该方法会立刻停止线程运行,放弃所有未执行的代码,包括catch和finally语句中的,所以因此可能会导致任务清理工作无法完成,比如文件流,数据库指针等的关闭。该方法会释放该线程的所有监听器的资源监视器,由于任务的执行成不成都成了不可知,因为stop的时候,鬼知道它执行到哪一步了,所有导致该线程所持有的资源状态不确定,可能出现数据不一致的问题。
理解中断

既然stop方法已经被弃用了,但是,对于要中断线程的这个需求还是存在的,所以中断机制就应运而生了,为了方便程序员在需要的时候终止线程。

首先我们要理解这个中断机制是怎么样工作的。

工作机制:

interrupt方法可以用来请求中断线程,当一个线程调用interrupt()方法时,线程中断标识位将被置位为true,线程会时不时检测这个中断标识位,以判断这个线程是否应该被中断,如果想查看标识位信息,可以使用Thread.currentThread().isInterrupted()查看标识位信息。还可以调用Thread.interrupeted()来对中断标识位进行复位。

用Thread的interrupt安全地终止线程

其实调用Thread对象的interrupt函数并不是立即中断线程,只是将线程中断状态标志设置为true,当线程运行中有调用其阻塞的函数(Thread.sleep,Object.wait,Thread.join等)时,阻塞函数调用之后,会不断地轮询检测中断状态标志是否为true,如果为true,则停止阻塞并抛出InterruptedException异常,同时还会重置中断状态标志;如果为false,则继续阻塞,直到阻塞正常结束。

结束未使用阻塞函数的线程
public class interript0 { //结束未使用阻塞函数的线程 public static void main(String[] args) { InterruptClass interruptClass = new InterruptClass(); Thread mThread = new Thread(interruptClass); mThread.start(); long i = System.currentTimeMillis(); while(System.currentTimeMillis() - i < 10*50){ mThread.isAlive(); } mThread.interrupt(); } static class InterruptClass implements Runnable{ @Override public void run() { System.err.println("开始工作!"); while(!Thread.currentThread().isInterrupted()){ System.out.println("在工作中!"); } System.err.println("结束工作了!");; } } }

就是判断是否中断标志位被置位为TRUE了,一旦被置位,就跳出来,表示当前结束了。

结束使用阻塞函数的线程
public class interript1 { //结束使用阻塞函数的线程 public static void main(String[] args) { InterruptClass interruptClass = new InterruptClass(); Thread mThread = new Thread(interruptClass); mThread.start(); long i = System.currentTimeMillis(); while(System.currentTimeMillis() - i < 10*1000){ mThread.isAlive(); } mThread.interrupt(); } static class InterruptClass implements Runnable{ @Override public void run() { System.err.println("开始工作!"); while(!Thread.currentThread().isInterrupted()){ System.out.println("在工作中!"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } System.err.println("结束工作了!");; } } }

这里添加了一个try中的阻塞函数,所以当检测出interrupt的时候,要置位表示当前线程已经被中断了,这时候才能跳出循环,结束当前线程。

最新回复(0)