一、线程通信
线程通信介绍:
确保线程能够按照预定的顺序执行,并且能够安全地访问共享资源,使多条线程更好的进行协同工作。
等待唤醒机制:
成员方法 | 说明 |
void wait() | 使当前线程等待 |
void notify() | 随机唤醒单个等待的线程 |
void notifyAll() | 唤醒所有 等待的线程 |
注意:这些方法需要使用锁对象调用
面试题:sleep方法和wait的别
答案:sleep方法是线程休眠,时间到了自动醒来;方法在休眠的时候,不会释放锁。
wait方法是线程等待,需要由其他线程进行notify唤醒;在等待期间,会释放锁。
确保两条线程按照各执行一次的的顺序进行案例代码
class ThreadText{
int f = 1;//用作标识
public void threadFun1() throws InterruptedException {
if(f != 1){//ruguo不是1该线程等待
ThreadText.class.wait();
}
System.out.println("我是Fun1");
f = 2;
ThreadText.class.notify();//重启线程
}
public void threadFun2() throws InterruptedException {
if(f != 2){//ruguo不是2该线程等待
ThreadText.class.wait();
}
System.out.println("我是Fun2");
f = 1;
ThreadText.class.notify();//重启线程
}
}
public static void main(String[] args) {
ThreadText t = new ThreadText();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (ThreadText.class){
try {
t.threadFun1();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (ThreadText.class){
try {
t.threadFun2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}).start();
}
案例总结:这个方式再加一条线程就会出现打印数据丢失的问题,是因为notify()是随机唤醒而且是在哪休眠就会在哪唤醒,唤醒后继续向下执行代码不会再进行if的判断,这就导致了每次切换的不确定性。可以通过 notifyAll()全部唤醒,再将if换成while进行判断,但是效率极低。
等待唤醒机制:
使用ReentrantLock 实现同步,并获取Condition对象
成员方法 | 说明 |
void await() | 指定线程等待 |
void signal() | 指定唤醒单个等待的线程 |
注意:在线程第一次调用await()方法的时候,会绑定condition对象
class ThreadText{
ReentrantLock lock = new ReentrantLock();
Condition f1 = lock.newCondition();
Condition f2 = lock.newCondition();
Condition f3 = lock.newCondition();
int f = 1;//用作标识
public void threadFun1() throws InterruptedException {
lock.lock();
if(f != 1){//不等于1第一条线程等待
f1.await();
}
System.out.println("我是Fun1");
f = 2;
f2.signal();
lock.unlock();
}
public void threadFun2() throws InterruptedException {
lock.lock();
if(f != 2){//不等于2第二条线程等待
f2.await();
}
System.out.println("我是Fun2");
f = 3;
f3.signal();
lock.unlock();
}
public void threadFun3() throws InterruptedException {
lock.lock();
if(f != 3){//不等于3第三条线程等待
f3.await();
}
System.out.println("我是Fun3");
f = 1;
f1.signal();
lock.unlock();
}
}
public static void main(String[] args) {
ThreadText t = new ThreadText();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
t.threadFun1();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
t.threadFun2();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
t.threadFun3();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
}
生产者消费者模式:
生产者消费者模式是一个十分经典的多线程协作模式
主要包含了两类线程:生产者线程,用于生产数据
消费者线程,用于消费数据
Warehouse.java
public class Warehouse {
public static boolean mark = false;
public static ReentrantLock lock = new ReentrantLock();
public static Condition con = lock.newCondition();
public static Condition pro = lock.newCondition();
}
Producer.java
public class Producer implements Runnable {
@Override
public void run() {
while (true){
Warehouse.lock.lock();
if (Warehouse.mark){
//true 有产品,等待
try {
Warehouse.pro.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else {
//false 没有产品,生产
System.out.println("生产者:开始生产产品了");
Warehouse.mark = true;
Warehouse.con.signal();
}
Warehouse.lock.unlock();
}
}
}
Consumer.java
public class Consumer implements Runnable {
@Override
public void run() {
Warehouse.lock.lock();
if (Warehouse.mark){
System.out.println("222222222222");
//true 有产品 消费
System.out.println("消费者:开始消费了");
Warehouse.mark = false;
Warehouse.pro.signal();
}else {
//false 没有产品 等待
try {
Warehouse.con.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Warehouse.lock.unlock();
}
}
main.java
public class Main {
public static void main(String[] args) {
new Thread(new Producer()).start();
new Thread(new Consumer()).start();
}
}
为了解耦生产者和消费者的关系,通常会采用共享的数据区域(缓冲区),就像是一一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
二、线程生命周期
线程的启动后的各种状态
线程被创建并启动以后,它并不是一启动就进入智行状态的,也不是一直处于执行状态。
线程对象在不同的时期有不同的状态。
状态 | 具体含义 |
NEW( 新建 ) | 创建线程对象 |
RUNNABLE( 就绪 ) | start 方法开始调用,但是还没有抢到CPU执行权 |
BLOCKED( 阻塞 ) | 线程开始运行,但是没有获取到锁对象 |
WAITING( 等待 ) | wait 方法 |
TIMED_WAITING( 计时等待 ) | sleep 方法 |
TERMINATED( 结束状态 ) | 代码全部运行完毕 |
三、线程池
介绍:
系统创建一个线程的成本比较高,因为它涉及到与操作系统交互
当程序中需要创建大量生存期很短的线程时,频繁的创建和销毁线程,就会严重浪费系统资源
将线程对象交给线程池维护,可以降低系统成本,从而提高程序的性能
强制:
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
注:线程池的好处是减少在创建和销毁线程上所消耗的时间及系统资源的开销,解决资源不足的问题。如果不实用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
学习路径:
理解线程池的好处——JDK提供的线程池 —— 自定义线程池(自左向右顺序)
强制:
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式更加明确线程池运行的规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长队为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:
允许创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
JDK提供的线程池
1.Executors中提供静态方法来创建线程池
方法 | 介绍 |
static ExecutorService newCachedThreadPool() | 创建一个默认线程池 |
static newFixedThreadPool(int nThreads) | 创建一个指定最多线程数量的线程池 |
public static void main(String[] args) {
/*JDK自带线程池
Executors 中提供静态方法来创建线程池
static ExecutorService newCachedThreadPool 创建一个默认的线程池
不建议使用的原因是这个线程池最多Integer.MAX_VALUE个线程,过大
*/
ExecutorService ex = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "这是JDK提供的线程池");
}
});
}
ex.shutdown();//停止线程池
}
/*JDK自带线程池
Executors 中提供静态方法来创建线程池
static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池,这个相对好点可以指定线程个数,但是点进去会发现源码中还有一些其他的配置,不太贴合我们的需要,所以也不建议使用
*/
ExecutorService ex = Executors.newFixedThreadPool(3);
for (int i = 0; i < 100; i++) {
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "这是JDK提供的线程池");
}
});
}
ex.shutdown();//停止线程池
2.自定义线程池
ThreadPoolExecutor(
int corePoolSize,//核心线程数量(留存的线程数量)
int maximumPoolSize,//最大线程数量(核心线程 + 临时线程)
long keepAliveTime,//空闲时间(多久会销毁临时线程)
TimeUnit unit ,//空闲时间单位
BLockingQueue<Runnable> workQueue,//任务队列(指定排队任务数量)
ThreadFactory threadFactory,//线程对象任务工厂
RejectedExecutionHandLer handler//拒绝策略
)
public static void main(String[] args) {
//bascText();//基础语法训练
//proConText();//生产销售线程
//jdkPoolText();
/*
参数5:1.有限队列 new ArrayBlockingQueue<>(10)
2.无限队列 new LinkedBlockingQueue<>()
*/
ThreadPoolExecutor tpe = new ThreadPoolExecutor(
2,//核心线程数量(留存的线程数量)
5,//最大线程数量(核心线程 + 临时线程)
60,//空闲时间
TimeUnit.SECONDS,//空闲时间单位
new ArrayBlockingQueue<>(10),//任务队列(指定排队任务数量)
Executors.defaultThreadFactory(),//线程对象任务工厂
new ThreadPoolExecutor.AbortPolicy()//拒绝策略
);
for (int i = 0; i < 13; i++) {
tpe.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "=====");
}
});
}
}
上面代码的循环次数(任务提交数量)小于等于核心线程数+队列数(共计12)时,只有两条线程在工作;循环次数(任务提交数量)大于等于核心线程数+队列数(共计12)并且小于等于最大线程数+队列数(共计15)时,会创建其他线程;循环次数(任务提交数量)大于最大线程数+队列数(共计15)时出发拒绝策略
拒绝策略:
策略选择 | 说明 |
ThreadPoolExecutor.AbortPolicy | 丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecutor . DiscardPolicy | 丟弃任务,但是不抛出异常这是不推荐的做法 |
ThreadPoolExecutor.Discard0ldestPolicy | 抛弃队列中等待最久的任务然后把当前任务加入队列中 |
ThreadPoolExecutor .CallerRunsPolicy | 调用任务的run()方法,绕过线程池直接执行 |
四、单列设计模式
单例指单个实例,保证类的对象在内存中只有一份
使用场景:
如果创建一个对象需要消耗的资源过多,比如 I/0 与数据库的连接
并且这个对象完全是可以复用的,我们就可以考虑将其设计为单例的对象
单例设计模式:
1)饿汉式
class Single1{
private Single1(){//私有构造方法
}
private static Single1 s = new Single1(); //私有化对象创建,上来直接创建对象
public static Single1 getInstance(){
return s;
};
}
2)懒汉试
public class DomOne {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Single2 s = Single2.getInstance();
}
});
}
}
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;
};
}