在一个线程操作一个资源的时候, 对这个资源进行“上锁”, 被锁住的资源, 其他的线程无法访问。
类似多个人去公共卫生间, 每一个人在进到卫生间的时候, 都会从里面进行反锁。 此时, 其他人如果也需要使用这个卫生间, 就得在门外等待。
21.1.2. 线程锁
线程锁, 就是用来“锁住”一个临界资源, 其他的线程无法访问。 在程序中, 可以分为对象锁和类锁
- 对象锁: 任何的对象, 都可以被当做是一把锁来使用。 但是需要注意, 必须要保证不同的线程看到的锁, 需要是同一把锁才能生效。 如果不同的线程看到的锁对象是不一样的, 此时这把锁将没有任何意义。
- 类锁: 可以将一个类做成锁, 使用 类.class 来作为锁。
21.1.3. 同步代码段
同步代码段, 是来解决临界资源问题最常见的方式。 将一段代码放入到同步代码段中, 将这段代码上锁。
第一个线程抢到了锁标记后, 可以对这个紧接资源上锁, 操作这个临界资源。 此时其他的线程再执行到synchronized的时候, 会进入到锁池, 直到持有锁的线程使用结束后, 对这个资源进行解锁。 此时, 处于锁池中的线程都可以抢这个锁标记, 哪一个线程抢到了, 就进入到就绪态, 没有抢到锁的线程, 依然处于锁池中。
/**
* @Description
*/
public class Program {
public static void main(String[] args) {
// 做⼀个 Runnable 接口的实现类对象,实现卖票
Runnable runnable = new Runnable() {
@Override
public void run() {
while (TicketCenter.ticketCount > 0) {
/*
* 同步代码段,这里的逻辑执行,会被上锁。当这里的逻辑执行结束之后,会自动的解锁。
* 小括号中需要写的是:锁。
* 这里的锁,可以分为:类锁 和 对象锁
*/
synchronized (Thread.class) {
if (TicketCenter.ticketCount <= 0) {
break;
}
System.out.println(String.format("售票员【%s】卖出⼀张票,剩余: %d", Thread.currentThread().getName(), --TicketCenter.ticketCount));
}
}
}
};
// 实例化四个线程,模拟四个售票员
Thread t1 = new Thread(runnable, "周杰伦");
Thread t2 = new Thread(runnable, "林俊杰");
Thread t3 = new Thread(runnable, "蔡依林");
Thread t4 = new Thread(runnable, "周润发");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketCenter {
public static int ticketCount = 100;
}
21.1.4. 同步方法
如果在一个方法中, 所有的逻辑, 都需要放到同一个同步代码段中执行。 这样的方法, 可以直接做成同步方法。
同步方法中的所有的逻辑, 都是在一个同步代码段中执行的。
如果是一个静态方法, 使用当前类做类锁; 如果是一个非静态方法, 使用this做对象锁。
/**
* 使用 synchronized 修饰的方法,就是一个同步方法
* 此时这个方法, 是一个静态的方法, 则这个方法使用的锁是类锁
* @return
*/
public static synchronized Chairman getInstance() {
if (Instance == null) {
Instance = new Chairman();
}
return Instance;
}
21.1.5. 单例设计模式
懒汉式单例, 在多线程的环境下, 会出现问题。 由于临界资源问题的存在, 单例对象可能会被实例化多次。
因此, 单例设计模式, 尤其是懒汉式单例, 需要针对多线程的环境进行处理。
/**
* @Description
*/
public class Boss {
private Boss() {}
private static Boss Instance = null;
public static synchronized Boss getInstance() {
if (Instance == null) {
Instance = new Boss();
}
return Instance;
}
}
21.1.6. 死锁
多个线程, 同时持有对方需要的锁标记, 等待对方释放自己需要的锁标记。
此时就是出现死锁。 线程之间彼此持有对方需要的锁标记, 而不进行释放, 都在等待。
/**
* @Description
*/
public class Program {
public static void main(String[] args) {
Runnable runnable1 = () -> {
synchronized ("a") {
System.out.println("线程A,持有了a锁,在等待b锁");
synchronized ("b") {
System.out.println("线程A同时持有了a锁和b锁");
}
}
};
Runnable runnable2 = () -> {
synchronized ("b") {
System.out.println("线程B,持有了b锁,在等待a锁");
synchronized ("a") {
System.out.println("线程B同时持有了a锁和b锁");
}
}
};
new Thread(runnable1, "A").start();
new Thread(runnable2, "B").start();
}
}
21.1.7. wait、notify
1. 方法简介
Object类中几个方法如下:
-
wait()
- 等待,让当前的线程,释放自己持有的指定的锁标记,进入到等待队列。
- 等待队列中的线程,不参与CPU时间⽚的争抢,也不参与锁标记的争抢。
-
notify()
- 通知、唤醒。唤醒等待队列中,⼀个等待这个锁标记的随机的线程。
- 被唤醒的线程,进⼊到锁池,开始争抢锁标记。
-
notifyAll()
- 通知、唤醒。唤醒等待队列中,所有的等待这个锁标记的线程。
- 被唤醒的线程,进⼊到锁池,开始争抢锁标记。
2. wait和sleep的区别
- sleep()方法,在休眠时间结束后,会自动的被唤醒。 而wait()进入到的阻塞态,需要被notify/notifyAll手动唤醒。
- wait()会释放自己持有的指定的锁标记,进入到阻塞态。sleep()进入到阻塞态的时候,不会释放自己持有的锁标记。
3. 注意事项
无论是wait()方法,还是notity()/notifyAll()⽅法,在使用的时候要注意,⼀定要是自己持有的锁标记,才可以做这个操作。否则会出现 IllegalMonitorStateException 异常。
4. 示例代码
/**
* @Description
*/
public class Program {
public static void main(String[] args) {
Runnable runnable1 = () -> {
synchronized ("a") {
System.out.println("线程A,持有了a锁,在等待b锁");
synchronized ("b") {
System.out.println("线程A同时持有了a锁和b锁");
// 当 "b" 锁使用结束之后,通知另外⼀个线程使用结束了
"b".notify();
}
}
};
Runnable runnable2 = () -> {
synchronized ("b") {
System.out.println("线程B,持有了b锁,在等待a锁");
try {
// 释放自己持有的 "b" 锁标记
"b".wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("a") {
System.out.println("线程B同时持有了a锁和b锁");
}
}
};
new Thread(runnable1, "A").start();
new Thread(runnable2, "B").start();
}
}
21.2. 线程池
21.2.1. 线程池的简介
线程池, 其实就是一个容器, 里面存储了若干个线程。
使用线程池, 最主要是解决线程复用的问题。 之前使用线程的时候, 当我们需要使用一个线程时, 实例化了一个新的线程。 当这个线程使用结束后, 对这个线程进行销毁。 对于需求实现来说是没有问题的, 但是如果频繁的进行线程的开辟和销毁, 其实对于CPU来说, 是一种负荷, 所以要尽量的优化这一点。
可以使用复用机制解决这个问题。 当我们需要使用到一个线程的时候, 不是直接实例化, 而是先去线程池中查找是否有闲置的线程可以使用。 如果有, 直接拿来使用; 如果没有, 再实例化一个新的线程。 并且, 当这个线程使用结束后, 并不是马上销毁, 而是将其放入到线程池中, 以便下次继续使用。
21.2.2. 线程池的开辟
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新