网络安全最新三、线程安全

    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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值