synchronized (this) {
value --;
}
}
public int getValue() {
synchronized (this) {
}
}
}
![](https://img-blog.csdnimg.cn/img_convert/a232e41c6268c91428867854ba67813a.png)
## 三、方法上的 synchronized
### 1)语法:
**成员方法上的synchronized**
class Test {
public synchronized void test() {
}
}
等价于 锁住当前类this对象
class Test{
public void test() {
synchronized(this) {
}
}
}
静态方法上的synchronized
class Test {
public synchronized static void test() {
}
}
等价于 锁住类对象
class Test{
public static void test() {
synchronized(Test.class) {
}
}
}
**不加synchronized 的方法**
不加synchronized 的方法就好比不遵守规则的人,不去老实排队(好比翻窗户进去) 不能保证原子性
### 2)线程八锁
![](https://img-blog.csdnimg.cn/img_convert/220292f7728589cb548ed8cbafbb906a.png)
锁住的是同一个对象,所以时间片先分配给谁,先输出谁,所以1 跟 2 都有可能
![](https://img-blog.csdnimg.cn/img_convert/e114cf317cf0a87ba417af0029090f69.png)
sleep()不会释放锁,所以情况跟情况1差不多,只是中间多了一个1s的等待
![](https://img-blog.csdnimg.cn/img_convert/bdbfd346baec485e8d03d7c6885f74d4.png)
![](https://img-blog.csdnimg.cn/img_convert/ddb43f7b8eee9c898d6d64121d569178.png)
![](https://img-blog.csdnimg.cn/img_convert/3cc364172014c45529301a3d5756b4d3.png)
![](https://img-blog.csdnimg.cn/img_convert/414fa54ff741cca0d3b2f4a639dff75f.png)
![](https://img-blog.csdnimg.cn/img_convert/a3f280acb6ba10197d69a8f72e70b87a.png)
## 四、线程安全分析
### 1)成员变量和静态变量是否线程安全?
* 静态变量不用说肯定是会存在线程安全问题的。
* 成员变量,如果只有一个实例对象,然后存在多线程的情况下,是会有多线程安全的问题。
* 如果他们没有共享,则线程安全
* 如果存在共享
* + 如果只有读操作,则线程安全
+ 如果有读写操作,则这段代码是临界区,需要考虑线程安全问题。
### 2)局部变量是否线程安全?
* 局部变量是线程安全的。
* 但是局部变量引用的对象则不一定
* + 如果对象没有逃离方法的作用范围,那么是线程安全的。
+ 如果对象逃离了方法的作用范围,需要考虑是不是线程安全。 例如:引用了堆中的对象,会被共享。
### 3)局部变量线程安全分析?
#### 1. 普通局部变量
public static void test1() {
int i = 10;
i ++;
}
这个代码不会出现线程安全问题
每一个线程会有自己对应的栈空间,每个线程调用test1()方案时局部变量i,会在每个线程自己的栈空间调用,进行压栈,所以各自调用自己的,互不干扰,不存在共享。
![](https://img-blog.csdnimg.cn/img_convert/1963eab2e98869582cd4ab60647dbc4c.png)
#### 2. 局部变量引用
public static void main(String[] args) {
ThreadUnsafe tes = new ThreadUnsafe();
for (int i = 0; i < 2; i ++ ) {
new Thread(() -> {
test.method1(200);
}, “Thread” + (i + 1)).start();
}
class ThreadUnsafe {
ArrayList list = new ArrayList<>();
public void method1(int loopNumber) {
for (int i = 0; i < loopNumber; i++ ) {
method2();
method3();
}
}
private void methdo2() {
list.add("1");
}
private void methdo3() {
list.remove(0);
}
}
上面的代码中,list是成员变量,所以是共享资源,也就是临界区,临界区中的代码如果不加以限制,多线程情况下,会造成执行顺序不可预测,发生竞态条件
所以上面的代码会有线程安全的问题,会导致发生数组下表越界异常(同时执行remove操作,这时候就会发生错误)
解决方法
需要确保只能有一个线程能够执行,或者将成员变量 变成 局部变量。
#### 3. 暴露引用对象
如果创建一个子类 继承 ThreadUnsafe类,然后子类对 method2 或者 method3 进行重写,创建一个新的线程执行
这时候list这个局部变量就暴露了, 也就是在子类中的一个新的线程中被引用到了,这时候list就是一个共享资源,也就是临界区,那么就会发生线程安全问题
public static void main(String[] args) {
ThreadUnsafe tes = new ThreadUnsafe();
for (int i = 0; i < 2; i ++ ) {
new Thread(() -> {
test.method1(200);
}, “Thread” + (i + 1)).start();
}
class ThreadUnsafe {
ArrayList list = new ArrayList<>();
public void method1(int loopNumber) {
for (int i = 0; i < loopNumber; i++ ) {
method2();
method3();
}
}
//private
public void methdo2() {
list.add(“1”);
}
//private
public void methdo3() {
list.remove(0);
}
}
class ThreadSafeSubClass extends ThreadSafe{
@Override
public void method3(ArrayList list) {
new Thread(() -> {
list.remove(0);
}).start();
}
}
解决方案:
可以通过将父类中的方法权限修饰符进行修改,变成private或者final等,子类就不能够进行重写,这样就不会导致线程安全问题。
#### 4. 常见线程安全类
* String
* Integer
* StringBuffer
* Random
* Vector
* Hashtable
* java.util.concurrent 包下的类
这里说他们是线程安全的是指,多个线程调用他们同一个实例的某个方法时,是线程安全的
Hashtable table = new Hashtable();
new Thread(() -> {
table.put(“key”, “value1”);
}).start();
new Thread(() -> {
table.put(“key”, “value2”);
}).start();
可以看源码,是添加了synchronized锁,保证了原子性
![](https://img-blog.csdnimg.cn/img_convert/b2b1b170473f463e76ad83299331851f.png)
但是线程安全是调用单一方法
如果多个方法组合调用 ,那么将就不是线程安全的了
##### 4.1. 线程安全类方法的组合
Hashtable table = newHashtable();
if (table.get(“key”) == null) {
table.put(“key”, value);
}
线程1跟线程2同时访问上面的代码
单独访问put跟get方法是有原子性的,但是两个组合起来就不是了
![](https://img-blog.csdnimg.cn/img_convert/2fcd6582126c68a22d01e92b7fb72ad7.png)
##### 4.2. 不可变类线程安全性
String、Integer是不可变类,所以其内部状态是不可修改的,因此他们的方法都是线程安全的
疑问:String 中有 substring等方法不是可以修改他的值吗
substring是创建一个新的值,所以不会对原本字符串进行修改。
##### 4.3. 案例分析
继承了HttpServlet,Servlet是Tomcat中的,只能有一个实例,所以omcat中的多线程调用的时候共享使用,就会发生线程安全得问题
例1:
![](https://img-blog.csdnimg.cn/img_convert/c2d598059fac9c09ade9e60ee6ce764d.png)
例2:
![](https://img-blog.csdnimg.cn/img_convert/4cb18d4d211c6d9c5efe35bd86a3a9b3.png)
最好对count有一些保护,防止称为临界区。
例3:
![](https://img-blog.csdnimg.cn/img_convert/aab2b379fb016a2903fafb4dae37e917.png)
例4:
![](https://img-blog.csdnimg.cn/img_convert/5d8ebc02a0bbf1ccceed138fd6a65c52.png)
例5:如果将例4中的Connection 写成成员变量,不是局部变量,那么就会有线程安全问题
因为 servlet 只有一份,导致 userservice只有一份,所以UserDao也只有一份,所以多线程访问的时候,就会导致可能第二个线程close链接,第一个线程就拿不到了。
例6:
![](https://img-blog.csdnimg.cn/img_convert/77374c6a4fbc1102eaaed5937b3ff14d.png)
![](https://img-blog.csdnimg.cn/img_convert/c97608dff09f79d38a0ce4a94c4eb834.png)
所以平时书写的时候,不想往外暴露的就写成final,或者private私有的,可以增强安全性。
String 类是不可变的,但是他也是写成final,防止发生继承之后覆盖行为,修改了。这也是很经典的 **闭合原则**
## 五、Monitor 概念
### 1)Java 对象头
![](https://img-blog.csdnimg.cn/img_convert/27dafe0cdf6faa2a857ac22f5fd5dad9.png)
![](https://img-blog.csdnimg.cn/img_convert/28b2b750f9fdb56dd0e28778d2347357.png)
![](https://img-blog.csdnimg.cn/img_convert/b80af3cdfcdfefdf58292c57060020b7.png)
例子:
int 占用 4个字节
Integer 占用 8个对象头 + 4 个int值字节 12字节
### 2)Monitor (锁)
Monitor 被翻译成 **监视器 或 管程(操作系统层面)**
![](https://img-blog.csdnimg.cn/img_convert/c4a19cdc315a848c7fff76c39cfa8883.png)
* 刚开始的时候Monitor 为null
* 当 第一个线程 执行到 synchronized(obj)的时候,因为是第一个,所以obj对象的 对象头中的 MarkWord就会通过 **指针**的形式关联一个 Monitor ,然后将当前线程 设置成 Monitor中的Owner,表示现在的所有者,只能有一个Owner
* 然后第二个线程来执行的时候,执行到 synchronized(obj)的时候,发现obj 关联的 Monitor 已经有 Owner 了,这时候就会将第二个线程放到 EntryList 中阻塞等待,相当于放到一个阻塞队列中,进入BLOCKED状态
* 然后第三个线程来执行也是相同,进入EntryList 中阻塞等待,以此类推。。。
* 然后等第一个线程的同步代码执行完毕之后,就会唤醒 EntryList中等待的线程来竞争锁,这是一种不公平的挑选,不是先来先出的。
注意:
* synchronized 必须是进入同一个对象的monitor才能有上述的效果
* 不加synchronized 的对象不会关联监视器,不遵从上述规则
### 3)synchronized 优化原理
#### 1. 轻量级锁
轻量级锁的使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
轻量级锁 使用者是没有感知的,语法仍然是 synchronized
**假设有两个方法同步块,利用同一个对象加锁**
static final Object obj = new Object();
public static void method1() {
synchronized( obj ) {
//同步块 A
method2();
}
}
public static void method2() {
synchronized( obj ) {
//同步块B
}
}
**原理:**
![](https://img-blog.csdnimg.cn/img_convert/affbb2eb8194d20689dc17feb6336af0.png)
![](https://img-blog.csdnimg.cn/img_convert/7c1c31f4d6ab9bffd6f2be89fe1368d6.png)
![](https://img-blog.csdnimg.cn/img_convert/813c540efe727e3a35660600afd09d65.png)
![](https://img-blog.csdnimg.cn/img_convert/4c7d3d764e225fa711bedb700ed4962c.png)
![](https://img-blog.csdnimg.cn/img_convert/fc941acb28621474bf1b1ead7c5a13c1.png)
#### 2. 锁膨胀
如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时一种情况就是有其他线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变成重量级锁
static Object obj = new Object();
public static void method1() {
synchronized( obj ) {
//同步块
}
}
![](https://img-blog.csdnimg.cn/img_convert/a0acf1d59427f12aba10314a198512e3.png)
**所谓的锁膨胀,也就是在将来的解锁操作 进入一个重量级锁的解锁操作**
根据上图进行解释分析:
* 当线程1 加轻量级锁失败,进入了锁膨胀流程
* 这时候Object 对象就会申请一个Monitor锁,并且让Object 的对象头修改,指向重量级锁的地址
* 然后线程1 就进入Monitor的 EntryList 阻塞队列 BLOCKED,这样就不会让线程1干耗着
![](https://img-blog.csdnimg.cn/img_convert/0bb26c6524fd680b8c84f5a46c673a0a.png)
* 当线程0 退出同步代码块进行解锁时,使用CAS将Mark Word 的值恢复给对象头,这时候就恢复失败了,因为Monitor的地址已经不是所记录的地址了
* 这时候经过了锁膨胀,已经是重量级锁的地址了,所以需要进行一个重量级锁的解锁操作,通过对象头地址找到Monitor, 将Monitor的Owner置为 空, 然后唤醒 EntryList 中的线程
#### 3. 自旋优化
重量级锁竞争的时候,还可以使用自旋来进行优化,也就是会进行几次循环重试。
如果当前线程自旋成功(即这时候持锁线程已经推出了同步块,释放了锁),这时候当前线程就可以避免阻塞了。
因为阻塞会导致上下文切换,性能影响比较大。
**自旋只有在多核cpu的情况下才有用,如果单核就没有意义。一个cpu执行同步代码块,另一个线程都没有cpu执行循环,所以没有意义**
![](https://img-blog.csdnimg.cn/img_convert/1af9deaff326fc3b6a0f2d6fed3768f3.png)
![](https://img-blog.csdnimg.cn/img_convert/549705749b4254d8eefda379bd534fe6.png)
![](https://img-blog.csdnimg.cn/img_convert/c37f55d176001f85897ec216d76db61d.png)
#### 4. 偏向锁
轻量级锁在没有竞争时(就自己当前线程在运行),每次重入仍然需要执行CAS操作,CAS肯定是执行失败,但是知道是自己线程,所以会保留下来,有性能损耗
Java 6 中引入了偏向锁来做进一步优化: 只有第一次使用CAS 将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS。
以后只要不发生竞争,这个对象就归该线程所有。
![](https://img-blog.csdnimg.cn/img_convert/7ceb8ed8509d115988fe4eb47926a224.png)
##### 4.1. 偏向状态
![](https://img-blog.csdnimg.cn/img_convert/b54f5e009a9c7b10d05ec4fc747a2ea1.png)
貌似对象的hashcode是懒生成的,当调用hashcode()方法获取 hashcode 值的时候才对对象头里面写入hashcode值,一旦hashcode已经写入,无法使用偏向锁进而使用轻量级锁等
一个对象创建时:
* 如果开启了偏向锁(默认开启),那么对象创建之后,markword的值为 0x05 也就是 最后 3 位 101,这时它的thread、 epoch、age 都为0
* 偏向锁默认是延迟开启的,不会再程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -xx:BiasedLockingStartupDelay=0 来禁用延迟
* 如果对象在一开始创建的,那么就是没有开启偏向锁,markword最后3位为001,这时候在延迟之后输出他的markword也还是之前的001,需要重新创建的才有效
* 如果没有开启偏向锁,那么对象创建之后,markword值为0x01 也就是001,这时它的 hashcode、age都为0,第一次用到hashcode才会赋值。
* 如果一个对象调用开启了偏向锁,调用synchronized之后使用的是偏向锁,那么前面54位地址操作系统会默认分配一个线程id,表示这个对象就给当前线程用了,锁代码同步块执行完毕之后,那54位地址还是执行当前线程,除非有其他线程来竞争,不然一直表示给当前线程使用。但是轻量级锁释放之后就会恢复。
* 如果禁用偏向锁 ,添加 VM 参数 -xx:-UseBiasedLocking(禁用)/ +UseBiasedLocking(启用)
锁使用优先级:
偏向锁 > 轻量级锁 > 重量级锁
##### 4.2. 撤销 - 调用对象 hashCode
线程调用hashcode() 方法之后,根据对象头格式,偏向锁有54位存储线程id,没有多余的地方存储31位的hashcode码,所以会将thread、epoch清空,转成正常Normal对象。
敲黑板:偏向锁和hashcode是互斥存在的;
* 轻量级锁的hashcode存储在线程栈帧的锁记录中;
* 重量级锁的hashcode存储在Monitor对象中!
##### 4.3. 撤销 - 其他线程使用对象
当一个线程使用了当前对象,如果使用的是偏向锁,那么会在thread中记录当前线程的id,表示这个对象给当前线程用
这时候如果有另一个线程来访问这个对象,这时候发现上面有偏向锁,偏向了某一个线程,这时候会撤销偏向锁,改成轻量级锁,然后记录锁地址。
最后解锁之后,无锁状态。
##### 4.4. 撤销 - 调用 wait / notify
* 想要使用wait / notify 这种机制, 只有重量级锁才有
* 所以偏向锁,轻量级锁都会升级成重量级锁。
##### 4.5. 批量重偏向
* 如果对象被多线程访问,但是没有竞争,这时偏向了线程 T1 的对象仍然有机会重新偏向 T2,重偏向会重置对象的 Thread ID,这是一个批量重偏向优化。
* 比如说对象已经偏向了 线程 T1 ,记录了 T1的Thread ID,然后等执行结束之后,线程 T2 来执行,会发现已经被T1用了偏向锁,这时候JVM会撤销偏向锁,改成轻量级锁
* 但是当撤销阈值达到超过20次之后,JVM觉得是不是偏向错了,于是会给这些对象加锁时给予重新偏向至加锁线程的能力。
##### 4.6. 批量撤销
* 当撤销偏向锁操作达到阈值40次之后,JVM会觉得是真的偏向错了,这时候会将整个类的所有对象都设置成不可偏向,新创建的对象也是。
##### 4.7. 锁消除
![](https://img-blog.csdnimg.cn/img_convert/788cff65ec2b5345d4ebd04d40fb8421.png)
我们知道加锁,不管了怎么优化,偏向锁,轻量级锁,都会对性能有锁损耗,但是为什么执行代码耗时一样。
这时候就涉及到了JVM了对象逃逸,
我们Java程序是对字节码通过解释 + 编译的方式来执行的,但是对于其中的一些热点代码,会进行一个优化
这时候就涉及 JIT即时编译器 ,会将热点代码进一步翻译成机器码,缓存起来,以后执行就不用 通过编译了
另外他的一个优化手段就是去分析这个局部变量是不是可以优化,发现根本不会逃离方法作用范围,那就不会共享,那么加锁就没有意义,所以Jit 即时编译器会直接将synchronized优化去掉,只是执行了锁中的代码块的代码。
锁消除参数默认开启,如果需要关闭可以使用功能下面 VM 参数
![](https://img-blog.csdnimg.cn/img_convert/3104e517d61e90f80f7638e40142bdbb.png)
## 六、wait notify
### 1) 原理
![](https://img-blog.csdnimg.cn/img_convert/164c4fbcce24c91a3330ab244d1bd1e1.png)
### 2)API介绍
wait()、notify()、 notifyAll() 方法都是属于Object 对象的方法,需要获取此对象的锁 之后才能够使用
wait() 对象调用wait() 方法之后,线程会 Owner中释放锁,然后进入WaitSet中等待唤醒
wait( time ) 对象调用带参数的wait()方法之后,线程会 Owner中释放锁,然后进入WaitSet中等待 指定时间,然后如果期间没有被唤醒,指定之间之后就会自动唤醒,然后进入EntryList再次尝试获取锁,竞争锁
notify () 对象调用notify()方法之后,会随机挑选一个 WaitSet 中的一个线程唤醒,然后进入EntryList 竞争锁
notifyAll() 对象调用notifyAll()方法之后,会 唤醒 WaitSet 中所有线程,然后进入EntryList中竞争锁。
wait(long n) 进入 TIMED\_WAITING 状态
wait( ) 进入 WAITING状态
### 3)wait notify 的正确使用姿势
#### 1. sleep vs wait
* sleep是Thread的静态方法, wait是 Object 的方法
* sleep 可以随意使用不需要配合synchronized使用, wait需要配合synchronized使用
* sleep 如果是在synchronized中使用,也是不会释放锁的,wait会释放锁
* 共同点: 都是进入TIMED\_WAITING 状态, 有时间的等待
#### 2. 正确使用姿势
synchronized(lock) {
while(条件不成立) {
lock.wait();
}
//干活
}
//另一个线程
synchronized(lock) {
lock.notifyAll();
}
#### 3. 设计模式 之 同步模式保护性暂停
##### 3.1. 定义
![](https://img-blog.csdnimg.cn/img_convert/7297530ee267a9e2afd5f96667a81334.png)
##### 3.2. 实现
class GuardedObject {
//结果
private Object response;
//获取结果
public Object get() {
synchronized (this) {
//没有结果
while(response == null) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return response;
}
}
//产生结果
public void generation(Object response) {
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
}
public static void main(String[] args) {
GuardedObject guardedObject = new GuardedObject();
new Thread(() -> {
System.out.println(guardedObject.get());
}).start();
new Thread(() -> {
int x = 111;
guardedObject.generation(x);
}).start();
}
相对于join的好处
join 需要等待线程执行的结束之后,才能唤醒自己的线程,需要是全局变量 等待另一个线程的结束
保护性暂停等待 设计模式不需要完全等待线程执行结束,可以线程执行到一半的时候就响应线程,从而唤醒自己线程,继续执行,可以是局部变量 等待另一个线程的结果
##### 3.3. 功能增强(超时)
class GuardedObject {
//结果
private Object response;
//获取结果
public Object get(long timeout) {
synchronized (this) {
//记录一个初始时间
long begin = System.currentTimeMillis();
//经过时间
long access = 0;
//没有结果
while(response == null) {
long waitout = timeout - access;
if (waitout <= 0) {
break;
}
try {
this.wait(waitout);
} catch (InterruptedException e) {
e.printStackTrace();
}
access = System.currentTimeMillis() - begin;
}
return response;
}
}
//产生结果
public void generation(Object response) {
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
}
##### 3.4. join 原理 源码
![](https://img-blog.csdnimg.cn/img_convert/9acae7e312294b4251121e2176254f1f.png)
join 实现源码跟 超时增强是一模一样的
在java中,Thread类线程执行完run()方法后,一定会自动执行notifyAll()方法。因为线程在die的时候会释放持用的资源和锁,自动调用自身的notifyAll方法。
##### 3.5. 扩展2
刚才思路的问题:
一个线程通过一个 GuardedObject 对象来进行通信, 通过参数的形式来传递,多个线程之间传递来传递去,非常不方便
实现解耦:
通过设计一个集合来管理多个 GuardedObject ,每个给予一个id,用于区分,然后 生产供给, 获取所需。
![](https://img-blog.csdnimg.cn/img_convert/eacb55ec8496c56e9b2c9a31d2082ca5.png)
class Mailboxes{
//集合
private static Map<Integer, GuardedObject> map = new Hashtable<>();
//id
private static int id = 1;
private static synchronized int geterateId() {
return id ++;
}
public static GuardedObject createGuardedObject() {
GuardedObject guardedObject = new GuardedObject(geterateId());
map.put(guardedObject.getId(), guardedObject);
return guardedObject;
}
public static GuardedObject getGuardedObject(int id) {
return map.remove(id);
}
//获取所有GuardedObject
public static Set<Integer> getIds() {
// System.out.println(map.keySet());
return map.keySet();
}
}
class GuardedObject {
//id
private int id;
//结果
private Object response;
public GuardedObject(int id) {
this.id = id;
}
public int getId() {
return id;
}
//获取结果
public Object get(long timeout) {
synchronized (this) {
//记录一个初始时间
long begin = System.currentTimeMillis();
//经过时间
long access = 0;
//没有结果
while(response == null) {
long waitout = timeout - access;
if (waitout <= 0) {
break;
}
try {
this.wait(waitout);
} catch (InterruptedException e) {
e.printStackTrace();
}
access = System.currentTimeMillis() - begin;
}
return response;
}
}
//产生结果
public void generation(Object response) {
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
}
class People extends Thread {
@Override
public void run() {
//准备收信
GuardedObject guardedObject = Mailboxes.createGuardedObject();
System.out.println(“准备收信” + guardedObject.getId());
Object res = guardedObject.get(5000);
//收到信
System.out.println(“收到信” + res);
}
}
class Postman extends Thread {
private int id;
private String mail;
public Postman(int id, String mail) {
this.id = id;
this.mail = mail;
}
@Override
public void run() {
//开始送信
// System.out.println(“送信” + id + “内容” + mail);
GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
System.out.println(“送信” + id + “内容” + mail);
guardedObject.generation(mail);
}
}
public class Main{
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i ++ ) {
new People().start();
}
Thread.sleep(1);
// System.out.println(Mailboxes.getIds());
for (Integer id : Mailboxes.getIds()) {
// System.out.println(id + “内容”);
new Postman(id, id + “内容”).start();
}
}
}
特点: 产生结果线程 和 使用结果线程是一一对应的。
#### 4. 设计模式 之 异步模式 生产者 / 消费者
##### 4.1. 定义
![](https://img-blog.csdnimg.cn/img_convert/bb5ea5a81279cf391429bcc6b5d44255.png)
为什么这里是异步,保护性暂停模式却是同步?
* 因为保护性暂停是一一对应的,只要产生了结果,我就就能立刻拿到进行处理,所以是同步的
* 但是生产者/消费者 产生了结果之后放入消息队列,如果前面有结果未处理,需要等待,不能立刻执行,所以称为异步
##### 4.2. 实现
final class MessageDeque {
private static LinkedList<Message> list = new LinkedList<>();
private static int capacity;
public MessageDeque(int capacity) {
this.capacity = capacity;
}
//存放消息
public void put(Message message) {
synchronized (list) {
//如果没有满
while (list.size() == capacity) {
try {
list.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("生产者线程等待结束, 没有满, 放入");
list.addLast(message);
list.notifyAll();
System.out.println("放入结束");
}
}
//取出消息
public Message take() {
synchronized (list) {
//如果没有消息
while(list.isEmpty()) {
try {
list.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Message message = list.removeFirst();
System.out.println("消费者等待结束, 拿出消息" + message.getId());
list.notifyAll();
return message;
}
}
}
final class Message {
private int id;
private Object mail;
public Message(int id, Object mail) {
this.id = id;
this.mail = mail;
}
public int getId() {
return id;
}
public Object getMail() {
return mail;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", mail=" + mail +
'}';
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
MessageDeque messageDeque = new MessageDeque(2);
for (int i = 0; i < 3; i ++ ) {
int id = i;
new Thread(() -> {
messageDeque.put(new Message(id, “消息” + id));
}).start();
}
new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
messageDeque.take();
}
}).start();
}
}
## 七、Park & Unpark
### 1) 基本使用
它们是 LockSupport类中的方法
// 暂停当前线程
LockSupport.park();
// 恢复当前线程的运行
LockSupport.unpark(暂停线程对象);
调用了Park方法的线程进入 Waiting的状态
特点:
与Object 的 wait & notify 相比
### 如何自学黑客&网络安全
#### 黑客零基础入门学习路线&规划
**初级黑客**
**1、网络安全理论知识(2天)**
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)
**2、渗透测试基础(一周)**
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等
**3、操作系统基础(一周)**
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)
**4、计算机网络基础(一周)**
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现
**5、数据库基础操作(2天)**
①数据库基础
②SQL语言基础
③数据库安全加固
**6、Web渗透(1周)**
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)
恭喜你,如果学到这里,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web 渗透、安全服务、安全分析等岗位;如果等保模块学的好,还可以从事等保工程师。薪资区间6k-15k
到此为止,大概1个月的时间。你已经成为了一名“脚本小子”。那么你还想往下探索吗?
如果你想要入坑黑客&网络安全,笔者给大家准备了一份:282G全网最全的网络安全资料包评论区留言即可领取!
**7、脚本编程(初级/中级/高级)**
在网络安全领域。是否具备编程能力是“脚本小子”和真正黑客的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力.
如果你零基础入门,笔者建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习;搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP, IDE强烈推荐Sublime;·Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,不要看完;·用Python编写漏洞的exp,然后写一个简单的网络爬虫;·PHP基本语法学习并书写一个简单的博客系统;熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选);·了解Bootstrap的布局或者CSS。
**8、超级黑客**
这部分内容对零基础的同学来说还比较遥远,就不展开细说了,附上学习路线。
![img](https://img-blog.csdnimg.cn/img_convert/3fd39c2ba8ec22649979f245f4221608.webp?x-oss-process=image/format,png)
#### 网络安全工程师企业级学习路线
![img](https://img-blog.csdnimg.cn/img_convert/931ac5ac21a22d230645ccf767358997.webp?x-oss-process=image/format,png)
如图片过大被平台压缩导致看不清的话,评论区点赞和评论区留言获取吧。我都会回复的
视频配套资料&国内外网安书籍、文档&工具
当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。
![img](https://img-blog.csdnimg.cn/img_convert/153b2778a3fe5198265bed9635d63469.webp?x-oss-process=image/format,png)
一些笔者自己买的、其他平台白嫖不到的视频教程。
![img](https://img-blog.csdnimg.cn/img_convert/32eb4b22aa740233c5198d3c161b37e8.webp?x-oss-process=image/format,png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化资料的朋友,可以点击这里获取](https://bbs.csdn.net/topics/618540462)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**