多线程性能优化基础

文章

锁的案例

类锁

java 复制代码
/**
 * 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;
    }


}

对象锁

java 复制代码
/**
 * synchronized 内置🔒
 *
 * 类说明:synchronized的作用
 *
 * 对象锁
 *
 */
public class SynTest {

	private long count =0;
	private Object obj = new Object(); // 作为一个锁 对象锁obj
	private String str = new String(); // 作为一个锁 对象锁str

	public long getCount() {
		return count;
	}

	public void setCount(long count) {
		this.count = count;
	}

	// count进行累加
	public void incCount(){

		// T1 T2
		synchronized (obj){ // 使用一把锁
			// T0 T1 T2 == 多个线程 并发问题
			count++;
		}
	}

	// count进行累加
	// synchronized == 对象锁 == this
	public 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;
		}

		@Override
		public 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);

		// 2w

		System.out.println(simplOper.count);//20000
	}
}

类锁和对象锁虽然只是加了synchroniezd,但是底层是调用了native代码去实现的,所以叫做隐式锁。

显示锁

java 复制代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用显示锁的范式
 */
public class LockDemo {

	private int count = 0;

	// 内置锁 == this
	private synchronized  void test() {

	}

	// 内置锁 == LockDemo.class
	private 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();
	}

}

多线程之生产者消费者案例

暴露安全问题

java 复制代码
/**
 * 描述资源
 */
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;
    }

    /**
     * 执行线程任务
     */
    @Override
    public 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;
    }

    /**
     * 执行线程任务
     */
    @Override
    public 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();
    }

}


内置锁解决安全问题

java 复制代码
/**
 * 描述资源
 */
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;
    }

    /**
     * 执行线程任务
     */
    @Override
    public 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;
    }

    /**
     * 执行线程任务
     */
    @Override
    public 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()

java 复制代码
/**
 * 描述资源
 */
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;
    }

    /**
     * 执行线程任务
     */
    @Override
    public 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;
    }

    /**
     * 执行线程任务
     */
    @Override
    public 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中用于实现线程本地存储的一个类。它为每个线程提供独立的变量副本,使得每个线程都可以独立的修改自己的副本,而不影响其他线程所对应的副本。这种机制常用于避免多线程环境下共享变量带来的线程安全的问题。

基本使用

  1. 创建ThreadLocal变量
java 复制代码
private static ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
  1. 设置值(set)
java 复制代码
threadLocalValue.set(100);
  1. 获取(get)
java 复制代码
Integer value = threadLocalValue.get(); // 如果未设置,默认返回 null
  1. 移除值
java 复制代码
threadLocalValue.remove(); // 防止内存泄漏,使用完后应调用 remove

示例

java 复制代码
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();
    }
}

输出

java 复制代码
Thread-1 初始值: 0
Thread-1 修改后: 1
Thread-2 初始值: 0
Thread-2 修改后: 1

两个线程各自拥有独立的 ThreadLocal 副本,互不影响。

相关推荐
CHEN5_023 小时前
【leetcode100】和为k的子数组(两种解法)
java·数据结构·算法
liyi_hz20083 小时前
O2OA (翱途)开发平台新版本发布预告:架构升级、性能跃迁、功能全面进化
android·java·javascript·开源软件
熊猫钓鱼>_>3 小时前
Java String 性能优化与内存管理:现代开发实战指南
java·开发语言·性能优化
华仔啊3 小时前
Spring事件的3种高级玩法,90%的人根本不会用
java·后端
练习时长一年3 小时前
Spring容器的refresh()方法
java·开发语言
Huangyi3 小时前
第一节:Flow的基础知识
android·前端·kotlin
程序员小假3 小时前
MySQL 与 Redis 如何保证双写一致性?
java·后端
Arlene3 小时前
JVM Java虚拟机
java·开发语言·jvm
千码君20163 小时前
Go语言:关于导包的两个重要说明
开发语言·后端·golang·package·导包