多线程性能优化基础
文章
- 锁的案例
- 类锁
- 对象锁
- 显示锁
- 多线程之生产者消费者案例
- 暴露安全问题
- 内置锁解决安全问题
- 采用唤醒机制解决问题
- ThreadLocal
- 基本使用
- 示例
锁的案例
类锁
/*** synchronized 内置🔒 JDK提供的 内部已经封装*/
public class GPSEngine {private static GPSEngine gpsEngine;
//持有的一把锁 GpsEngine.class对象锁 == 类锁public static synchronized GPSEngine getInstance() {if (gpsEngine == null) {
//Thread-0 此时 Thread-1,Thread-2,Thread-3...进不来gpsEngine = new GPSEngine();}return gpsEngine;//Thread-0执行完毕 释放这个锁}//双重检验,懒加载单例,优化上面的性能public static GPSEngine getGpsEngine() {if (null == gpsEngine) {//Thread-1,Thread-2,Thread-3...都可以进来//GpsEngine.class对象锁 == 类锁synchronized (GPSEngine.class) { //Thread-0执行中,此时Thread-1,Thread-2,Thread-3...进不来//Thread-1需要判断一下,Thread-2需要判断一下,Thread-3需要判断一下if (null == gpsEngine) {gpsEngine = new GPSEngine();}}}//Thread-0执行完毕,释放锁,解锁return gpsEngine;}}
对象锁
/*** synchronized 内置🔒** 类说明:synchronized的作用** 对象锁**/
public class SynTest {private long count =0;private Object obj = new Object(); // 作为一个锁 对象锁objprivate String str = new String(); // 作为一个锁 对象锁strpublic long getCount() {return count;}public void setCount(long count) {this.count = count;}// count进行累加public void incCount(){// T1 T2synchronized (obj){ // 使用一把锁// T0 T1 T2 == 多个线程 并发问题count++;}}// count进行累加// synchronized == 对象锁 == thispublic synchronized void incCount2(){count++;}// count进行累加// this == 对象锁public void incCount3(){synchronized (this){count++;}}// 线程private static class Count extends Thread{private SynTest simplOper;public Count(SynTest simplOper) {this.simplOper = simplOper;}@Overridepublic void run() {for(int i=0;i<10000;i++){simplOper.incCount(); // count = count+10000}}}public static void main(String[] args) throws InterruptedException {SynTest simplOper = new SynTest();// 启动两个线程Count count1 = new Count(simplOper);Count count2 = new Count(simplOper);count1.start();count2.start();Thread.sleep(50);// 2wSystem.out.println(simplOper.count);//20000}
}
类锁和对象锁虽然只是加了synchroniezd,但是底层是调用了native代码去实现的,所以叫做隐式锁。
显示锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 使用显示锁的范式*/
public class LockDemo {private int count = 0;// 内置锁 == thisprivate synchronized void test() {}// 内置锁 == LockDemo.classprivate static synchronized void test2() {}private synchronized void test3() {// 业务逻辑,无法被中断}// 声明一个显示锁之可重入锁 new 可重入锁// 非公平锁private Lock lock = new ReentrantLock();public void incr(){// 使用 显示锁 的规范lock.lock();try{count++;} finally { // 打死都要执行 最后一定会执行lock.unlock();}}// 可重入锁🔒 意思就是递归调用自己,锁可以释放出来// synchronized == 天生就是 可重入锁🔒// 如果是非重入锁🔒 ,就会自己把自己锁死public synchronized void incr2(){count++;incr2();}public static void main(String[] args) {LockDemo lockDemo = new LockDemo();}}
多线程之生产者消费者案例
暴露安全问题
/*** 描述资源*/
class Res {private String name;private int id;// 生产public void put(String name) { // 生产一个面包id += 1;this.name = name + " 商品编号:" + id;System.out.println(Thread.currentThread().getName() + "生产者 生产了:" + this.name);}// 消费public void out() {id -= 1;System.out.println(Thread.currentThread().getName() + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:" + this.name);}}/*** 描述生产者任务*/
class ProduceRunnable implements Runnable {/*** 此变量已经不是共享数据了,因为:* new Thread(produceRunnable).start();* new Thread(consumeRunnable).start();** 所以:Thread-0有自己的res Thread-1也有自己的res*/private Res res;ProduceRunnable(Res res) {this.res = res;}/*** 执行线程任务*/@Overridepublic void run() {res.put("面包🍞");}
}/*** 描述消费者任务*/
class ConsumeRunnable implements Runnable {/*** 此变量已经不是共享数据了,因为:* new Thread(produceRunnable).start();* new Thread(consumeRunnable).start();** 所以:Thread-0有自己的res Thread-1也有自己的res*/private Res res;ConsumeRunnable(Res res) {this.res = res;}/*** 执行线程任务*/@Overridepublic void run() {res.out();}
}/*** 多线程通讯案例** 案例一存在安全问题: 分析以下程序是否存在安全🔐问题:*/
public class ThreadCommunicationDemo {public static void main(String[] args) {// 创建资源对象Res res = new Res();// 创建生产者任务ProduceRunnable produceRunnable = new ProduceRunnable(res);// 创建消费者任务ConsumeRunnable consumeRunnable = new ConsumeRunnable(res);// 启动生产者任务new Thread(produceRunnable).start();// 启动消费者任务new Thread(consumeRunnable).start();}}
内置锁解决安全问题
/*** 描述资源*/
class Res2 {/*** name 是共享数据,被Thread-0 Thread-1公用使用*/private String name;/*** id name 是共享数据,被Thread-0 Thread-1公用使用*/private int id;/*** 对操作共享数据的地方加入同步锁的方式来解决安全问题* public synchronized(this) void put(String name) {*/public synchronized void put(String name) {id += 1;// this.name = name + " 生成商品编号:" + id;System.out.println(Thread.currentThread().getName() + "生产者 生产了:" + this.id);}/*** 对操作共享数据的地方加入同步锁的方式来解决安全问题* public synchronized(this) void put(String name) {*/public synchronized void out() {// this.name = name + " 消费商品编号:" + id;System.out.println(Thread.currentThread().getName() + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:" + this.id);id -= 1;}}/*** 描述生产者任务*/
class ProduceRunnable2 implements Runnable {/*** 此变量已经不是共享数据了,因为:* new Thread(produceRunnable).start();* new Thread(consumeRunnable).start();** 所以:Thread-0有自己的res Thread-1也有自己的res*/private Res2 res;ProduceRunnable2(Res2 res) {this.res = res;}/*** 执行线程任务*/@Overridepublic void run() {for (int i = 0; i < 20; i++) {res.put("面包🍞");}}
}/*** 描述消费者任务*/
class ConsumeRunnable2 implements Runnable {/*** 此变量已经不是共享数据了,因为:* new Thread(produceRunnable).start();* new Thread(consumeRunnable).start();** 所以:Thread-0有自己的res Thread-1也有自己的res*/private Res2 res;ConsumeRunnable2(Res2 res) {this.res = res;}/*** 执行线程任务*/@Overridepublic void run() {for (int i = 0; i < 20; i++) {res.out();}}
}/*** 多线程通讯案例(加入了synchronized解决了安全问题:)** 由于以上程序本身就是多线程程序,所以寻找共享数据,然后对操作共享数据的地方加入同步锁的方式来解决安全问题 案例二** 执行结果:每次不一样由CPU随机性决定的,CPU随机的执行:例如:这个线程执行一下,也有可能哪个线程执行一下,也可能这个线程执行完,等等:*/
public class Test {public static void main(String[] args) {// 创建资源对象Res2 res = new Res2();// 创建生产者任务ProduceRunnable2 produceRunnable = new ProduceRunnable2(res);// 创建消费者任务ConsumeRunnable2 consumeRunnable = new ConsumeRunnable2(res);// 启动生产者任务new Thread(produceRunnable).start();// 启动消费者任务new Thread(consumeRunnable).start();}}
采用唤醒机制解决问题
等待区域:wait()
获取对象的锁
sync(持有的锁 对象的锁){因为被锁住了,你没法获取this锁
wait();内部就会释放this锁,这样 通知区域才可以拿到锁
}
this锁 同一把锁
通知区域:notify();
获取对象的锁
syn(持有的锁 对象的锁){
notify();按道理我是根本获取不到this锁的为什么? 因为wait()
}
/*** 描述资源*/
class Res3 {/*** name 是共享数据,被Thread-0 Thread-1公用使用*/private String name;/*** id 是共享数据,被Thread-0 Thread-1公用使用*/private int id;/*** flag 是共享数据,被Thread-0 Thread-1公用使用*/private boolean flag; // 定义标记 默认第一次为false/*** 对操作共享数据的地方加入同步锁的方式来解决安全问题* public synchronized(this) void put(String name) {*/public synchronized void put(String name) {if (flag) {try {wait();} catch (InterruptedException e) { e.printStackTrace(); }}id++;System.out.println(Thread.currentThread().getName() + "生产者 生产了:" + id);flag = true;notify();}public synchronized void out() {if (!flag) {try {wait();} catch (InterruptedException e) { e.printStackTrace(); }}System.out.println(Thread.currentThread().getName() + ">>>>>>>> 消费者 消费了:" + id);flag = false;notify();}
}
/*** 描述生产者任务*/
class ProduceRunnable3 implements Runnable {/*** 此变量已经不是共享数据了,因为:* new Thread(produceRunnable).start();* new Thread(consumeRunnable).start();** 所以:Thread-0有自己的res Thread-1也有自己的res*/private Res3 res;ProduceRunnable3(Res3 res) {this.res = res;}/*** 执行线程任务*/@Overridepublic void run() {for (int i = 0; i < 20; i++) {res.put("面包🍞");}}
}/*** 描述消费者任务*/
class ConsumeRunnable3 implements Runnable {/*** 此变量已经不是共享数据了,因为:* new Thread(produceRunnable).start();* new Thread(consumeRunnable).start();** 所以:Thread-0有自己的res Thread-1也有自己的res*/private Res3 res;ConsumeRunnable3(Res3 res) {this.res = res;}/*** 执行线程任务*/@Overridepublic void run() {for (int i = 0; i < 20; i++) {res.out();}}
}/*** 以上案例二,虽然解决了安全🔐问题,但是:CPU随机执行线程,搞得很混乱,没有满足(生产一个,消费一个)的功能*** 1.定义共享数据 boolean flag = false;* 2.生产者 if(flag == false) { 开始生产 生产完毕后 notify唤醒,由于第一次没有线程wait()等待, 属于空唤醒,在Java里面是运行空唤醒的 }* 3.生产者,设置为冻结状态:释放CPU执行资格,释放CPU执行权 ,CPU就可以去执行Thread-0线程了* 4.消费者 if(flag == false) { 开始消费 消费完毕后 notify唤醒(注意⚠️如果不notify唤醒 就属于死锁了,因为两个线程都冻结了),然后在wait(); 冻结状态:释放CPU执行资格,释放CPU执行权 ,CPU就会去执行Thread-1线程了}* 这样来回的切换,生产者生产后,告诉消费者,消费者消费后,告诉生产者 ............*** 多线程通讯案例*** 案例三:等待唤醒机制:* wait(); 等待/冻结 :可以将线程冻结,释放CPU执行资格,释放CPU执行权,并把此线程临时存储到线程池* notify(); 唤醒线程池里面 任意一个线程,没有顺序;* notifyAll(); 唤醒线程池里面,全部的线程;* 注意:⚠️ wait(); notify(); 这些必须要有同步锁包裹着**/
public class Test2 {public static void main(String[] args) {// 创建资源对象Res3 res = new Res3();// 创建生产者任务ProduceRunnable3 produceRunnable = new ProduceRunnable3(res);// 创建消费者任务ConsumeRunnable3 consumeRunnable = new ConsumeRunnable3(res);// 启动生产者任务new Thread(produceRunnable).start();// 启动消费者任务new Thread(consumeRunnable).start();}}
儿时的游戏:(等待 与 唤醒)
有一群小朋友一起玩一个游戏,这个游戏可能大家都玩过,大家一起划拳,划拳输得最惨的那个小朋友去抓人(这个小朋友取名为 CPU),被抓的很多人取名为线程,有很多线程,如果其中一个小朋友(例如:Thread-3) 被木头了(wait()😉 就站着不准动了(冻结状态),Thread-3小朋友站着不动(冻结状态) CPU小朋友就不会去抓Thread-3小朋友,因为Thread-3小朋友(释放CPU执行资格) 需要其他小朋友(例如:Thread-5) 去啪一下Thread-3小朋友(notify()😉, 此时Thread-3小朋友就可以跑了(被唤醒) 此时Thread-3小朋友具备被抓的资格(具备CPU执行资格),能否被抓得到 要看CPU小朋友的随机抓人法(CPU切换线程的随机性)
等待唤醒机制:
wait(); 等待/冻结 :可以将线程冻结,释放CPU执行资格,释放CPU执行权,并把此线程临时存储到线程池
notify(); 唤醒线程池里面 任意一个线程,没有顺序;
notifyAll(); 唤醒线程池里面,全部的线程;
使用等待唤醒注意事项:
1.使用来wait();冻结,就必须要被其他方notify();,否则一直wait()冻结,所以等待与唤醒是配合一起使用的;
2.wait(); notify(); notifyAll(); 等方法必须被synchronized(锁) {包裹起来},意思就是:wait(); notify(); notifyAll(); 必须要有同步锁🔒,否则毫无意义;
3.wait(); notify(); notifyAll(); 等方法必须持有同一把锁🔒,因为:lockA.wait(); 只能使用 lockA.notify(); / lockA.notifyAll(); , 它们是使用同一把锁🔒的;
等待:🔒锁.wait();
唤醒:🔒锁.notify();
唤醒:🔒锁.notifyAll();
ThreadLocal
ThreadLocal是Java中用于实现线程本地存储的一个类。它为每个线程提供独立的变量副本,使得每个线程都可以独立的修改自己的副本,而不影响其他线程所对应的副本。这种机制常用于避免多线程环境下共享变量带来的线程安全的问题。
基本使用
- 创建ThreadLocal变量
private static ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
- 设置值(set)
threadLocalValue.set(100);
- 获取(get)
Integer value = threadLocalValue.get(); // 如果未设置,默认返回 null
- 移除值
threadLocalValue.remove(); // 防止内存泄漏,使用完后应调用 remove
示例
public class ThreadLocalExample {private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);public static void main(String[] args) throws InterruptedException {Runnable task = () -> {int value = threadLocal.get();System.out.println(Thread.currentThread().getName() + " 初始值: " + value);threadLocal.set(value + 1);System.out.println(Thread.currentThread().getName() + " 修改后: " + threadLocal.get());threadLocal.remove(); // 清理资源};Thread t1 = new Thread(task, "Thread-1");Thread t2 = new Thread(task, "Thread-2");t1.start();t2.start();t1.join();t2.join();}
}
输出
Thread-1 初始值: 0
Thread-1 修改后: 1
Thread-2 初始值: 0
Thread-2 修改后: 1
两个线程各自拥有独立的 ThreadLocal 副本,互不影响。