美文网首页
笔记-Android中的线程使用

笔记-Android中的线程使用

作者: e小e | 来源:发表于2018-12-13 10:05 被阅读15次

    目录

    Java中的线程
    • Java中如何创建线程
    • Java中的线程同步问题(synchronized关键字,lock, wait,notify,notifyall)
    • Java中保证成员变量访问的同步和原子操作
    • Java中如何终止线程
    Android中的线程
    • 线程间的通信Handler,Looper,MessageQueue
    • ThreadLocal
    • Asynctask引起内存泄漏

    下面开始正文

    Java中的线程

    提起android中去使用线程,我们首先必须搞懂java中线程的一些基本概念.

    1, Java中如何创建线程

    在java中创建线程的几种方式
    第一种: 直接new Thread

    Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.print("执行线程");
                }
            });
            thread.start();
    

    第二种: 线程工厂

     ThreadFactory factory = new ThreadFactory() {
                @Override
                public Thread newThread(@NonNull Runnable r) {
                    return new Thread(r, "线程名字");
         }
    };
    Runnable runnable = new Runnable() {
          @Override
          public void run() {
               System.out.println(Thread.currentThread().getName() + " started!");
          }
    };
    Thread thread1 = factory.newThread(runnable);
    thread1.start();
    

    第三种: 线程池

    Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("Thread with Runnable started!");
                }
            };
    Executor executor = Executors.newCachedThreadPool();
    executor.execute(runnable);
    
    2,Java中的线程同步问题

    在使用线程过程,难免会遇到线程同步的问题,首先必须清楚线程不同步是如何产生的,然后再来看看解决方法.
    下面看一段示例代码,它会出现一个情况就是线程不同步.

    public static class ThreadTest{
            private static int x,y;
    
            public static void startThread1(){
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 100_00_00; i++){
                            x = i;
                            y = i;
                        }
    
                        if (x != y){
                            System.out.println("x != y x = "+x+" y = "+y);
                        }
                    }
                });
                thread.start();
            }
    
            public static void startThread2(){
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 100_00_00; i++){
                            x = i;
                            y = i;
                        }
    
                        if (x != y){
                            System.out.println("x != y x = "+x+" y = "+y);
                        }
                    }
                });
                thread.start();
            }
        }
    

    我们运行startThread1()和startThread2()会输出

    x != y x = 394076 y = 396613
    

    这是因为出现了线程的不同步才产生的异常现象, 这是在线程1运行过程中,线程2也在运行导致多线程操作x,y,从而x y 不相等.
    为了避免线程的不同步,java中引入了synchronized关键字. 下面对需要线程同步的代码块加入synchronized关键字.

    public static class ThreadTest{
            private static int x,y;
    
            public static void startThread1(){
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (ThreadDemoActivity.class){
                            for (int i = 0; i < 100_00_00; i++){
                                x = i;
                                y = i;
                            }
    
                            if (x != y){
                                System.out.println("x != y x = "+x+" y = "+y);
                            }
                        }
                    }
                });
                thread.start();
            }
    
            public static void startThread2(){
                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (ThreadDemoActivity.class){
                            for (int i = 0; i < 100_00_00; i++){
                                x = i;
                                y = i;
                            }
    
                            if (x != y){
                                System.out.println("x != y x = "+x+" y = "+y);
                            }
                        }
                    }
                });
                thread.start();
            }
        }
    

    这下就不会有前面提到的线程不同步的问题了. synchronized关键字有几种不同的使用方法
    synchronized关键字加到方法前面

    public synchronized void demo(){
            //...
    }
    

    synchronized定义代码块

    private Object lock = new Object();
    
        public void demo(){
            synchronized (lock){
                //...   
            }
        }
    

    synchronized都会去关联到一个锁对象,前者加到方法前面的synchronized,它的锁对象是类的对象,后者则是使用自定义的锁对象,前者更加方便,但是它只能指定一个类对象作为锁对象,后者更加灵活自由,可以定义同步代码块,自定义锁对象(就像刚开始的示例代码中使用了ThreadDemoActivity.class作为锁对象,当然也可以自定义一个对象作为锁对象)
    synchronized除了解决上面提到的线程互斥访问问题,还会会解决另外一个问题数据的同步问题. 当我们在代码中有一个成员变量

    x = 1
    

    一个线程a要对x = 5赋值,它需要分三个步骤,一个是拷贝x成员变量到它线程所属的内存区域,二是把x的值赋成5,三是把x = 5放回原有的内存区域. 因为这样做会提高效率。synchronized在解决线程间同步问题的时候也顺带解决了这个数据的同步问题。
    所以总结一下:
    synchronized解决了两个问题,问题一数据访问的互斥,问题二是数据的同步问题

    为了解决线程间的同步问题,除了使用synchronized关键字之外,还可以使用lock来解决,不过它用起来会比较麻烦。通常的代码格式是:

    private Lock lock = new ReentrantLock();
    
    public void demo(){
        lock.lock();
        //同步代码块
        lock.unlock();
    }
    

    用得比较常见的地方的读写锁,在一个线程写的时候不允许别的线程写和读,但是在一个线程读的情况下,运行别的线程读,但是不能写。

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    private ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    
    private int x = 1;
    
    public void write(){
        writeLock.lock();
        x = 5;
        writeLock.unlock();
    }
    
    public void print(){
         readLock.lock();
         System.out.print("x = "+x);
         readLock.unlock();
    }
    

    关于wait, notify和notifyAll的使用.
    在程序设计中,我们会有一个线程必须满足一个条件才能继续执行的需求,而这个条件是另外一个线程去达成的,可以看下下面代码

    private String shareMsg = null;
    
        private synchronized void initshareMsg(){
            shareMsg = "share msg";
        }
    
        private synchronized void printshareMsg(){
            while (shareMsg != null){
                System.out.print(shareMsg);
            }
        }
    
        public void run(){
            Thread thread_1 = new Thread(() -> {
                initshareMsg();
            });
            Thread thread_2 = new Thread(() -> {
                printshareMsg();
            });
            thread_2.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread_1.start();
        }
    

    很显然上面的代码会出现死锁的现象,printshareMsg会拿到锁一直循环下去,initshareMsg永远拿不到monitor,所以不能执行, 我们可以通过wait和notify的配合使用来达到我们想要的效果. 修改后代码如下.

    private String shareMsg = null;
    
        private synchronized void initshareMsg(){
            shareMsg = "share msg";
            notify();   //放弃monitor,通知之前wait的线程,继续执行
        }
    
        private synchronized void printshareMsg(){
            while (shareMsg == null){
                try {
                    wait();     //等待,放弃monitor,让别的线程获得monitor
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.print(shareMsg);
            }
        }
    
        public void run(){
            Thread thread_1 = new Thread(() -> {
                initshareMsg();
            });
            Thread thread_2 = new Thread(() -> {
                printshareMsg();
            });
            thread_2.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread_1.start();
        }
    

    那么notifyall又有什么用?如果有多个线程都处于wait状态,那么仅仅调用notify只能唤醒一个线程,而notifyall可以唤醒所有处于wait的线程来竞争monitor.

    3, Java中保证成员变量访问的同步和原子操作

    关键字:volatile, Atomic相关类
    上面提到了,synchronized解决了数据访问的互斥和数据的同步问题,但是单独对一个成员变量来说不能加synchronized关键字,而我们仅仅对一个成员变量做数据访问的互斥和数据的同步加上synchronized关键字又会感觉太麻烦,目前就有一个很好解决这个问题的方法是使用
    volatile关键字和Atomic相关类,它们的作用容易混淆.
    volatile主要用来解决的是数据的同步问题


    image.png

    如上图,未加volatile的情况下线程b获取到x的值可能出现x = 1的情况,加上volatile关键字就可以避免这个问题.
    Atomic相关类主要解决的问题是像a++这样的操作,a++这个操作其实分成了两步,一步是r = a+1, 第二步是 a = r. 这显然不是一个原子操作,使用AtomicInteger则把a++封装成为一个原子操作.

    AtomicInteger a = new AtomicInteger();
    a.incrementAndGet();
    

    a++不能靠volatile保证原子操作,volatile解决的是同步问题Atomic相关的类解决的原子操作问题.

    4, Java中如何终止线程

    通常我们开启一个线程的后,会有终止一个线程的需求,那么在java中是如何去终止线程呢?可以通过调用

    thread.stop()
    

    它可以立即终止线程,但是它有个不好的地方是在于,立即终止是存在一定的风险,因为线程正常执行过程中立即终止会导致程序出现异常。所以stop这个方法是被弃用的,而正规终止线程的方式是使用interrupt去终止线程.

    Thread thread = new Thread(){
                @Override
                public void run() {
                    super.run();
                    for (int i = 0; i < 100; i++){
                        if (isInterrupted()){
                            //线程终止,收尾操作
                        }
                    }
                }
            };
            thread.interrupt();
    

    通过isInterrupted判断外界是否调用了线程终止,从而进行一些收尾操作后终止线程,这样显然更加安全. 另外Thread还有一个判断终止的接口是Thread.interrupt(),它和isInterrupted()的区别是Thread.interrupt()调用后会把中断标志位重置,意思就是Thread.interrupt()第一次调用后是true,第二次调用后就是false了.
    另外我们在使用Thread.sleep通常会抛出InterruptedException

    try {
          Thread.sleep(2000);
     } catch (InterruptedException e) {
          e.printStackTrace();
    }
    

    这个InterruptedException,就是在线程休眠过程中调用interrupt就会抛出该异常.

    Android中的线程

    1,线程间的通信Handler,Looper,MessageQueue

    Looper
    其实Android中线程和java线程区别就是,Android提供了一种创建无限循环线程的模式, 就是looper机制,我们来看看如何在java中去创建无限循环的线程.

    private void run(){  //执行入口
            CustomizableThread customizableThread = new CustomizableThread();
            //设置任务
            customizableThread.setTask(new Runnable() {
                @Override
                public void run() {
                    System.out.print("执行任务");
                }
            });
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //退出循环
            customizableThread.quit();
        }
    
        /**
         * 创建一个无限循环的线程
         */
        class CustomizableThread extends Thread {
            private Runnable task;
            private boolean quit;
    
            synchronized void setTask(Runnable task){
                this.task = task;
            }
    
            synchronized void quit(){
                quit = true;
            }
    
            @Override
            public void run() {
                super.run();
                while (!quit){
                    synchronized (this){
                        if (task != null){
                            task.run();
                            task = null;
                        }
                    }
                }
            }
        }
    

    上面这种无限循环的线程就是Android经常提到的Looper的原型了. 在Android中它把上面提到的无限循环的机制写成了一个 Looper对象,大致如下(当然实际代码肯定比这个复杂很多,这里只是为了说明Looper到底起到了什么作用)

    class Looper {
            private Runnable task;
            private boolean quit;
    
            synchronized void setTask(Runnable task){
                this.task = task;
            }
    
            synchronized void quit(){
                quit = true;
            }
    
            public void loop(){
                while (!quit){
                    synchronized (this){
                        if (task != null){
                            task.run();
                            task = null;
                        }
                    }
                }
            }
        }
    

    另外looper会把这个task做成一个队列的形式,那就是MessageQueue了,那么Handler又是起到什么作用呢,Handler它其实一个关联一个looper的对象,用于像looper中MessageQueue发送消息,大致的模型如下:


    image.png
    ThreadLocal

    接下来说下ThreadLocal,ThreadLocal是用来存放线程独立的对象,什么是线程独立的线程对象,我们知道线程之间是可以共享内存的,

    public class ThreadLocalDemo {
        private Integer mInteger = new Integer(1);  //线程之间是共享的.
    
        private void run(){
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    mInteger = 2;
                }
            });
            thread1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.print(mInteger);
                }
            });
            thread2.start();
        }
    }
    

    那么如果使用ThreadLocal的话就可以做到内存的独立。

    public class ThreadLocalDemo {
        private Integer mInteger = new Integer(1);
    
        static ThreadLocal<Integer> sThreadLocal1 = new ThreadLocal<>();
        static ThreadLocal<Integer> sThreadLocal2 = new ThreadLocal<>();
    
        public void run(){
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    sThreadLocal1.set(mInteger);
                    Integer integer = sThreadLocal1.get();
                    integer = 3;
                    System.out.println("ThreadLocal thread1 integer "+integer);
                }
            });
            thread1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    sThreadLocal2.set(mInteger);
                    Integer integer = sThreadLocal2.get();
                    System.out.println("ThreadLocal thread2 integer "+integer);
                }
            });
            thread2.start();
        }
    }
    
    I/System.out: ThreadLocal thread1 integer 3
    I/System.out: ThreadLocal thread2 integer 1
    

    上面结果输出可以说明thread1对integer的赋值只对thread1生效.
    实际上在android中looper就是用ThreadLocal进行存放的,这样可以做到每个线程之间looper是独立的.

    public final class Looper {
          static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
          private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
        //其余代码省略...
    }
    
    Asynctask引起内存泄漏

    什么是内存泄漏,内存泄漏是GC Root引用链中有无用的对象。这里的GC Root有3种
    1,正在运行的线程
    2,static变量
    3,native引用到的
    Asynctask实际上是后台启动线程,返回到ui线程的一个过程,它常常会引用到外部的Activity引用,导致Activity的内存泄漏,但是这是暂时的,通常情况下Asynctask不会长期引起内存泄漏.

    相关文章

      网友评论

          本文标题:笔记-Android中的线程使用

          本文链接:https://www.haomeiwen.com/subject/dmujcqtx.html