多线程特别重要,虽然内容偏多,但是需要熟练掌握。面试也会在此章节有考验的!请大家耐心学习!
目录
一、什么是线程
二、线程的组成(创建线程方式)
三、线程的状态(方法实例详解)
四、线程安全(实例详解)
五、多线程之消费者与生产者、死锁
六、线程通信(实例详解)
七、线程池
八、线程安全的集合
思维导图参考: 【十三】Java多线程思维导图
一、什么是线程
什么是进程
什么是线程
- 线程,又称轻量级进程(Light Weight Process)
- 程序中的一个顺序控制流程,同时也是CPU的基本调度单位
- 比如:
- 迅雷是一个进程,当中的多个下载任务就是多个线程
- Java虚拟机是一个进程,当中默认包含主线程(Main函数),可通过代码创建多个独立线程,与Main并发执行
二、线程的组成
- 任何一个线程都具有基本的组成部分:
- CPU时间片: 操作系统(OS)会为每个线程分配时间
- 运行数据:
- 堆空间: 存储线程需使用的对象,多个线程可以共享堆中的对象
- 栈空间: 存储线程需使用的局部变量,每个线程都拥有独立的栈
- 线程的逻辑代码
创建线程的第一种方式
创建线程的第二种方式
三、线程的状态
线程的基本状态
常见方法(sleep、yield、join)
- 休眠:
- public static void sleep(long millis)
- 当前线程主动休眠millis毫秒(注意:1000毫秒=1秒)
//public static void sleep
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.start();
MyRunnable task = new MyRunnable();
Thread t2 = new Thread(task);
t2.start();
// main线程
for (int i = 1; i <= 20; i++) {// 线程任务
System.out.println(Thread.currentThread().getName() + " - " + i);
if (i == 10) {// 指定一下休眠条件
// 通知完t1后,main线程休眠2秒
Thread.sleep(2000);// 有限期等待。等待时间由参数的毫秒值决定
}
}
}
}
class MyThread extends Thread {// 线程类
public void run() {
for (int i = 1; i <= 20; i++) {// 线程任务
// 获得当前线程的线程名称
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
class MyRunnable implements Runnable {
public void run() {
for (int i = 1; i <= 20; i++) {// 线程任务
// 获取当前线程的线程名称
if (i % 2 == 0) {
System.out.println("线程2得到了偶数!休眠了!");
}
try {
Thread.sleep(1000);// 如果写在run方法里,那么只能通过tryCatch处理异常。遵循异常方法的覆盖原则
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
- 放弃:
- public static void yield()
- 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
//public static void yield()
public class TestYield {
public static void main(String[] args) {
Thread t1 = new Thread(new Task());
t1.start();
//main线程
for (int i = 1; i <= 20; i++) {//线程任务
System.out.println(Thread.currentThread().getName() + " - " + i);
if (i % 10 == 0) {//放弃的条件(特定时候放弃时间片)
System.out.println("main主动放弃了!");
Thread.yield();//放弃!主动放弃当前持有的时间片,回到就绪状态,进入下一次时间片的竞争!
}
}
}
}
class Task implements Runnable {//线程类
public void run() {//线程任务
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
- 结合:
- public final void join()
- 允许其他线程加入到当前线程中,等待加入的线程执行完毕后,此线程再去竞争时间片执行
//public final void join()
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Thread t0 = new Thread(new Task());
t0.start();
for (int i = 1; i <= 10; i++) {//打印10个数字
System.out.println(Thread.currentThread().getName() + " - " + i);//打印线程名字和数字
if (i == 5) {//将t0加入到main线程执行流程中,等待t1线程执行结束后!main再进行竞争时间片!
t0.join();//无限期等待!等待条件为调用join方法的线程执行完毕后!再进入就绪状态,竞争时间片!
}
}
}
}
class Task implements Runnable {//线程实现类——Thread-0
public void run() {//线程任务
for (int i = 1; i <= 10; i++) {//打印10个数字
System.out.println(Thread.currentThread().getName() + " - " + i);//打印线程名字和数字
}
}
}
线程的状态(等待)
四、线程安全
线程安全问题
解决线程安全问题的方法
同步方式(1):同步代码块
synchronized(临界资源对象) {//对临界资源对象加锁
//代码(原子操作)
}
- 注意:
- 每个对象都有一个互斥锁标记,用来分给线程的
- 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
- 线程退出同步代码块时,会释放相应的互斥锁标记
线程的状态(阻塞)
同步方式(2):同步代码块
synchronized 返回值类型 方法名称(形参列表0) {//对当前对象(this)加锁
//代码(原子操作)
}
- 注意:
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记
- 如需用不包含同步代码快的方法,或普通方法时,则不需要锁标记,可直接调用
- 已知JDK中线程安全的类:
- StringBuffer
- Vector
- Hashtable
- 以上类中的公开方法,均为synchonized修饰的同步方法
synchronized实例
//什么场景下加锁?什么场景下不加锁?
//写(增、删、改) 操作---> 加锁!
//读操作 不加锁
//------------------------------------------------------------------------------
//线程安全问题:没有加锁即线程不安全时,两个人同时取钱会出现余额不足也能取出钱的问题
//
//实例问题:妻子、丈夫携主、副卡在同一个银行账户中取钱(两个线程访问临界资源对象)
//
//解决方法:
//1.给所有取款(原子操作)加锁
//2.给原子操作所在的方法加锁
//3.给原子操作的对象加锁
public class TestSynchronized {
public static void main(String[] args) {
// 临界资源,被共享的对象
// 临界资源对象只有一把锁!
Account acc = new Account("6002", "1234", 2000);
// 两个线程对象 共享同一银行卡资源对象
// 给定任务后,第二个参数是对线程自定义命名
Thread husband = new Thread(new Husband(acc), "丈夫");
Thread wife = new Thread(new Wife(acc), "妻子");
husband.start();
wife.start();
}
}
class Husband implements Runnable {
Account acc;
public Husband(Account acc) {
this.acc = acc;
}
// 线程任务:取款!
public void run() {
// synchronized(acc) {//对临界资源对象加锁
this.acc.withdrawal("6002", "1234", 1200);// 原子操作!
// }
}
}
class Wife implements Runnable {
Account acc;
public Wife(Account acc) {
this.acc = acc;
}
// 线程任务:取款!
public void run() {
// synchronized(acc) {//如果丈夫先拿到了锁,进行原子操作!那么妻子会等!死等!
this.acc.withdrawal("6002", "1234", 1200);
// }
}
}
// 银行账户 银行卡
// this === 当前类的实例对象
class Account {
String cardNo;// 卡号
String password;// 密码
double balance;// 余额
public Account(String cardNo, String password, double balance) {
super();
this.cardNo = cardNo;
this.password = password;
this.balance = balance;
}
// 取款(原子操作,从插卡验证,到取款成功的一系列步骤,不可缺少或打乱)
// public void synchronized withdrawal(String no, String pwd, double money) { //为方法加锁
public void withdrawal(String no, String pwd, double money) {
// 等待! --- >阻塞状态
synchronized (this) {// 对当前共享实例加锁
System.out.println(Thread.currentThread().getName() + "正在读卡。。。");
// (原子操作,从插卡验证,到取款成功的一系列步骤,不可缺少或打乱)
if (no.equals(this.cardNo) && pwd.equals(this.password)) {
System.out.println(Thread.currentThread().getName() + "验证成功。。。");
if (money <= this.balance) {
try {
Thread.sleep(1000);// 模拟现实世界,ATM机在数钱
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = this.balance - money;
System.out.println(Thread.currentThread().getName() + "取款成功!当前余额为:" + this.balance);
} else {
System.out.println(Thread.currentThread().getName() + "当前卡内余额不足!");
}
} else {
System.out.println(Thread.currentThread().getName() + "卡号或密码错误!");
}
}
}
}
五、经典问题
- 死锁:
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象标记时,产生死锁
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁
- 生产者、消费者:
- 若干个生产者在生产产品,这些产品将提供若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品
思路: 从生产者、消费者角度出发,模拟生产者、消费者、商品、超市四者之间的关系,而其中的共享资源对象即是超市
public class TestProductCustomer {
public static void main(String[] args) {
Shop shop = new Shop();// 共享资源对象
Thread p = new Thread(new Product(shop), "生产者");
Thread c = new Thread(new Customer(shop), "消费者");
p.start();
c.start();
}
}
// 商品
class Goods {
private int id;
public Goods(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
// 超市
class Shop {
Goods goods;
boolean flag;// 标识商品是否充足
// 生产者调用的 存的方法
public synchronized void saveGoods(Goods goods) throws InterruptedException {
// 1.判断商品是否充足
if (flag == true) {
System.out.println("生产者:商品充足,等待库存卖完...");
this.wait();// 商品充足,生产者不用生产,而等待消费者买完!进入等待状态
}
// 商品不充足!生产者生产商品,存到商场里
System.out.println(Thread.currentThread().getName() + "生产并在商场里存放了" + goods.getId() + "件商品");
this.goods = goods;
flag = true;// 已经有商品了!可以让消费者购买了!
// 消费者等待...
this.notifyAll();// 将等待队列的消费者唤醒!前来购买商品
}
// 消费者调用的 取的方法
public synchronized void buyGoods() throws InterruptedException {
if (flag == false) {// 没有商品了!消费者就要等待!
System.out.println("消费者:商品不充足了,等待商品上架...");
this.wait();// 消费者进入等待队列!等待生产者生产商品后,唤醒!
}
// 正常购买商品
System.out.println(Thread.currentThread().getName() + "购买了" + goods.getId() + "件商品");
// 商品买完了!标识没货了!
this.goods = null;
flag = false;
// 唤醒生产者去生产商品
this.notifyAll();
}
}
// 生产者
class Product implements Runnable {
Shop shop;// 商场
public Product(Shop shop) {
this.shop = shop;
}
public void run() {
// 通过循环,生产商品存放到Shop里
for (int i = 1; i <= 30; i++) {
try {
// 生产者线程调用存商品的方法。传一个商品对象
this.shop.saveGoods(new Goods(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者
class Customer implements Runnable {
Shop shop;// 商场
public Customer(Shop shop) {
this.shop = shop;
}
public void run() {// for循环模拟买商品
for (int i = 1; i <= 30; i++) {
try {
this.shop.buyGoods();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
六、线程通信
- 等待:
- public final void wait()
- public final void wait(long timeout)
- 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在obj的等待队列中。释放锁,进入等待队列
- 通知:
- public final void notify() //释放一个
- public final void notifyAll() //释放全部
- 必须在对obj加锁的同步代码块中。从obj的Waiting中释放一个或全部线程。对自身没有任何影响
//复杂:一个线程持有A对象的锁,需要B对象的锁, 另一个线程持有B、想要A
//简单:一个线程持有A对象的锁,另一个线程也想要!阻塞
//
//情况1:三个线程抢锁,其中一个线程或两个线程把所让给了其他线程(wait),
// 然后另外的线程就开始抢锁直至执行结束——死锁(没有notify唤醒)
//情况2:三个线程抢锁,其中一个线程或两个线程把所让给了其他线程(wait),
// 然后另外的线程就开始抢锁并其中的一个或者最后一个notify唤醒——有可能出现死锁(因为notify是随机唤醒)
//情况3:三个线程抢锁,其中一个线程或两个线程把所让给了其他线程(wait),
// 然后另外的线程就开始抢锁并其中的一个或者最后一个notifyAll唤醒——正常执行(因为notifyAll是唤醒全部等待的线程)
public class TestWaitNotify {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
MyThread t1 = new MyThread(obj);
MyThread2 t2 = new MyThread2(obj);
t1.start();
t2.start();
// 主线程通知完两个线程后,休眠。
Thread.sleep(2000);
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "进入到同步代码块");
// obj.wait();//主线程获得到了锁,也主动释放
// 此时此刻等待队列里有两个线程
// obj.notify();//在obj的等待队列中,随机唤醒一个拿锁执行代码
obj.notifyAll();// 将obj的等待队列所有的线程都唤醒。
System.out.println(Thread.currentThread().getName() + "退出了同步代码块");
}
}
}
class MyThread extends Thread {
Object obj;
public MyThread(Object obj) {
this.obj = obj;
}
public void run() {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "进入到同步代码块");
// Thread-0先拿到了锁,让给其他线程先拿锁!
try {
obj.wait();// 主动释放当前持有的锁!并进入无限期等待!
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "退出了同步代码块");
}
}
}
class MyThread2 extends Thread {
Object obj;
public MyThread2(Object obj) {
this.obj = obj;
}
public void run() {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "进入到同步代码块");
try {
obj.wait();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
// obj.notify();//在obj这个共享对象的等待队列中,唤醒一个正在等待拿锁的线程!
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "退出了同步代码块");
}
}
}
七、线程池
线程池概念
- 现有问题:
- 线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出
- 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降
- 线程池:
- 线程容器,可设定线程分配的数量上限
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象
- 避免频繁的创建和销毁
线程池原理
将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程
获取线程池
- 常用的线程池接口和类(所在包java.util.concurrent):
- Executor: 线程池的顶级接口
- ExecutorService: 线程池接口,可通过submit(Runnable task)提交任务代码
- Executors工厂类: 通过此类可以获得一个线程池
- 通过newFixedThreadPool(int nThreads) 获得固定数量的线程池。参数: 指定线程池中线程的数量
- 通过newCachedThreadPool() 获取动态数量的线程池,如不够创建新的,没有上限
Callable接口
接口的定义
public interface Callable<V> {
public V call() throws Exception;
}
- JDK5加入,与Runnable接口类似,实现之后代表一个线程任务
- Callable具有泛型返回值、可以声明异常
Callable接口的使用
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCallable {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3);
MyTask task = new MyTask();
es.submit(task);
}
}
class MyTask implements Callable<Integer> {
public Integer call() throws Exception {
for (int i = 0; i <= 20; i++) {
if (i == 30) {
Thread.sleep(1000);
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return null;
}
}
Future接口
- 概念: 异步接收ExecutorService.submit() 所返回的状态结果,当中包含了call() 的返回值
- 方法: V get() 以阻塞形式等待Future中的异步处理结果(call()的返回值)
线程的同步
- 同步:
- 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续
- 注意:单条执行路径
线程的异步
- 异步:
- 形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立即返回。二者竞争时间片,并发执行
- 注意:多条执行路径
Future接口的使用
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestFuture {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(2);
MyCall1 mc1 = new MyCall1();
MyCall2 mc2 = new MyCall2();
//通过submit执行提交的任务,Future接受返回的结果
Future<Integer> result1 = es.submit(mc1);
Future<Integer> result2 = es.submit(mc2);
//通过Future的get方法,获得线程执行完毕后的结果
Integer value1 = result1.get();
Integer value2 = result2.get();
System.out.println(value1 + value2);
}
}
//计算1~50的和
class MyCall1 implements Callable<Integer> {
public Integer call() throws Exception {
Integer sum = 0;
for (int i = 1; i <= 50; i++) {
sum += i;
}
return sum;
}
}
//计算1~50的和
class MyCall2 implements Callable<Integer> {
public Integer call() throws Exception {
Integer sum = 0;
for (int i = 1; i <= 50; i++) {
sum += i;
}
return sum;
}
}
newCachedThreadPool() 获取动态数量的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String[] args) {
// 线程池(引用) ---->Executors工具类(工厂类)
// ExecutorService es = Executors.newFixedThreadPool(3);//手动限定线程池里的线程数量
ExecutorService es = Executors.newCachedThreadPool();// 动态获取数量线程池
// 1.创建任务对象
MyTask1 task = new MyTask1();
// 2.将任务提交到线程池,由线程池调度、执行
es.submit(task);// Runnable类型的对象
es.submit(task);
es.submit(task);
}
}
// 线程任务
class MyTask1 implements Runnable {
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
Lock接口
- JDK5加入,与synchronized比较,显示定义,结构更灵活
- 提供更多使用性方法,功能更强大、性能更优越
- 常用方法:
- void lock() //获取锁,如锁被占用,则等待
- boolean tryLock() //尝试获取锁(成功返回true。失败返回false,不阻塞)
- void unlock() //释放锁
Lock接口的使用
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLocks {
public static void main(String[] args) {
Test obj = new Test();
Thread t1 = new Thread(new MyTask(obj));
Thread t2 = new Thread(new MyTask2(obj));
t1.start();
t2.start();
}
}
class Test {
// 接口引用 实现类对象 创建一把锁
Lock lock = new ReentrantLock();// Test里有一把锁的属性,就是Test自身的锁!
// ReentrantLock rl = new ReentrantLock();
// 第一、使用Lock,需要明确的写上锁和释放锁!
// 第二、为了避免拿到锁的线程在运行期间出现异常,导致程序终止,没有释放锁!应用try{}finally{}来保证,无论正确执行与否,最终都会释放锁!
public void method() {
System.out.println(Thread.currentThread().getName() + "进入到上锁的方法里!");
try {
lock.lock();// 显示的写上 在此处获得锁
// 模拟程序出错!
// int a = 10/0;
// method();不要出现无穷递归!容易内存溢出!导致锁一直没有释放!
System.out.println(Thread.currentThread().getName() + "退出了上锁的方法里!");
} finally {
// 显示的写上,此处释放锁
lock.unlock();
}
}
}
class MyTask implements Runnable {
Test obj;// 共享资源对象
public MyTask(Test obj) {
this.obj = obj;
}
public void run() {
obj.method();
}
}
class MyTask2 implements Runnable {
Test obj;// 共享资源对象
public MyTask2(Test obj) {
this.obj = obj;
}
public void run() {
obj.method();
}
}
重入锁
- ReenTrantLock: Lock接口的实现类,与synchronized一样具有互斥锁功能
读写锁
- ReentrantReadWriteLock:
- 一种支持一写多度的同步锁,读写分离,可分别分配读锁、写锁
- 互斥规则:
- 写-写: 互斥,阻塞
- 读-写: 互斥,读阻塞写、写阻塞读
- 读-读: 不互斥、不阻塞
- 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率
八、线程安全的集合
- Collection体系集合下,除Vector以外的线程安全集合
Collection中的工具方法
- Collection工具类中提供了多个可以获得线程安全集合的方法
- public static < T > Collection< T > synchronizedCollection(Collection< T > c)
- public static < T > List< T > synchronizedList(List< T > list)
- public static < T > Set< T > synchronizedSet(Set< T > set)
- public static < K,V > Map< K,V > synchronizedMap(Map< K,V > m)
- public static < T > SortedSet< T > synchronizedSet(SortedSet< T > s)
- public static < K,V > SortedMap< K,V > synchronizedSortedMap(SortedMap< K,V > m)
- JDK1.2提供,接口统一、维护性高,但性能没有提升,均以synchronized实现
- 与普通集合使用方式无异
CopyOnWriteArrayList
- 线程安全的ArrayList,加强版读写分离
- 写有锁,读无锁,读写之间不堵塞,优于读写锁
- 写入时,先copy一个容器副本、再添加新元素,最后替代引用
- 使用方式与ArrayList无异
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<String>();
}
}
CopyOnWriteArraySet
- 线程安全的Set,底层使用CopyOnWriteArrayList实现
- 唯一不同在于,使用addIfAbsent() 添加元素,会遍历数组
- 如存在元素,则不添加(扔掉副本)
public class TestCopyOnWriteArraySet {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<String>();
}
}
ConcurrentHashMap
- 初始容量默认为16段(Segment),使用分段锁设计
- 不对整个Map加锁,而是为每个Segment加锁
- 当多个对象存入同一个Segment时,才需要互斥
- 最理想状态为16个对象分别存入16个Segment,并行数量16
- 使用方式与HashMap无异
- 注意:JDK1.7版本源码解释
Queue接口(队列)
- Collection的子接口,表示队列FIFO(First In First Out),意为先进先出
- 常用方法:
- 抛出异常:
- boolean add(E e) //顺序添加一个元素(到达上限后,再添加则会抛出异常
- E remove() //获得第一个元素并移除(如果队列没有元素时,则会抛出异常)
- E element() //获得第一个元素但不移除(如果队列没有元素时,则会抛出异常)
- 返回特殊值: 推荐使用
- boolean offer(E e) //顺序添加一个元素(到达上限后则会返回false)
- E poll() //获得第一个元素并移除(如果队列没有元素时,则返回null)
- E keep() //获得第一个元素但不移除(如果队列没有元素时,则返回null)
- 抛出异常:
ConcurrentLinkedQueue
- 线程安全、可高效读写的队列,高并发下性能最好的队列
- 无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
- V: 要更新的变量
- E: 预期值
- N: 新值
- 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作
public class TestConcurrentLinkedQueue {
public static void main(String[] args) {
Queue<String> queue = new ConcurrentLinkedQueue<String>();
queue.offer("Hello");//插入
queue.offer("World");//插入
queue.poll();//删除Hello
queue.peek();//删除World
}
}
BlockingQueue接口(阻塞)
- Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法
- 方法:
- void put(E e) //将指定元素插入此队列中,如果没有可用空间,则等待
- E take() //获取并移除此队列头部元素,如果没有可用元素,则等待
- 可用于解决生产者、消费者问题
阻塞队列
- ArrayBlockingQueue:
- 数组结构实现,有界队列(手工固定上限)
public class TestArrayBlockingQueue {
public static void main(String[] args) {
BlockingQueue<String> abq = new ArrayBlockingQueue<String>(10);
}
}
- LinkedBlockingQueue:
- 链表结构实现,无界队列(默认上限Integer.MAX_VALUE)
public class TestArrayBlockingQueue {
public static void main(String[] args) {
BlockingQueue<String> abq = new LinkedBlockingQueue<String>();
}
}
队列接口的使用
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class TestQueue {
public static void main(String[] args) {
// 列表,尾部添加(指定下标)
// 链表,头尾添加
// 队列,FIFO
// 如果要强制LinkedList只遵循队列的规则!
// Queue<String> link = new LinkedList<String>(); //遵循队列规则的链表
LinkedList<String> link = new LinkedList<String>();
link.offer("A");
link.offer("B");
link.offer("C");
// 用列表的方式打乱了FIFO队列的规则
// link.add(0,"D");
// 强制LinkedList后,不能调用带有下标的add方法
System.out.println(link.peek());// 队列中的第一个元素!
// 严格遵守了队列的规则,且是线程安全的,采用了CAS交换算法
Queue<String> q = new ConcurrentLinkedQueue<String>();
// 1.抛出异常的 2.返回结果的
q.offer("A");
q.offer("B");
q.offer("C");
q.poll();// 删除表头
System.out.println(q.peek());// 获得表头
// 手动固定队列上限
BlockingQueue<String> bq = new ArrayBlockingQueue<String>(3);
// 无界队列 最大有 Integer.MAX_VALUE
BlockingQueue<String> lbq = new LinkedBlockingQueue<String>();
}
}
集合接口总结
- ExecutorService线程接口、Executors工厂
- Callable线程任务、Future异步返回值
- Lock、ReenTrantLock重入锁、ReentrantReadWriteLock读写锁
- CopyOnWriteArrayList线程安全的ArrayList集合
- CopyOnWriteArraySet线程安全的Set集合
- ConcurrentHashMap线程安全的HashMap集合
- ConcurrentLinkedQueue线程安全的Queue队列
- ArrayBlockingQueue线程安全的Queue队列(生产者、消费者)