一、传统的线程通信
-
要借助于Object类提供的wait()、notift()、notifyAll(),这三个方法都要由同步监视器来调用,分下面两种情况:
- synchronized 修饰的同步方法,同步监视器就是该类的默认实例(this),所以可以直接在同步代码块中调用这三个方法
- synchronized 修饰的同步代码块,同步监视器是synchronized 括号中的对象,所以要用对象调用这三个方法。
-
wait():导致线程等待,直到其他线程调用该**同步监视器的notify()/motifyAll()方法
-
notify:唤醒此同步监视器上的单个线程,如果所有线程都在此同步监视器下等待,则随机唤醒一个,只有当前线程放弃对该同步监视器的锁定后(调用wait()),才可以执行被唤醒的线程
-
notifyAll():唤醒此同步监听器上的所有等待的线程,只有当前线程放弃对该同步监视器的锁定后(调用wait()),才可以执行被唤醒的线程
public class Account {
// 银行账户
private String accountNo;
//余额
private int balance;
// 是否有钱
private boolean flag = false;
public Account(String accountNo, int balance) {
this.accountNo = accountNo;
this.balance = balance;
}
/**
* 取钱
*/
public synchronized void draw(int monny) throws InterruptedException {
if (!flag){
// 如果没有钱可取,就等待
wait();
}else {
if (balance>=monny){
Log.e("testthread",Thread.currentThread().getName()+"取钱成功:"+monny);
balance-=monny;
Log.e("testthread","余额:"+balance);
flag = false;
// 唤醒等待的线程
notifyAll();
}else {
Log.e("testthread",Thread.currentThread().getName()+"取钱失败,余额不足:"+balance);
}
}
}
/**
* 存钱
*/
public synchronized void deposit(int monny) throws InterruptedException {
if (flag){
//有钱就等着
wait();
}else {
Log.e("testthread",Thread.currentThread().getName()+"存钱成功:"+monny);
balance +=monny;
Log.e("testthread","余额:"+balance);
flag = true;
// 唤醒等待的线程
notifyAll();
}
}
}
// 取钱的线程
public class DrawThread extends Thread {
// 取钱的账户
private Account account;
// 取钱的金额
private int monny;
DrawThread(String name,Account account,int monny){
super(name);
this.account = account;
this.monny = monny;
}
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
account.draw(monny);
}
}
}
//存钱的线程
public class DepositThread extends Thread {
private Account account;
private int monny;
DepositThread(String name,Account account,int monny){
super(name);
this.account = account;
this.monny = monny;
}
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
account.deposit(monny);
}
}
}
mBntFun5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Account account = new Account("admin",0);
new DrawThread("取钱A",account,800).start();
new DepositThread("存钱A",account,800).start();
new DepositThread("存钱B",account,800).start();
new DepositThread("存钱C",account,800).start();
}
});
二、使用Condition控制线程通信
- 当使用Lock对象来保证同步的时候,可以使用Condition来保持协调,让那些持有Lock却无法继续执行的线程,释放Lock对象;也可以唤醒其他处于等待的线程。
- 此时,Lock代替了同步方法或同步代码块的作用,Condition代替了同步监视器的作用
- Condition被绑在一个Lock对象上
- 要获得特点Lock对象的Condition,就要调用Lock.newCondition()
- Condition提供了三个方法:
- await():导致线程等到,知道Condition的signal、signalAll来唤醒
- signal:唤醒此Lock对象上等待的单个线程,如果所有线程都在等待,则随机唤醒一个,只有当前线程放弃对Lock对象的锁定(调用await())后,才可以执行被唤醒的线程
- signalAll:唤醒此Lock对象上等待的所有线程,只有当前线程放弃对Lock对象的锁定(调用await())后,才可以执行被唤醒的线程
public class ConditionAccount {
private String account;
private int balance;
// 是否有钱
private boolean flag;
// 显示定义一个LOCK对象
private ReentrantLock lock = new ReentrantLock();
// 定义特定Lock对象的Condition对象
Condition condition = lock.newCondition();
/**
* 取钱
* @param monny
*/
public void draw(int monny) throws InterruptedException {
lock.lock();
try {
if (!flag){
//没有钱,就等着
condition.await();
}else {
if (balance >= monny){
Log.e("testthread",Thread.currentThread().getName()+"取钱成功:"+monny);
balance -=monny;
Log.e("testthread","余额:"+balance);
flag = false;
// 唤醒其他线程
condition.signalAll();
}
}
}finally {
lock.unlock();
}
}
/**
* 存钱
* @param monny
*/
public void deposit(int monny) throws InterruptedException {
lock.lock();
try {
if (flag){
//有钱,就等着
condition.await();
}else {
Log.e("testthread",Thread.currentThread().getName()+"存钱成功:"+monny);
balance +=monny;
Log.e("testthread","余额:"+balance);
flag = true;
// 唤醒其他线程
condition.signalAll();
}
}finally {
lock.unlock();
}
}
}
// 存钱的线程
public class DepositThread extends Thread {
private ConditionAccount account;
private int monny;
DepositThread(String name,ConditionAccount account,int monny){
super(name);
this.account = account;
this.monny = monny;
}
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
try {
account.deposit(monny);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 取钱的类
public class DrawThread extends Thread {
// 取钱的账户
private ConditionAccount account;
// 取钱的金额
private int monny;
DrawThread(String name,ConditionAccount account,int monny){
super(name);
this.account = account;
this.monny = monny;
}
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
try {
account.draw(monny);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
mBntFun5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ConditionAccount account = new ConditionAccount("admin",0);
new DrawThread("取钱A",account,800).start();
new DepositThread("存钱A",account,800).start();
new DepositThread("存钱B",account,800).start();
new DepositThread("存钱C",account,800).start();
}
});
三、使用阻塞队列(BlockingQueue)控制线程通信
- BlockingQueue这个接口是Queue的子接口,但是它并不是作为容器,而是用于线程同步的工具
- 特征:
- 当生产者线程试图向BlockingQueue中插入元素,但是该队列已经满了,则会阻塞
- 当消费者线程试图向BlockingQueue中取出元素,但是该队列已经空了,则会阻塞
方法:
抛出异常 | 不同返回值 | 阻塞线程 | 指定超时时长 | |
---|---|---|---|---|
队尾插入元素 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
队头删除元素 | remove() | poll() | take() | poll(time,unit) |
获取,不删除元素 | element() | pick() | 无 | 无 |
实现类:
-
DelayQueue< E extends Delayed>:
—>基于 PriorityBlockingQueue 实现的,要求集合元素都实现Delay接口 -
LinkedBlockingQueue< E>:
–>基于链表实现的BlockingQueue队列 -
ArrayBlockingQueue< E>
-
Synchronous< E>
–>同步队列,对该队列的存取操作必须交替进行 -
PriorityBlockingQueue< E>
–>并不是标准的阻塞队列,用remove()、take()、poll()取出元素时,并不是取出队列中存在时间最长的元素,而是队列中最小的元素
mBntFun5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String> (2);
// 在没满时,这里存入元素,用add()、offer()、Put()都可以
queue.add("android");
queue.add("android");
// 会报错:java.lang.IllegalStateException: Queue full
//queue.add("android");
//会返回false
//queue.offer("android");
// 会阻塞线程:程序无响应
try {
queue.put("android");
} catch (InterruptedException e) {
e.printStackTrace();
Log.e("testqueue","阻塞:"+e.getMessage());
}
}
});
// 生产者
public class Produce extends Thread{
private BlockingDeque<String> queue;
Produce(BlockingDeque<String> queue){
this.queue = queue;
}
String[] array = new String[]{
"AA","BB","CC"
};
@Override
public void run() {
super.run();
for (int i=0;i<1000;i++){
Log.e("testthread",getName()+"准备生产");
try {
Thread.sleep(200);
// 存入元素
queue.put(array[i%3]);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("testthread",getName()+" 生产完成"+queue);
}
}
}
// 消费者
public class Consume extends Thread{
private BlockingDeque<String> queue;
Consume(BlockingDeque<String> queue){
this.queue = queue;
}
@Override
public void run() {
super.run();
for (int i=0;i<1000;i++){
Log.e("testthread",getName()+" 准备消费");
try {
Thread.sleep(200);
// 存入元素
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("testthread",getName()+" 消费完成"+queue);
}
}
}
mBntFun5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BlockingDeque<String> queue = new LinkedBlockingDeque<>(1);
//启动三个生产现场
new Produce(queue).start();
new Produce(queue).start();
new Produce(queue).start();
//启动一个消费线程
new Consume(queue).start();
}
});
### 四、线程组和未处理的异常
- ThreadGroup:线程组,可以对一批线程进行分类管理
- 用户创建的所有线程都属于线程组,如果没有显示指定线程组,则属于默认的线程组,默认情况下和父线程在同一个线程组
- 一旦线程加入某线程组,知道她死亡,都会一直属于这个线程组,运行过程组不能更改线程组
设置线程属于哪一个线程组
- public Thread(ThreadGroup group, Runnable target)
- public Thread(ThreadGroup group, String name)
- public Thread(ThreadGroup group, Runnable target, String name)
ThreadGroup的构造方法:
- public ThreadGroup(String name)
- public ThreadGroup(ThreadGroup parent, String name)
ThreadGroup提供的方法:
- String getName() 获取线程组的名字
- boolean isDaemon():是否是后台线程
- void setDaemon(boolean daemon):设置为后台线程
- void interrupt():中断此线程组中的所有线程
- int activeCount():返回活动线程的数目
- void setMaxPriority(int pri):设置最高优先级
mBntFun5.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ThreadGroup maingroup = Thread.currentThread().getThreadGroup();
//主线程组:main
Log.e("testqueue","主线程组:"+maingroup.getName());
//创建一个新的线程组
ThreadGroup newgroup = new ThreadGroup("新线程组");
newgroup.setDaemon(true);
Log.e("testqueue",maingroup.getName()+":"+newgroup.isDaemon());
}
});