(一)传统线程通信
(有同步监视器,synchronozed代码块或方法均可)
wait(); 阻塞,不同于死锁
notify(); 随机唤醒同步监视器上的一个线程
notifyAll(); 唤醒同步监视器上的所有线程
说明:可以先唤醒其他线程,但是不会执行,当前线程wait()后才会执行
可以先wait再notify。如先wait即被阻塞,notify不会执行
存取交互
public synchronized void draw(double drawAmount)
{
// 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
if (!flag)
{
wait();
}
else
{
// 执行取钱
System.out.println(Thread.currentThread().getName()
+ " 取钱:" + drawAmount);
balance -= drawAmount;
System.out.println("账户余额为:" + balance);
// 将标识账户是否已有存款的旗标设为false。
flag = false; //先锁定,即wait()
// 唤醒其他线程
notifyAll(); //再唤醒
}
}
public synchronized void deposit(double depositAmount)
{
// 如果flag为真,表明账户中已有人存钱进去,则存钱方法阻塞
if (flag) //①
{
wait();
}
else
{
// 执行存款
System.out.println(Thread.currentThread().getName()
+ " 存款:" + depositAmount);
balance += depositAmount;
System.out.println("账户余额为:" + balance);
// 将表示账户是否已有存款的旗标设为true
flag = true;
// 唤醒其他线程
notifyAll();
}
}
(二)Condition控制线程通信
用Look来同步线程时
private final Lock lock = new ReentrantLock();// 显式定义Lock对象
// 获得指定Lock对象对应的Condition。newCondition()方法
private final Condition cond = lock.newCondition();
public void draw(double drawAmount)
{
// 加锁
lock.lock();
try
{
// 如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
if (!flag)
{
cond.await();
}
else
{
// 执行取钱
System.out.println(Thread.currentThread().getName()
+ " 取钱:" + drawAmount);
balance -= drawAmount;
System.out.println("账户余额为:" + balance);
// 将标识账户是否已有存款的旗标设为false。
flag = false;
// 唤醒其他线程
cond.signalAll();
}
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
// 使用finally块来释放锁
finally
{
lock.unlock();
}
}
(三)阻塞队列(BlockingQueue)
BlockingQueue 实现是线程安全的,主要作为线程同步的工具。实现主要用于生产者-使用者队列。
BlockingQueue定义的常用方法如下:
Tables | 抛出异常 | 不同返回值 | 阻塞线程 | 指定超时时长 |
---|---|---|---|---|
队尾插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
头部移除 | remove() | poll() | take() | poll(time, unit) |
检查 | element() | peek() | 无 | 无 |
BlockingQueue
也是java.util.concurrent
下的主要用来控制线程同步的工具。
BlockingQueue
有四个具体的实现类,根据不同需求,选择不同的实现类
ArrayBlockingQueue
:一个由数组支持的有界阻塞队列,规定大小的BlockingQueue
,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的。LinkedBlockingQueue
:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的。PriorityBlockingQueue
:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator
决定的顺序。SynchronousQueue
:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。LinkedBlockingQueue
可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE
,其中主要用到put
和take
方法,put
方法在队列满的时候会阻塞直到有队列成员被消费,take
方法在队列空的时候会阻塞,直到有队列成员被放进来。
定义一个大小为2的BlockingQueue,当试图放第三个元素时会阻塞线程。
BlockingQueue<String> bq = new ArrayBlockingQueue<String>(2);
bq.put("java");
bq.put("python");
bq.put("js");//阻塞线程
//为空时take()也会阻塞
生产者消费者模式:
import java.util.concurrent.*;
class Producer extends Thread
{
private BlockingQueue<String> bq;
public Producer(BlockingQueue<String> bq)
{
this.bq = bq;
}
public void run()
{
String[] strArr = new String[]
{
"Java",
"Struts",
"Spring"
};
for (int i = 0 ; i < 999999999 ; i++ )
{
System.out.println(getName() + "生产者准备生产集合元素!");
try
{
Thread.sleep(200);
// 尝试放入元素,如果队列已满,线程被阻塞
bq.put(strArr[i % 3]);
}
catch (Exception ex){ex.printStackTrace();}
System.out.println(getName() + "生产完成:" + bq);
}
}
}
class Consumer extends Thread
{
private BlockingQueue<String> bq;
public Consumer(BlockingQueue<String> bq)
{
this.bq = bq;
}
public void run()
{
while(true)
{
System.out.println(getName() + "消费者准备消费集合元素!");
try
{
Thread.sleep(200);
// 尝试取出元素,如果队列已空,线程被阻塞
bq.take();
}
catch (Exception ex){ex.printStackTrace();}
System.out.println(getName() + "消费完成:" + bq);
}
}
}
public class BlockingQueueTest2
{
public static void main(String[] args)
{
// 创建一个容量为1的BlockingQueue
BlockingQueue<String> bq = new ArrayBlockingQueue<>(1);
// 启动3条生产者线程
new Producer(bq).start();
new Producer(bq).start();
new Producer(bq).start();
// 启动一条消费者线程
new Consumer(bq).start();
}
}
由于容量为1,虽然有3个生产线程1个消费线程,每次只有1个生产者生产,然后消费。