文章
锁的案例
类锁
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中用于实现线程本地存储的一个类。它为每个线程提供独立的变量副本,使得每个线程都可以独立的修改自己的副本,而不影响其他线程所对应的副本。这种机制常用于避免多线程环境下共享变量带来的线程安全的问题。
基本使用
- 创建ThreadLocal变量
java
private static ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
- 设置值(set)
java
threadLocalValue.set(100);
- 获取(get)
java
Integer value = threadLocalValue.get(); // 如果未设置,默认返回 null
- 移除值
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 副本,互不影响。