二、进程安全的集合
StringBuffer 是线程安全的,是因为每个方法都添加了 synchronized ,即都是同步的
StringBuilder 是线程不安全
![](https://i-blog.csdnimg.cn/blog_migrate/7b16eb87e0297881c8bce136c6196276.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d5af89adaa31f3bed528e719cf884aa1.png)
ArrayList 是线程不安全
Vector 是线程安全
![](https://i-blog.csdnimg.cn/blog_migrate/c99040495fd14ec88b17c67f681d2923.png)
HashMap 是线程不安全
Hashtable 是线程安全
![](https://i-blog.csdnimg.cn/blog_migrate/ab33186002620cb23186a1128756aff9.png)
比 HashMap 安全,比 Hashtable 快,即安全又快的集合 ConcurrentHashMap【很重要,面试】
三、死锁
死锁:互现持有对方的锁还不释放
就相当:之前我需要做公交去做核算,可是公交司机需要我的核算才能让我坐车。
四、线程通信
4.1 介绍
线程通信,就是线程之间产生联系。
即通知,例如线程A执行到一定时候会==停下==,同时通知另外的线程B执行;线程B执行到一定时候也停下,==通知线程A执行==
需要 Object 类的方法
wait() 让当前线程等待
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒所有处于等待状态的线程
4.2 两个个线程通信
需求:昨天打印机两台交替执行
public class Printer {
private int flag = 1;
public synchronized void print1(){
if (flag != 1) {
try {
// 锁是谁谁调用wait
// 当前线程调用
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("1 ");
System.out.print("2 ");
System.out.print("3 ");
System.out.print("4 ");
System.out.print("\r\n");
// 干完,要修改flag
flag = 2;
// 通知另外一个处于等待状态的线程
// 还是用this调用
this.notify();
}
public synchronized void print2(){
if (flag != 2) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("A ");
System.out.print("B ");
System.out.print("C ");
System.out.print("D ");
System.out.print("\r\n");
flag = 1;
this.notify();
}
}
public static void main(String[] args) {
// 同步方法,print1和print2方法有同一个对象调用
// 方法锁是同一个this,即打印机对象
Printer printer = new Printer();
new Thread(){
@Override
public void run() {
while (true) {
printer.print1();
}
}
}.start();
new Thread(){
@Override
public void run() {
while (true) {
printer.print2();
}
}
}.start();
}
4.3 练习
创建A1 A2两个线程,分别打印1-10,11-20,交替运行
public class A extends Thread{
@Override
public void run() {
synchronized (Test.class) {
for (int i = 1; i < 11; i++) {
if (Test.flag != 1) {
try {
Test.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A --> " + i);
Test.flag = 2;
Test.class.notify();
}
}
}
}
class B extends Thread{
@Override
public void run() {
synchronized (Test.class) {
for (int i = 11; i < 21; i++) {
if (Test.flag != 2) {
try {
Test.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("B --> " + i);
Test.flag = 1;
Test.class.notify();
}
}
}
}
4.4 三个线程的通信
第一次
线程3抢到,判断标志是1,不是自己执行,改动标志为2,随机唤醒一条线程,但是此时所有线程都处于就绪状态,唤醒不唤醒无所谓...
第二次
假如线程2抢到,判断标志是2,自己执行,改动标志为3,随机唤醒一条线程,但是此时所有线程都处于就绪状态,唤醒不唤醒无所谓...
第三次
假如线程3抢到,判断标志是3,自己执行,改动标志为1,随机唤醒一条线程,但是此时所有线程都处于就绪状态,唤醒不唤醒无所谓...
第四次
假如这次又是线程3抢到,判断标志是1,不是自己执行,线程3陷入等待状态,让出资源
如果此时线程2抢到,判断标志是1,不是自己执行,线程2陷入等待状态,让出资源
此时只能线程1执行,判断标志是1,自己执行,改动标志为2, 随机唤醒一条线程,如果唤醒是线程3,线程3从等待状态起来后通过while继续判断标志,发现不是自己,继续等待.此时只有线程1活跃,那么线程1执行,判断标志发现不是自己,陷入等待
解决方案: 全部唤醒 ,使用方法notifyAll(),唤醒所有处于等待状态的线程
public class Printer1 {
private int flag = 1;
public synchronized void print1(){
while (flag != 1) {
try {
// 锁是谁谁调用wait
// 当前线程调用
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("1 ");
System.out.print("2 ");
System.out.print("3 ");
System.out.print("4 ");
System.out.print("\r\n");
// 干完,要修改flag
flag = 2;
// 通知另外一个处于等待状态的线程
// 还是用this调用
// 唤醒所有线程
this.notifyAll();
}
public synchronized void print2(){
while (flag != 2) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("A ");
System.out.print("B ");
System.out.print("C ");
System.out.print("D ");
System.out.print("\r\n");
flag = 3;
this.notifyAll();
}
public synchronized void print3(){
while (flag != 3) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print("一 ");
System.out.print("二 ");
System.out.print("三 ");
System.out.print("四 ");
System.out.print("\r\n");
flag = 1;
this.notifyAll();
}
}
public class TestNotify1 {
public static void main(String[] args) {
// 同步方法,print1和print2方法有同一个对象调用
// 方法锁是同一个this,即打印机对象
Printer1 printer1 = new Printer1();
new Thread(){
@Override
public void run() {
while (true) {
printer1.print1();
}
}
}.start();
new Thread(){
@Override
public void run() {
while (true) {
printer1.print2();
}
}
}.start();
new Thread(){
@Override
public void run() {
while (true) {
printer1.print3();
}
}
}.start();
}
}
第一次
线程3抢到,判断标志是1,不是自己执行,线程3陷入等待状态,让出资源
第二次
线程2抢到,判断标志是1,不是自己执行,线程2陷入等待状态,让出资源
第三次
线程1抢到,判断标志是1,自己执行,改动标志为2,随机唤醒一条线程
假如唤醒的是线程2,判断标志是2,自己执行,改动标志为3,唤醒线程3
但是假设线程2抢到,判断标志不是自己,线程2等待
假设线程1抢到,判断标志为3,不是自己,线程1也等待
线程3抢到资源开始执行,改动标志为1
随机唤醒一条线程,假如唤醒的线程2,现在判断标志是1,不是自己执行
线程2继续等待,现在活着的是线程3,抢到资源,判断标志不是自己,继续等待
4.5 总结
特殊的:
wait和notify方法需要在同步方法或者同步代码块里面执行后
wait会让当前线程进入等待状态,让出资源。奇特线程可以执行
问:wait和sleep区别:
1、wait是Object类的方法,sleep是Thread类方法
2、wait和sleep都可以让当前线程进入阻塞状态
3、wait阻塞当前线程,会让出系统资源,其他线程可执行;sleep阻塞当前线程,会持有锁不释放,qit线程不可执行
4、wait需要在同步方法或同步代码快中使用,但是sleep可以在同步或非同步都可以使用
问:为什么wait和notify方法要设计在Object类中?
答:因为所可以是任意对象,又因为wait和notify需要被对象调用,所以锁对象任意,wait和notify方法也能被任意对象调用,所以就设计在Object类中,因为Object类是所有类的父类。
五、生产者消费者
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置⼀个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓 冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到⼀个空的缓冲区中取产品,也不允许生产者向⼀个满的缓冲区中放入产品。
商品类 Phone
商店类 Shop
package com.qf.pc;
/**
* --- 天道酬勤 ---
*
* @author QiuShiju
* @desc
*/
public class Phone {
String name;
public Phone(){}
public Phone(String name){
this.name = name;
}
}
class Shop {
Phone phone;
// 进货(生产)
public synchronized void putPhone(Phone phone) {
if (this.phone != null) {
// 有货,先等待,让别人消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
// 进货
this.phone = phone;
System.out.println("进货,"+phone.name );
// 通知消费
this.notify();
}
// 售货(消费)
public synchronized void sellPhone() {
if (this.phone == null) {
// 没有商品,不能消费,先等待生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace( );
}
}
// 消费
System.out.println("消费,"+phone.name );
this.phone = null;
// 通知生产
this.notify();
}
}
class TestPC {
public static void main(String[] args) {
Shop shop = new Shop( );
// 生产者线程
new Thread(){
@Override
public void run() {
for (int i = 1; i < 6; i++) {
shop.putPhone(new Phone("IPhone"+i));
}
}
}.start();
// 消费者线程
new Thread(){
@Override
public void run() {
for (int i = 1; i < 11; i++) {
shop.sellPhone();
}
}
}.start();
}
}
六、线程池
6.1 线程池概念
如果有非常多的任务需要非常多的线程来完成,每个线程的工作时间不长,就需要创建很多线程,工作完又立即销毁[==线程频繁创建和销毁线程==]
频繁创建和销毁线程非常消费性能,那么线程池,就是可以创建一些线程,放在"池子"中,用的时候去池子取一个线程去使用,使用完再放回去,线程可以重用
线程池,底层其实就是集合队列,里面存储线程对象,用的时候去抽即可,就不要频繁创建线程了
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资 源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过度切换”的问题 --> 摘自阿里官方手册
6.2 线程池原理
![](https://i-blog.csdnimg.cn/blog_migrate/0be5454c961f8c6c9f9c3fbc91683c3d.png)
将任务(task)提交(submit/execute)给线程池(threadpool),由线程池分配线程,运行任务,任务结束后,线程重新放入线程池供后续线程使用
6.3 创建线程池的方式
使用线程池创建线程,执行任务
JDK提供了关于线程池的一些类和接口
Executor: 线程池的根接口,通过execute(Runnable task) 执行任务
ExecutorService: Executor的子接口,通过submit(Runnable task) 来提交任务,执行任务
ThreadPoolExecutor: ExecutorService的子实现类,通过submit(Runnable task) 来提交任务,执行任务
Executors: 执行器类(线程池工厂类),通过该类来获得不同特点的线程池对象
类中有很多静态方法,直接调用就可以获得各种特点线程池对象
6.4 不同特点的线程池
通过Executors调用以下静态方法获得不同特点的线程池对象
以上线程池操作在阿里java开发手册中是不建议用的.....
说明:Executors 返回的线程池对象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。 ----------------------- OOM 内存溢出,即系统资源耗尽
线程池执行任务时,可以采用两种方法:
execute(): 没有返回值,无法判断任务是否执行成功
submit():会返回Future对象,通过该对象判断任务是否执行成功
public static void main(String[] args) {
// 固定大小线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 50; i++) {
fixedThreadPool.execute(new Runnable( ) {
@Override
public void run() {
// System.out.println(Thread.currentThread().getName()+"干活" );
}
});
}
// 动态调整大小线程池
ExecutorService fixedThreadPool2 = Executors.newCachedThreadPool();
for (int i = 0; i < 50; i++) {
fixedThreadPool2.execute(new Runnable( ) {
@Override
public void run() {
// System.out.println(Thread.currentThread().getName()+"干活" );
}
});
}
// 定时线程池
ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 5; i++) {
scheduledExecutor.schedule(new Runnable( ) {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"干活" );
}
},3, TimeUnit.SECONDS);
}
}
6.5 ThreadPoolExecutor[重要]
ThreadPoolExecutor ==很重要,有7个参数==
问: 什么是创建临时线程?
答: 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建线程
问:什么时候开始拒绝任务?
答:核心线程和临时线程都在忙,任务队列也满了,新的任务过来就会拒绝
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(10);
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 15, 10, TimeUnit.SECONDS, queue);
for (int i = 0; i < 50; i++) {
poolExecutor.submit(new Runnable( ) {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"干活" );
}
});
}