android 多线程 — Thread

2018-05-30  本文已影响138人  前行的乌龟

java 的线程类型是 Thread ,所以多线程的学习也是从 Thread 开始的

继承 Thread 类


启动一个线程,执行我们定义的任务,最简单的方式就是继承 Thread 类了

// 创建 Thread 类
class MyThread extends Thread{
    private String name ;

    public MyThread(){
        name = "AA";
    }

    @Override
    public void run() {
        System.out.println(name);
    }
}

// 启动 Thread 线程对象
MyThread thread = new MyThread();
        thread.start();

注意线程对象的启动是通过 start() 执行的

实现 Runnable 接口


创建自己的线程类型,我们还可以把线程的核心代码写到 Runnable 接口里,然后通过参数传递给 Thread 对象

// Runnable 对象类型
class MyRunnable implements Runnable{
    public MyRunnable() {
    }

    @Override
    public void run() {
        System.out.println("子线程ID:"+Thread.currentThread().getId());
    }
}

// 把 Runnable 作为参数传递给 Thread 对象
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();

接口的方式更灵活,我们不用去专门写一个 Thread 类出来了,Runnable 直接一个匿名实现类多方便

Callable、Future、FutureTask


这3个类总体来是是处理返回数据的线程的,这3个都是一起用,不难

上面我们可以把一个 Runnable 类型的接口对象传进 Thread 线程对象里面去运行,不知道大家注意到没有,这个 Runnable 是没有返回值的,我们启动线程之后只能使用 handle 做跨线程通信

那么有没有其他的方式啊,有啊 Callable 接口,启动线程之后,线程获取到结果后,可以把这个结果返回给我们,这个结果有个统一的包装类型,就是 Future,然后 Future.get() 方法我们就能拿到这个结果了,但是呢有一点,这个 Future.get() 是需要等待其他线程运行知道结束繁返回数据,也就是说会阻塞我们的当前线程,不过 Callable、Future 都是配合 java 的Executor 线程池框架的,也不会想我们上面那样直接传给 Thread 使用。

不过完事没有绝对,FutureTask 就是能在 Thread 里面运行的 Callable、Future。Thread 构造方法里面只能接受 Runnable 类型的接口对象,FutureTask 内部间接的实现 Runnable 接口,所以能直接在 Thread 里使用

来看个小例子,先启动一个线程,睡眠2秒,之后返回结果,我们在 UI 线程里等待结果(此时会阻塞 UI 线程) ,收到结果后再启动一个线程,2个线程都打印下时间,看看能不能符合我们的预期

        // 带返回结果的线程任务,String 泛型是我们预订的返回数据类型
        Callable<String> callable1 = new Callable<String>() {
            @Override
            public String call() throws Exception {
                Log.d("AA", Thread.currentThread().getName() + "启动,time:" + System.currentTimeMillis());
                Thread.sleep(2000);
                return "AA";
            }
        };
        
        // 普通线程任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Log.d("AA", Thread.currentThread().getName() + "启动,time:" + System.currentTimeMillis());
            }
        };

        // 用 Callable 对象构建一个 FutureTask  线程返回结果
        FutureTask<String> futureTask = new FutureTask<String>(callable1);

        // 创建出2个线程
        Thread thread1 = new Thread(futureTask);
        Thread thread2 = new Thread(runnable);

        // 先启动会返回结果的线程
        thread1.start();
        try {
            // 这里阻塞 UI 线程,等待线程1 的返回数据,再去启动线程2
            futureTask.get();
            thread2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
结果

从时间看对的上,线程2在线程12000毫秒后执行的

Runnable 和 Callable 的区别在接口声明上就能看的很明显了

public interface Runnable {  
    public abstract void run();  
}  
public interface Callable<V> {   
      V   call()   throws Exception;   
}

我们再看看 Future 的这个接口

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;  
}
FutureT 执行示意图

好了 Callable、Future、FutureTask 就先说到这里,下面这个例子是 Callable、Future 在线程池中执行的例子,这个才是最常用的需求

        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "AA";
            }
        };
        
        // new 一个线程池对象
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        // 把任务添加到线程池中去执行,获取到一个 Future 任务结果操作对象
        Future<String> future = executorService.submit(callable);

        try {
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        Log.d("AA", Thread.currentThread().getName() + "获取到异步线程结果,time:" + System.currentTimeMillis());

Thread 线程的状态


线程作为一个复杂的系统,因各种情况的不同会有好几种状态,对这几种线程状态我们必须做到熟知才行

线程状态:

线程运行描述:

线程状态运行图

blocked、waiting、time waiting 都可以叫阻塞,详细区别下是有必要的,涉及的方法不同

Thread 类方法解析


上面我们说了如何创建一个 Thread 线程对象并执行,获取到结果。其实 Thread 自身还有一些很重要的方法需要记住。

我简单点直接放张图


Thread 方法列表
System.out.println(Thread.currentThread().getName());
// 当前线程睡眠1秒
Thread.currentThread().sleep(1000);
Thread.yield();
Thread t= Thread.currentThread();
System.out.println(t.getName()+" "+t.getId());
        Thread t1 = new Thread();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.d("AA", Thread.currentThread().getName() + "isAlive:" + t1.isAlive());
Snip20180530_2.png

可以看到就绪状态都不行,都不算是活跃状态,其他的可想而知

        Thread t2 = new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    Thread.sleep(2000);
                    Log.d("AA", Thread.currentThread().getName() + "时间,time:" + System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        try {
            Log.d("AA", Thread.currentThread().getName() + "时间,time:" + System.currentTimeMillis());
            t2.start();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Log.d("AA", Thread.currentThread().getName() + "时间,time:" + System.currentTimeMillis());
Snip20180530_3.png

可以看到 UI 线程被成功的阻塞了,和我们的预期一样,和 callable 和 Future 的效果是一样的

守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

找到张图表示线程各方法的效果:


9110701-a08a6158edf64957.jpeg

停止线程


线程停止是个重要的点,有3中写法:

线程的优先级


在Java中,线程的优先级分为1~10这10个等级,如果小于1或大于10,则JDK抛出异常throw new IllegalArgumentException()。
JDK中使用3个常量来预置定义优先级的值,代码如下:

Thread thread = new Thread();
t1.setPriority( Thread.NORM_PRIORITY );

线程优先级特性:

守护线程


在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。

守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:

thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)

在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)

不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。

最后

我这里写的也不是很全,有没看懂的建议大家再去找资料,这里主要是给我自己看,按照的我的记忆顺序写的, 以后忘了再看方便的多。

有不全的大家见谅,有错误请下面留言我更正

参考资料:

上一篇 下一篇

猜你喜欢

热点阅读