Android线程知识

线程概念及使用场景

线程是操作系统中最小的执行单元,一个进程中可以有多个线程,他们可以并发的执行多个任务。同一个进程中的多线程是共享内存的,可以很方便的相互通信

线程是一次性消费品,一般用于耗时任务中,避免阻塞UI线程。线程的创建和销毁是比较消耗CPU资源的,所以,一般不频繁的创建和销毁线程,如果存在这种需求,请使用线程池。

创建线程

线程的使用方式有两种:

  1. 继承Thread对象,重写run方法
  2. 继承Runnable接口,实现run方法
继承Thread,重写run方法
private class MyThread extends Thread {
        public MyThread(String name){
            super(name);
        }
        @Override
        public void run() {
            Log.i(TAG, "run: 线程处理任务的地方");
        }
    }
 private MyThread mThread = new MyThread("MyThread");//创建线程,并制定线程名字
 mThread.start();//启动线程
继承Runnable接口,实现run方法
private class MyRunnable implements Runnable {
        @Override
        public void run() {
            Log.i(TAG, "run: 线程处理任务的地方");
        }
    }
private MyRunnable mRunnable = new MyRunnable();
new Thread(mRunnable, "MyRunnable Thread").start();
  • 注意:无数次解bug的经验告诉我,创建线程时,切记要传name参数来制定线程名字,否则出bug时,你很难跟踪是哪个线程的锅。
线程的生命周期

一个线程从创建到销毁可能要经过如下6个状态:

状态说明
新建(new)线程创建,处于初始状态,但还没有开始执行
就绪(runnable)线程调用了start方法,等待jvm调度
运行(running)线程正在运行,执行任务
阻塞(block)线程被synchronized锁住了,正在等待其他线程释放锁,调用wait,join,sleep,线程进入等待状态
死亡(dead)整个线程执行结束,线程死亡

线程状态图

多线程同步问题
对象锁

一个类可以有多个对象,也就可以有多个对象锁,对象与对象之间的锁没有竞争关系,但是同一个对象中的对象锁是有竞争关系,也就是对象中某个方法获取锁后,整个对象都锁住了,其他带锁的方法和代码块都被锁住了。
对象锁的两种表现形式:

//对象锁,锁住整个方法
private synchronized void testObjectLock1() {
    // TODO: 2018/3/19  
}
//对象锁,锁住代码块
private void testObjectLock2() {
        synchronized (this) {
            // TODO: 2018/3/19  
        }
 }
类锁

一个类可以有多个对象,所以多个对象共享一把类锁,对象与对象之间是有锁资源的竞争关系。对象A和对象B都继承自同一个类,如果对象A获取了锁,那么对象B同样被锁住了。故使用类锁时,请考虑锁的范围是否太大?
类锁的两种表现形式:

//静态方法锁:由于静态方法不依赖于对象,所以静态方法锁是类锁
    public static synchronized void testClassLock1() {
        // TODO: 2018/3/19  
    }
//类锁,锁住代码块,同一个类的其他对象都被锁住了
    private void testClassLock2() {
        synchronized (TestLockActivity.class) {
            // TODO: 2018/3/19
        }
    }
死锁

两个线程在执行过程中并,互相等待对方的锁资源而永远得不到的现象叫做死锁。举例:线程1先获得A锁,等地B锁,线程2先获得B锁,等待A锁。线程1一直等不到B锁,线程2一直等不到A锁。
死锁例子:

 private Object mLock1 = new Object();
    private Object mLock2 = new Object();

    private void testA() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (mLock1) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (mLock2) {
                        Log.i(TAG, "testA: get mLock2, start task");
                    }
                }
            }
        }, "testA_Thread").start();
    }

    private void testB() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (mLock2) {
                    try {
                        Thread.sleep(2050);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (mLock1) {
                        Log.i(TAG, "testB: get mLock1, start task");
                    }
                }

            }
        }, "testB_Thread").start();
    }

A线程持有Lock1锁,等待Lock2锁,B线程持有Lock2锁,等待Lock1锁。A线程永远等不到Lock2锁,因为B线程没有获得Lock1锁,从而永远不会释放Lock2锁,同理B线程也永远等不到Lock1锁。如此,导致循环等待,却永远等不到对方的锁。

举个例子:有两个宝箱,每个宝箱都要两把锁同时才能打开,当锁插进去了,且只有宝箱打开了锁才能取下来。现在蒙古大汗送给郭靖和华筝各有一个相同的宝箱,郭靖拿着第一把锁,华筝拿着第二把锁。他们想打开宝箱的正确姿势是同时先用两把锁打开第一个宝箱,之后取下锁,同时打开第二把锁。时间飞快,郭靖只身来到中原,认识了漂亮的黄蓉,出于好奇,黄蓉拿着郭靖的第一把锁去开宝箱,结果发现需要第二把锁,此时第一把锁已经取不出来了。他们就去找华筝,但是吧,华筝看到他的靖哥哥不在喜欢他,也就用第二把锁去开他自己的宝箱,这下子完了,郭靖的箱子在等华筝的锁,华筝的箱子在等郭靖的锁,但是对方的锁都拔不出来了。那么出现了死锁现象。杯具了。

