线程概念及使用场景
线程是操作系统中最小的执行单元,一个进程中可以有多个线程,他们可以并发的执行多个任务。同一个进程中的多线程是共享内存的,可以很方便的相互通信
线程是一次性消费品,一般用于耗时任务中,避免阻塞UI线程。线程的创建和销毁是比较消耗CPU资源的,所以,一般不频繁的创建和销毁线程,如果存在这种需求,请使用线程池。
创建线程
线程的使用方式有两种:
- 继承Thread对象,重写run方法
- 继承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对象并没有提供很好的结束线程的方法,不过可以有如下几种方法:
使用flag标记:死循环用flag标记做条件,当检测到flag!=true时,退出线程,根据外部条件改变flag标记。
捕捉中断异常:在线程中使用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的区别
- sleep是thread类中的方法,wait是object类中的方法
- sleep不会释放锁资源,而wait会释放锁资源
- 两个方法都会放弃CPU执行权,wait等待notify释放锁唤醒,而sleep在睡眠时间结束就等CPU自动调度
- 使用时,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.优先级越高,被执行的越快。