博主在这啰嗦一下,网上也能搜索到,很多人可能知道多线程是什么,怎么开启一个多线程,但是如果要问你实现多线程的方式有哪几种,可能你会顿一下,要想准确地回答出这个问题,还真不是靠死记硬背就能记住的,我在这再重申一下,博文中也会提到前两种的实现方式,至于第三种,本篇不会涉及到,感兴趣的可以自己下来尝试一下;
思考下,上述两种实现方式我们应该优先使用哪个呢?(从继承和实现的层面上考虑)
此处略................ ..................
通俗易懂的解释:
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码
摘自知乎
根据上述言简意赅的解释,我们已经知道了这“家伙的”用途了,说抽象点,就是给修饰的对象(可以是方法、对象、或者是段代码段)加了一把"锁",什么是锁,我打个比方吧:假如你要上厕所,厕所只有一间,一间只有一个坑,上厕所的人可不只有你一个,怎么办,难道大家都拥挤进来,共享这一个坑吗? 当然不是,我们最惬意的方式就是一个人独享厕所,为了做到独享,我们需要排队(先获得锁的人有优先蹲坑权),为了不让其他人在自己蹲坑的时候闯进来,我们需要在上厕所的时候给门上把锁,把其他人"锁"在外面,防止自己在蹲坑的时候有人不遵守规矩"硬"闯进来;这样一来的话,只有等我们上完出来,把锁打开(释放锁)后,下一个人才能进来,独享他自己的蹲坑时间;
当然,程序中锁的释放不是由我们自己写代码手动控制的(区别于Lock接口中的unlock方法),而是由JVM说的算的,如果同步块中的代码异常的话,JVM会主动释放当前线程获得的锁,如果线程顺利执行完毕后,JVM也会主动释放锁,反之,如果线程持有对象的锁却始终处于dosomething状态时,那么其他想要获得该对象锁的线程则会一直处于wait状态,即阻塞在那;
就好比,你一个人占用厕所一直不出来,结果就是,后面的人都进不来,于是乎,一个小时,两个小时过去了,当排队的人都等不及的时候,他们可能会踹门而入,也有可能会报警;当然程序中,如果线程阻塞在那的话,我们除了祈求他只是处理慢了点外只能等了,要是后面的线程处理的任务很重要的话,那这个等就要命了,怎么办,只能kill掉整个进程了,接下来就是排查代码到底是哪个环节出错了;这就是synchronize的弊端,如果线程拿到锁不作为一直阻塞在那的话,其他线程只能等待了,等到地老天荒、海枯石烂,我去,我要是排在后面那个wait他的线程,说什么我都要知道是谁TM给我堵那了!我要Kill他!
当然,还有一种锁叫ReentrantLock,区别于synchronize,这个锁是一个类,实现了Lock接口,我们看下Lock接口中有哪些方法:
这个锁就很厉害了,我们完全可以做到自主控制锁;比如每次养成好的习惯在finally块中写unlock();比如线程执行时,先tryLock(),看看锁有没有被占用,如果占用的话,线程也不能闲着,可以去干其他事情,而且还提供了带参数的tryLock(Long,TimeUnit),可以在多少时间内拿不到锁的话去执行其他事情;再比如调用lockInterruptibly()可以中断阻塞的线程而不必让线程一直在那等等等,等到海枯石烂....本篇不对这个锁做详细的案列分析,后面有时间再单开博文说明吧;
/**
* 一定要区别于start()和run()方法
* (1)run()为Runnable接口中的方法
* (2)Thread线程类实现了Runnable接口
* (3)Thread线程类中包含了一个Runnable的实例target,run()方法真正是由target实现的【target.run()】
* (4)start()方法为Thread类中的方法,其加了synchronized关键字,防止一个同步操作被多次启动
* (5)start()方法内部实现机制调用的是本地库方法(native)
* (6)start是开启一个线程,可以达到多线程执行的效果,start后并不会立马执行线程,而是
* 交给cpu来调度
* (7)run只是一个普通的方法,执行它,程序依然只有一个主线程
* 且run方法中的代码块执行完后,才能执行下面的代码,没有真正达到多线程执行的效果
*/
我们基于上面的注释说明,来看一段demo:
package com.appleyk.dbinit.MyLock;
/**
* start()和run()的区别
*
* @author appleyk
* @version V.1.0.1
* @blob https://blog.csdn.net/appleyk
* @date created on 下午 6:04 2019-7-1
*/
public class DeadLock0 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " -- 我执行了!");
}
});
long start = System.currentTimeMillis();
thread.start(); // 异步的,开启一个线程后,并不会立马执行线程,而是扔给cpu进行调度,因此下面的内容继续执行,不受影响
System.out.println("耗时:"+( System.currentTimeMillis()-start));
start = System.currentTimeMillis();
thread.run(); // 同步的,必须执行完run方法块中的代码才能继续执行下面的内容(阻塞)
System.out.println("耗时:"+( System.currentTimeMillis()-start));
}
}
直接来看输出结果:
A、互斥使用(资源独占)
一个资源每次只能给一个线程使用
说明:对象锁只有一把,同一时间,只能有一个线程持有,其他线程需等待
B、不可强占(不可剥夺)
资源申请者不能强行的从资源占有者手中夺取资源,资源只能由占有者自愿释放
说明:当线程A先拿到对象锁时,线程B除了等待线程A主动释放该对象锁时,什么都干不了(想都别想)
C、请求和保持
一个线程在申请新的资源的同时保持对原有资源的占有
说明:线程A原本持有对象M的锁,但又想要申请获取对象N的锁,这时候,如果获得对象N的锁遇到阻塞时,就会导致线程A
原本持有的对象M的锁无法得到释放,这就导致其他想要获取对象M锁的线程陷入无限的等待中
D、循环等待
存在一个线程等待队列 {T1 , T2 , … , Tn},,其中T1等待T2释放占有的资源,T2等待T3释放占有的资源,…,Tn等待T1释放 占有的资源,形成一个线程等待环路
说明:A说B写的模块代码有问题,B说C写的模块代码有问题,C又反过来说是A写的不对,卧槽,这..... egg疼!
上述产生死锁的四个必要条件只要有一个不成立,就可以推翻或者排除出现死锁的可能,因此,我们在使用多线程开发程序之前,一定要好好设计和斟酌一下,防止写出来的程序在线程调度上出岔子,造成死锁就麻烦了,慎重!
什么是死锁:两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去 (摘自知乎)
前言:知道了死锁是什么后,接下来我们就针对死锁的特性,手写一段代码,模拟一下两个线程互相抢夺资源无果造成无限等待的场景;
package com.appleyk.dbinit.MyLock;
/**
* 手写死锁 -- 方式1
*
* @author appleyk
* @version V.1.0.1
* @blob https://blog.csdn.net/appleyk
* @date created on 上午 8:14 2019-7-1
*/
public class DeadLock1 {
public static void main(String[] args) {
final Object obj1 = new Object();
final Object obj2 = new Object();
MyThread1 thread1 = new MyThread1(obj1, obj2);
MyThread2 thread2 = new MyThread2(obj1, obj2);
// 开启多线程,模拟死锁
thread1.start();
thread2.start();
}
}
class MyThread1 extends Thread{
private final Object obj1 ;
private final Object obj2 ;
public MyThread1(Object obj1 , Object obj2) {
this.obj1 = obj1 ;
this.obj2 = obj2;
}
@Override
public void run() {
// 线程1获得obj1对象的锁(其他线程等待arr1对象锁释放)
synchronized(obj1){
System.out.println(Thread.currentThread().getName()+"--已获得对象obj1的锁,准备获得对象obj2的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 1、线程1获得obj2对象的锁(其他线程等待obj2对象锁释放)
* 2、如果其他线程在线程1之前先拿到obj2对象锁的话,线程1需等待
*/
synchronized(obj2){
System.out.println(Thread.currentThread().getName()+"--获得对象obj2的锁");
}
}
}
}
class MyThread2 extends Thread {
private final Object obj1;
private final Object obj2;
public MyThread2(Object obj1, Object obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
@Override
public void run() {
/**
* 1、线程2获得obj2对象的锁(其他线程等待obj2对象锁释放)
* 2、如果其他线程在线程2之前先拿到obj2对象锁的话,线程2需等待
*/
synchronized (obj2) {
System.out.println(Thread.currentThread().getName() + "--已获得对象obj2的锁,准备获得对象obj1的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 线程2获得obj1对象的锁(其他线程等待obj1对象锁释放)
*/
synchronized (obj1) {
System.out.println(Thread.currentThread().getName() + "--获得对象obj1的锁");
}
}
}
}
执行效果:
补充:这两个哥们也是挺逗的,我等你,你等我,谁都不肯把自己手里处理完的对象锁释放了给对方用,索性,咱俩就一直干等着呗,等到海枯石烂..... 等等,这代码可是我写的啊,为什么会这样呢?
package com.appleyk.dbinit.MyLock;
/**
* 手写死锁 -- 方式2
*
* @author appleyk
* @version V.1.0.1
* @blob https://blog.csdn.net/appleyk
* @date created on 下午 5:18 2019-7-1
*/
public class DeadLock2 {
public static void main(String[] args) {
Thread thread1 = new MyThread( 1 );
Thread thread2 = new MyThread( 0 );
thread1.start();
thread2.start();
}
}
class MyThread extends Thread{
private static final Object obj1 = new Object() ;
private static final Object obj2 = new Object();
private Integer flag = 1;
MyThread( Integer flag){
this.flag = flag;
}
@Override
public void run() {
if(1 == flag){
synchronized (obj1){
System.out.println(Thread.currentThread().getName()+"--已获得对象obj1的锁,准备获得对象obj2的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
// dosomething
System.out.println(Thread.currentThread().getName()+"-- 获得了对象obj2的锁");
}
}
}else {
synchronized (obj2){
System.out.println(Thread.currentThread().getName()+"--已获得对象obj2的锁,准备获得对象obj1的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
// dosomething
System.out.println(Thread.currentThread().getName()+"-- 获得了对象obj1的锁");
}
}
}
}
}
效果(和第一种方式一样,这不废话吗):
package com.appleyk.dbinit.MyLock;
/**
* 手写死锁 -- 方式3
*
* @author appleyk
* @version V.1.0.1
* @blob https://blog.csdn.net/appleyk
* @date created on 下午 4:30 2019-7-1
*/
public class DeadLock3 {
static final Object obj1 = new Object();
static final Object obj2 = new Object();
public static void main(String[] args) {
deadLock();
}
private static void deadLock(){
new Thread(new Runnable() {
@Override
public void run() {
synchronized(obj1){
System.out.println(Thread.currentThread().getName()+"--已获得对象obj1的锁,准备获得对象obj2的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println(Thread.currentThread().getName()+"--获得对象obj2的锁");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized(obj2){
System.out.println(Thread.currentThread().getName()+"--已获得对象obj2的锁,准备获得对象obj1的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println(Thread.currentThread().getName()+"-获得对象obj1的锁");
}
}
}
}).start();
}
}
效果同上(还是贴图吧,省得有些人有强迫症,说我没测试过就贴代码):
(1)连接主进程
(2)查看&检测
(1)JPS查看当前运行着的Java进程
(2)jstack检测指定pid是否发生死锁
上图中,我们可以很明显的看出,Thread-1在等待获得对象<0x00...76b27dc78>的lock,但是对象<0x00...76b27dc78>的锁却被Thread-2所持有(被locked了),因为此情况满足产生死锁的条件,所以,我们最后可以看到检测的结果:Found 1 deadlock.
关于并发,必然要提到锁,关于锁,还是有很多要讲的,本篇只是浅显的介绍了同步锁和死锁,关于锁的设计思想和类型,还有很多很多要说道的地方,由于博主也是在不断的充电中,所以后续的内容博主会慢慢补充,比如什么是CAS啊,什么是AQS啊....等等等