如何优雅的结束线程

一般在线程中开一个死循环做任务,那么当退出应用时,该如何优雅的结束正在运行的线程呢?
Thread对象并没有提供很好的结束线程的方法,不过可以有如下几种方法:

  1. 使用flag标记:死循环用flag标记做条件,当检测到flag!=true时,退出线程,根据外部条件改变flag标记。

  2. 捕捉中断异常:在线程中使用sleep,wait,notify等方法都需要捕获InterruptedException异常,当我们捕获到了该异常就直接跳出循环,退出线程

private void testQuitThread(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                //外部根据场景改变mQuit变量值来退出循环,从而退出线程
                while (!mQuit) {
                    // TODO: 2018/3/19 耗时任务
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        //捕获到中断异常,直接break;退出整个循环,从而退出线程
                        break;
                    }
                }
            }
        }).start();
    }

interrupt方法:该方法并不是中断线程,而是通知线程可以中断了,至于是否要中断,由线程自己控制。分两种情况:
1.当线程处于正常活动时,调用该方法,仅仅是设置中断标志为true,也就是此时Thread.isInterrupted()方法返回true。
2.当线程处于阻塞状态时(sleep),调用了该方法,线程会抛出InterruptedException异常,同时中断标志为false,也就是此时Thread.isInterrupted()方法返回false。

wait,notify,notifyAll

wait,notify方法时Object类的方法,并不是Thread类特有的方法

注意:以上三个方法使用的前提是必须被它的对象锁锁住。

  • wait:放弃cpu控制权,释放锁资源,等待对象调用notify通知唤醒
  • notify:放弃cpu控制权,释放锁资源,唤醒某一个正在wait等待的对象锁。
  • notifyAll:放弃cpu控制权,释放锁资源,唤醒所有正在wait等待的对象锁。

生产者-消费者模型:

private Object mObjectLock = new Object();
    private void producer() {
        while (true) {
            Log.i(TAG, "producer: start");
            synchronized (mObjectLock) {
                if (mProductorNum == TOTAL) {
                    try {
                        Log.i(TAG, "producer: 生产者已满,等待消费");
                        mObjectLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    mProductorNum++;
                    mObjectLock.notify();
                    Log.i(TAG, "producer: producer==>" + mProductorNum);
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void customer() {
        new Thread(mRunnable, "MyRunnable Thread").start();
        while (true) {
            Log.i(TAG, "customer: start");
            synchronized (mObjectLock) {
                if (mProductorNum == 0) {
                    try {
                        Log.i(TAG, "customer: 消费者已空,等待生产");
                        mObjectLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    try {
                        if (mProductorNum >= 5) {
                            Thread.sleep(900);
                        } else {
                            Thread.sleep(1000);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        Log.i(TAG, "customer: mCustomer state=>>" + mCustomerThread.isInterrupted());
                    }
                    mProductorNum--;
                    Log.i(TAG, "customer: customer==>" + mProductorNum);
                    mObjectLock.notify();
                }
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
wait 和sleep的区别
  1. sleep是thread类中的方法,wait是object类中的方法
  2. sleep不会释放锁资源,而wait会释放锁资源
  3. 两个方法都会放弃CPU执行权,wait等待notify释放锁唤醒,而sleep在睡眠时间结束就等CPU自动调度
  4. 使用时,wait必须只有自己的对象锁,否则抛异常;而sleep就不需要
join,yield,isAlive
  • join:join是Thread提供了让一个线程等待另一个线程完成的方法。当在某个线程执行过程中,调用其他线程的join()方法时,调用者将会被阻塞,知道其他线程执行完成为止。示例代码如下:
private Thread mThreadA = new Thread(new Runnable() {
        @Override
        public void run() {
            // TODO: 2018/3/19 耗时任务 
        }
    });
    private Thread mThreadB = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                //在B线程中调用A线程的join方法,B线程被阻塞,直到A线程结束,B线程才会继续往下执行
                mThreadA.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
  • yield:该方法是Thread提供的让当前正在运行的线程暂停,但是它不会阻塞线程,它只是让该线程直接转入到就绪状态。也就是yield()方法只是让当前线程暂停了一下,让系统的线程调度器重新调度一次,所以很可能出现这种情况:当该线程调用了yield()方法,之后该线程又立马被调度了。
  • isAlive()判断线程是否还存活,当线程start了,但是还没结束,这期间线程是存活的
线程优先级

Thread提供了设置线程执行优先级的方法setPriority(),该方法参数范围如下:

/**
     * 最低优先级为1.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * 默认优先级为5.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * 最高优先级是10
     */
    public final static int MAX_PRIORITY = 10;

线程的优先级等级范围 1~10,1优先级最低,10优先级最高。默认优先级为中等也就是5.优先级越高,被执行的越快。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值