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. 线程池的开辟**
在Java中, 使用ThreadPoolExecutor类来描述线程池, 在这个类的对象实例化的时候, 有几个常见的参数:
![](https://img-blog.csdnimg.cn/f62bbb3bb7f14397aaecca095ca8cd3d.png)
* BlockingQueue
+ ArrayBlockingQueue
+ LinkedBlockingQueue
+ SynchronouseQueue
* RejectedExecutionHandler
+ ThreadPoolExecutor.AbortPolicy : 丢弃新的任务,并抛出异常 RejectedExecutionException
+ ThreadPoolExecutor.DiscardPolicy : 丢弃新的任务,但是不会抛出异常
![img](https://img-blog.csdnimg.cn/img_convert/e2cac6eab703e07f6f16605b67a95966.png)
![img](https://img-blog.csdnimg.cn/img_convert/1d72cbf652fababeaa8e080bdaaf82e8.png)
![img](https://img-blog.csdnimg.cn/img_convert/e6e2ca793c743f0c179c43bcb3e230f2.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**
FCZG7-1714512296068)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[需要这份系统化资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618545628)**