笔记-Android中的线程使用

2018-12-13  本文已影响15人  e小e

目录

Java中的线程
Android中的线程

下面开始正文

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不会长期引起内存泄漏.

上一篇下一篇

猜你喜欢

热点阅读