一、线程通信
1、简介
确保线程能够按照预定的顺序执行并且能够安全地访问共享资源
使多条线程更好的进行协同工作
2、常用方法
这些方法来自Object类,需要使用锁对象进行调用
3、注意事项
(1)sleep方法和wait方法的区别
sleep是线程休眠,时间到了自动醒来,休眠时不会释放锁
wait是线程等待,需要其他线程进行唤醒,等待时会释放锁
(2)所有醒着的线程都有概率抢到CPU
(3)线程被唤醒之后(若抢到CPU),从之前进入等待的地方继续往下执行
4、等待唤醒机制
使用 ReentrantLock 实现同步,并获取 Condition 对象,使用 Condition 对象调用以下方法
public class AwaitDemo {
public static void main(String[] args) {
Printer2 p = new Printer2();
new Thread(new Runnable() {
public void run() {
while (true) {
try {
p.print1();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
new Thread(new Runnable() {
public void run() {
while (true) {
try {
p.print2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
new Thread(new Runnable() {
public void run() {
while (true) {
try {
p.print3();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
}
}
class Printer2 {
int flag = 1;
ReentrantLock myLock = new ReentrantLock();
Condition c1 = myLock.newCondition();
Condition c2 = myLock.newCondition();
Condition c3 = myLock.newCondition();
public void print1() throws InterruptedException {
myLock.lock();
if (flag != 1) {
c1.await();
}
System.out.print(1);
System.out.println(1);
flag = 2;
c2.signal();
myLock.unlock();
}
public void print2() throws InterruptedException {
myLock.lock();
if (flag != 2) {
c2.await();
}
System.out.print(2);
System.out.println(2);
flag = 3;
c3.signal();
myLock.unlock();
}
public void print3() throws InterruptedException {
myLock.lock();
if (flag != 3) {
c3.await();
}
System.out.print(3);
System.out.println(3);
flag = 1;
c1.signal();
myLock.unlock();
}
}
循环输出:
11
22
33
Tips:对于一个Condition对象,哪个线程最先使用该对象调用await方法,该对象就绑定到该线程
如果使用一个未绑定线程的Condition对象调用signal方法,将会随机唤醒一个线程
5、生产者消费者模式
生产者消费者模式是一个十分经典的多线程协作的模式
包含了两类线程:
生产者线程,用于生产数据
消费者线程,用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域 (缓冲区),就像是一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
public class ProducerAndConsumer {
public static void main(String[] args) {
new Thread(new Producer()).start();
new Thread(new Consumer()).start();
}
}
class SharedData {
public static boolean flag = false;
public static ReentrantLock lock = new ReentrantLock();
public static Condition producer = lock.newCondition();
public static Condition consumer = lock.newCondition();
}
class Producer implements Runnable {
@Override
public void run() {
while (true) {
SharedData.lock.lock();
if (!SharedData.flag) {
System.out.println("produce");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
SharedData.flag = true;
SharedData.consumer.signal();
} else {
try {
SharedData.producer.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
SharedData.lock.unlock();
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
while (true) {
SharedData.lock.lock();
if (SharedData.flag) {
System.out.println("consume");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
SharedData.flag = false;
SharedData.producer.signal();
} else {
try {
SharedData.consumer.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
SharedData.lock.unlock();
}
}
}
二、线程生命周期
线程被创建并启动以后,它并不是一启动就进入了执行状态,也不是一直处于执行状态。
线程对象在不同的时期有不同的状态
三、线程池
1、简介
系统创建一个线程的成本是比较高的,因为它涉及到与操作系统交互
当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程,就会严重浪费系统资源
将线程对象交给线程池维护
可以降低系统成本,提升程序的性能
实际开发中,线程资源必须通过线程池提供,不允许在线程中自行显示创建线程
2、JDK 提供的线程池(实际开发中不使用)
Executors 中提供静态方法来创建线程池
3、自定义线程池
(1)ThreadPoolExecutor 类的构造方法:七个参数
ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数 = 核心线程数 + 最大临时线程数
long keepAliveTime, // 等待时间,超过该时间就删除临时线程
TimeUnit unit, // 等待时间的单位
BlockingQueue<Runnable> workQueue, // 任务队列(要指定最大任务数)
ThreadFactory threadFactory, // 线程对象任务工厂(用于创建临时对象)
RejectedExecutorHandler handler // 拒绝策略
)
(2)拒绝策略
(3)注意事项
临时线程什么时候创建?
线程任务数 > 核心线程数 + 任务队列的数量
什么时候会开启拒绝策略?
线程任务数 > 最大线程数 + 任务队列的数量
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
for (int i = 0; i < 16; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " submitted");
}
});
}
}
}
四、单例设计模式
单例指单个实例,保证类的对象在内存中只有一份
使用场景:
如果创建一个对象需要消耗的资源过多,比如 I/O 与数据库的连接
并且这个对象完全是可以复用的, 我们就可以考虑将其设计为单例的对象
class Single1 {
private Single1() {}
private static Single1 s = new Single1();
public static Single1 getInstance() {
return s;
}
}
class Single2 { // 延迟加载模式
private Single2() {}
private static Single2 s;
public static Single2 getInstance() {
if (s == null) {
synchronized (Single2.class) {
if (s == null) {
s = new Single2();
}
}
}
return s;
}
}