一、线程创建
线程的创建包括三种方法,分别是继承自Thread,实现Runnable接口、实现Callback接口,前两个在开发中最为常见。
1、继承自Thread
public class OneThread extends Thread {
@Override
public void run() {
Log.e("TAG","OneThread:" + Thread.currentThread().getName());
}
}
2、实现Runnable接口
public class TwoThread implements Runnable {
@Override
public void run() {
Log.e("TAG","TwoThread:" + Thread.currentThread().getName());
}
}
测试:
public void create(View view) {
OneThread oneThread = new OneThread();
TwoThread twoThread = new TwoThread();
//线程一
oneThread.start();
//线程二
Thread thread = new Thread(twoThread);
thread.start();
}
结果:
二、线程终止
在早期的jdk中有一个stop方法可以终止线程,但是已经被弃用了,提供了interrupt方法来中断线程,当一个线程调用interrupt中断时候,线程中线程中断标识位会被置于true,线程会不断的检查这个标识位,来判断是否中断,这里可以调用Thread.currentThread.isInterrupted判断。线程中断与否。除此自外还可以使用Thread.interrupted进行复位。
中断原则:如果一个线程处于了阻塞状态,如线程调用了thread.sleep、thread.join、thread.wait等,再检查时候发现中断标识位为true,则会在阻塞方法处抛出InterruptedExcept异常,并且在抛出异常前将线程中的标识位置为false。这个异常应该是需要处理的。
线程的安全处理方法一是在catch里面进行Thread.currentThread.Interrupted处理,让外界Thread.currentThread.isInterrupted进行判断;另一种方法是使用一个boolean值进行设置。
1、使用interrupt终止
public class ThreeThread implements Runnable {
@Override
public void run() {
Log.e("TAG", "ThreeThread:" + Thread.currentThread().getName());
while (!Thread.currentThread().isInterrupted()) {
Log.e("TAG", "线程没有中断");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
Log.e("TAG", "线程ThreeThread终止了");
}
}
测试:
public void stop(View view) {
Thread threeThread = new Thread(new ThreeThread());
threeThread.start();
SystemClock.sleep(1000);
threeThread.interrupt();
}
结果:
2、使用boolean变量进行终止
public class FiveThread implements Runnable {
private boolean on = true;
@Override
public void run() {
Log.e("TAG", "FiveThread:" + Thread.currentThread().getName());
while (on) {
Log.e("TAG", "线程没有中断");
SystemClock.sleep(500);
}
Log.e("TAG", "线程FiveThread终止了");
}
public void cancel(){
this.on = false;
}
}
测试:
public void stop(View view) {
FiveThread runnable = new FiveThread();
Thread fiveThread = new Thread(runnable);
fiveThread.start();
SystemClock.sleep(1000);
runnable.cancel();
}
结果:
三、线程同步
Lock和Condition接口为开发人员提供了高度的程序锁定控制,但是大多数情况下并不需要使用Lock和Condition进行复杂的次序控制,在Java语言内部,每一个对象都有一个内部锁。如果一个方法被synchronized声明那么对象锁将锁住整个方法或者同步代码块,要调用该方法或者执行或者执行该代码块,必须拿到该所锁对象,否则只能等待无法访问。也就是说任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized。
同时Java也提供了wait、notify、notifyAll三个方法进行线程同步,wait方法是将一个线程添加到等待集合中,notify、notifyAll是解除等待线程的阻状态。二者的区别是notify表示唤醒一个线程,notifyAll也表示唤醒一个线程,但它会notify所有的线程,具体唤醒哪一个线程,由jvm来决定。主要的效果区别是notify用得不好容易导致死锁,锁池中的队列空了,等待池中有一堆线程,但不会再被唤醒永远等待。
再借用知乎中一个通俗的说法来进行说明:比如说,你是你家挣钱的,儿子和女儿是花钱的。儿子给家里要100,女儿要30。可是家里没钱,他们只能在外面等待池里面等。后来你出去打工,赚钱了,赚了50,这时你要在儿子和女儿之间选择一个人叫醒。如果使用notify不凑巧,你把儿子叫醒了,儿子发现钱还是不够,又去等。因为你只能叫一次,女儿就错过了使用这50块钱的机会。如果下一次也叫醒了儿子或者女儿不等了,那么就会进入死锁。你决定使用notifyAll把所有的人都叫醒,虽然费劲一点。这样一来,儿子发现不够,接着等,女儿发现够了,就用了。
现在以生产者消费者模式来进行详细说明,生产者生产商品供消费者进行消费,因此需要创建两个线程,一个是生产者线程一个是消费者线程,这里面需要协调好生产者和消费者的关系,生产者的生产要供得上消费者的使用,同时应该满足生产者不至于每次生产的太多产品,够消费者使用即可。不能供大于求或者供过于求现象。
1、商品类
public class Goodes {
public int num;
public String name;
@Override
public String toString() {
return "Goodes{" +
"num=" + num +
", name='" + name + '\'' +
'}';
}
}
2、生产者
public class SixThread1 implements Runnable {
private Goodes mGoodes;
public Goodes getGoodes() {
return mGoodes;
}
public void setGoodes(Goodes goodes) {
this.mGoodes = goodes;
}
@Override
public void run() {
//模拟生产30个商品
for (int i = 0; i < 30; i++) {
//使用synchronized加锁,以保证次序
synchronized (mGoodes) {
if (mGoodes.num > 0) {
try {
//如果有商品就不进行生产,本次商品生产进入等待池
mGoodes.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有商品这进行生产
mGoodes.name = "android进阶之光";
mGoodes.num = mGoodes.num + 1;
Log.e("TAG", "生产了" + mGoodes.name + "---------" + mGoodes.num);
//本次商品生产完毕,唤醒消费者线程开始消费
mGoodes.notify();
}
}
}
}
3、消费者
public class SixThread2 implements Runnable {
private Goodes mGoodes;
public Goodes getGoodes() {
return mGoodes;
}
public void setGoodes(Goodes goodes) {
this.mGoodes = goodes;
}
@Override
public void run() {
//模拟消费30个商品
for (int i = 0; i < 30; i++) {
//加锁保证每次只能走一个消费流程
synchronized (mGoodes) {
if (mGoodes.num <= 0) {
try {
//如果本次没有东西消费了进入等待池,等待生产
mGoodes.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//模拟耗时,生产和消费不同步
SystemClock.sleep(1000);
Log.e("TAG", "取走了" + mGoodes.name + "---------" + mGoodes.num);
//消费了一个goodes
mGoodes.num = mGoodes.num - 1;
//唤醒生产者线程开始生产
mGoodes.notify();
}
}
}
}
测试:
public void synchronization(View view) {
Goodes goodes = new Goodes();
//生产者生产商品
SixThread1 sixThread1 = new SixThread1();
sixThread1.setGoodes(goodes);
//消费者取走商品
SixThread2 sixThread2 = new SixThread2();
sixThread2.setGoodes(goodes);
new Thread(sixThread1).start();
new Thread(sixThread2).start();
}
这里生产者和消费者必须是同一个Goodes对象,以保证,生产和消费的同一个。同时两个线程的对象锁也应该保持一致,否则出现异常。
结果:
四、阻塞队列
在新增的Concurrent包中,队列阻塞很好的解决了多线程中如何高效安全传输数据的问题,通过队列可以很容易实现数据共享,比如上述的生产者和消费者模型,就不需要使用wait和nofity在生产者和消费者之间通信了,从而简化了很多。
1、BlockingQueue的核心方法:
(1)offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.(本方法不阻塞当前执行方法的线程);
(2)offer(E o, long timeout, TimeUnit unit):可以设定等待的时间,如果在指定的时间内,还不能往队列中加入BlockingQueue,则返回失败。
(3)put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.
(4)poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
(5)poll(long timeout, TimeUnit unit):从BlockingQueue取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则知道时间超时还没有数据可取,返回失败。
(6)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入;
(7)drainTo():一次性从BlockingQueue获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。
2、常见的阻塞队列
(1)ArrayBlockingQueue
他是用数组实现的有界的阻塞队列,,按照先进先出的原则对元素进行排序,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,同时ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。默认情况下不保证线程公平地访问队列。
(2)LinkedBlockingQueue
基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回,只有当队列缓冲区达到最大值缓存容量时才阻塞;当消费者从队列中消费掉一份数据,生产者线程会被唤醒。如果构造一个LinkedBlockingQueue而没有指定容量的大小,他会默认采取Integer的最大值,这样一来肯能没有扥到队列阻塞产生就产生OOM了。一般情况下才处理多线程生产者消费者问题的时候使用这两个类足够了,其他的类不再介绍。
3、队列阻塞案例 —— 生产者消费者模式
在上面使用了同步方法来实现生产者消费者模式,现在使用队列阻塞来实现。
(1)生产者
public class SevenThread1 implements Runnable {
private ArrayBlockingQueue<Goodes> mQueue;
public void setGueue(ArrayBlockingQueue<Goodes> queue) {
this.mQueue = queue;
}
@Override
public void run() {
//模拟生产5个商品
for (int i = 0; i < 5; i++) {
Goodes goodes = new Goodes();
goodes.name = "android进阶之光" + (i + 1);
goodes.num = goodes.num + 1;
try {
//将商品加入队列
mQueue.put(goodes);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("TAG", "生产者队列里面目前有:" + mQueue.size() + "个数据,为:" + goodes.toString());
}
}
}
(2)消费者
public class SevenThread2 implements Runnable {
private ArrayBlockingQueue<Goodes> mQueue;
public void setGueue(ArrayBlockingQueue<Goodes> queue) {
this.mQueue = queue;
}
@Override
public void run() {
while (true) {
//模拟耗时,生产和消费不同步
SystemClock.sleep(1000);
Goodes take = null;
try {
//从队列里面取出商品,按照先存先取的顺序
take = mQueue.take();
Log.e("TAG", "消费者队列里面目前有:" + mQueue.size() + "个数据,为:" + take.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试:
public void blockingQueue(View view) {
ArrayBlockingQueue<Goodes> queue = new ArrayBlockingQueue<>(10, false);
//生产者生产商品
SevenThread1 sevenThread1 = new SevenThread1();
sevenThread1.setGueue(queue);
//消费者1取走商品
SevenThread2 sevenThread2 = new SevenThread2();
sevenThread2.setGueue(queue);
new Thread(sevenThread1).start();
new Thread(sevenThread2).start();
}
结果: