进程:每个进程都有独立的代码和数据空间,进程间的切换回有较大的开销,一个进程包含1—n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器,切换开销小。(线程是CPU调度的最小单位)
线程三大特性:可见性(Volatile保证可见但不保证原子性)、原子性(同步)、有序性。
在java中,每次程序运行至少启动2个线程,一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际就是在操作系统中启动了一个线程。
随机性原理:由CPU的快速切换造成,哪个线程获取到了CPU的执行权,哪个线程就执行。
返回当前线程的名称:Thread.currentThread().getName();
线程的名称:Thread-编号(编号从0开始)
线程要运行的代码统一存放在了run方法中。
Thread类本质上是实现了Runnable接口的一个实例,它代表一个线程的实例。并且,启动线程的唯一方法就是通过Thread类的start()方法,这个方法是一个native方法,它将启动一个新线程,并执行run()方法。
public class ThreadDemo extends Thread {
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
ThreadDemo t1=new ThreadDemo();
ThreadDemo t2=new ThreadDemo();
t1.start();
t2.start();
}
}
注意:通过Thread类的方式,可以完成多线程的建立,但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承thread类,因为java单继承的局限性。
Runnable接口将线程要执行的任务封装成了对象, 实现Runnable接口可以避免单继承的局限性。
public class ThreadDemo implements Runnable {
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
Thread t1=new Thread(new ThreadDemo());
Thread t2=new Thread(new ThreadDemo());
t1.start();
t2.start();
}
}
前两种方式线程执行完后都没有返回值,只有这种是带返回值的。无返回值得任务必须实现Runnable接口,类似的,可返回值的任务必须实现Callable接口。执行Callable任务后,可以获取一个Future对象,在该对象上调用get就可已获取到Callable任务返回的Object了,再结合线程池接口就可以实现返回有结果的多线程。
@SuppressWarnings("unchecked")
public class Test4 {
@SuppressWarnings("rawtypes")
public static void main(String[] args) throws Exception, ExecutionException {
System.out.println("——程序开始运行——");
Date date1 = new Date();
int taskSize = 5;
ExecutorService executorService = Executors.newFixedThreadPool(taskSize);
List list = new ArrayList();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
Future f = executorService.submit(c);
list.add(f);
}
executorService.shutdown();
for (Future f : list) {
System.out.println(">>>" + f.get().toString());
}
Date date2 = new Date();
System.out.println("——程序结束运行——,程序运行时间【" + (date2.getTime() - date1.getTime() + "毫秒】"));
}
}
class MyCallable implements Callable
运行结果为:
——程序开始运行——
>>>0 任务启动
>>>1 任务启动
>>>2 任务启动
>>>4 任务启动
>>>3 任务启动
>>>0 任务终止
>>>0 任务返回运行结果,当前任务时间【1000毫秒】
>>>2 任务终止
>>>1 任务终止
>>>1 任务返回运行结果,当前任务时间【1000毫秒】
>>>2 任务返回运行结果,当前任务时间【1000毫秒】
>>>4 任务终止
>>>3 任务终止
>>>3 任务返回运行结果,当前任务时间【1000毫秒】
>>>4 任务返回运行结果,当前任务时间【1000毫秒】
——程序结束运行——,程序运行时间【1056毫秒】
线程生命周期图如下:
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
1、java优先级用整数表示,取值范围1~10,thread类有以下三个静态常量:
1)static int MAX_PRIORITY //最高优先级,取值为10
2)static int MIN_PRIORITY //最低优先级,取值为1
3)static int NORM_PRIORITY //默认优先级,取值为5
2、Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
3、每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级
interrupt():不要以为它是中断某个线程,它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的。
public class Test2 {
public static void main(String[] args) {
Thread t1=new Thread(new Aaa());
Thread t2=new Thread(new Aaa());
t1.setName("1:");
t1.setPriority(Thread.MAX_PRIORITY);
System.out.println(t1.getPriority());
//t2.setPriority(1);
t1.start();
try {
//t1.join();
t1.join();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
t2.start();
System.out.println("开始休眠---");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("休眠后---");
}
}
class Aaa implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。
原因:当多个线程共享同一个全局变量,做写的时候,可能会受到其他线程的干扰,导致数据有问题。(做读的时候,不会产生线程安全问题)
解决:同步。(保证数据原子性)
1、同步方法
即用synchronized关键字修饰的方法。由于java的每个对象都有一个this锁,当用此关键字修饰方法时,this锁会保护整个方法。在调用该方法前,需要获得this锁,否则就处于阻塞状态。
public synchronized void save(){
}
注意:synchronized也可以修饰static,对象是 类名.class,锁住的是整个类。因为static随类的加载而加载,并且优于对象存在,所以可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经封装成了对象,这个对象就是字节码文件对象。所以静态加载时,只有一个对象存在,静态同步方法使用的就是这个对象。2、同步代码块
同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。/**
*
*
* Title: 多线程模拟购票窗口
*
* Description: 熟练同步代码块和同步函数的使用
*
* @author Marmara
*
* @date 2018年5月11日
*/
public class TicketSale implements Runnable {
private int tc = 100;
// private Object o1=new Object();
public static void main(String[] args) {
TicketSale t = new TicketSale();
Thread t1 = new Thread(t, "一号窗口");
Thread t2 = new Thread(t, "二号窗口");
t1.start();
t2.start();
}
@Override
public void run() {
while (tc > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sale();
}
}
public synchronized void sale() {
if (tc > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (100 - tc + 1) + "张票。");
tc--;
}
}
}
运行结果:
二号窗口正在出售第:1张票。
一号窗口正在出售第:2张票。
一号窗口正在出售第:3张票。
二号窗口正在出售第:4张票。
一号窗口正在出售第:5张票。
二号窗口正在出售第:6张票。
一号窗口正在出售第:7张票。
二号窗口正在出售第:8张票。
一号窗口正在出售第:9张票。
二号窗口正在出售第:10张票。
二号窗口正在出售第:11张票。
一号窗口正在出售第:12张票。
一号窗口正在出售第:13张票。
二号窗口正在出售第:14张票。
二号窗口正在出售第:15张票。
一号窗口正在出售第:16张票。
二号窗口正在出售第:17张票。
一号窗口正在出售第:18张票。
二号窗口正在出售第:19张票。
一号窗口正在出售第:20张票。
二号窗口正在出售第:21张票。
一号窗口正在出售第:22张票。
二号窗口正在出售第:23张票。
一号窗口正在出售第:24张票。
二号窗口正在出售第:25张票。
一号窗口正在出售第:26张票。
一号窗口正在出售第:27张票。
二号窗口正在出售第:28张票。
二号窗口正在出售第:29张票。
一号窗口正在出售第:30张票。
一号窗口正在出售第:31张票。
二号窗口正在出售第:32张票。
二号窗口正在出售第:33张票。
一号窗口正在出售第:34张票。
一号窗口正在出售第:35张票。
二号窗口正在出售第:36张票。
二号窗口正在出售第:37张票。
一号窗口正在出售第:38张票。
一号窗口正在出售第:39张票。
二号窗口正在出售第:40张票。
一号窗口正在出售第:41张票。
二号窗口正在出售第:42张票。
二号窗口正在出售第:43张票。
一号窗口正在出售第:44张票。
二号窗口正在出售第:45张票。
一号窗口正在出售第:46张票。
一号窗口正在出售第:47张票。
二号窗口正在出售第:48张票。
一号窗口正在出售第:49张票。
二号窗口正在出售第:50张票。
二号窗口正在出售第:51张票。
一号窗口正在出售第:52张票。
二号窗口正在出售第:53张票。
一号窗口正在出售第:54张票。
一号窗口正在出售第:55张票。
二号窗口正在出售第:56张票。
一号窗口正在出售第:57张票。
二号窗口正在出售第:58张票。
一号窗口正在出售第:59张票。
二号窗口正在出售第:60张票。
一号窗口正在出售第:61张票。
二号窗口正在出售第:62张票。
二号窗口正在出售第:63张票。
一号窗口正在出售第:64张票。
一号窗口正在出售第:65张票。
二号窗口正在出售第:66张票。
二号窗口正在出售第:67张票。
一号窗口正在出售第:68张票。
一号窗口正在出售第:69张票。
二号窗口正在出售第:70张票。
二号窗口正在出售第:71张票。
一号窗口正在出售第:72张票。
一号窗口正在出售第:73张票。
二号窗口正在出售第:74张票。
二号窗口正在出售第:75张票。
一号窗口正在出售第:76张票。
一号窗口正在出售第:77张票。
二号窗口正在出售第:78张票。
一号窗口正在出售第:79张票。
二号窗口正在出售第:80张票。
一号窗口正在出售第:81张票。
二号窗口正在出售第:82张票。
一号窗口正在出售第:83张票。
二号窗口正在出售第:84张票。
一号窗口正在出售第:85张票。
二号窗口正在出售第:86张票。
二号窗口正在出售第:87张票。
一号窗口正在出售第:88张票。
一号窗口正在出售第:89张票。
二号窗口正在出售第:90张票。
一号窗口正在出售第:91张票。
二号窗口正在出售第:92张票。
一号窗口正在出售第:93张票。
二号窗口正在出售第:94张票。
二号窗口正在出售第:95张票。
一号窗口正在出售第:96张票。
一号窗口正在出售第:97张票。
二号窗口正在出售第:98张票。
二号窗口正在出售第:99张票。
一号窗口正在出售第:100张票。
注意:同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
3、使用特殊域变量(volatile)实现线程同步
volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新, 因此每次使用该域就要重新计算,而不是使用寄存器中的值volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。例如:在上面的例子当中,只需在tc前面加上volatile修饰,即可实现线程同步。
/**
*
* Title: 多线程模拟购票窗口
* Description: 熟练同步代码块和同步函数的使用
* @author Marmara
* @date 2018年5月11日
*/
public class TicketSale implements Runnable{
private volatile int tc=100;
//private Object o1=new Object();
public static void main(String[] args) {
TicketSale t=new TicketSale();
Thread t1=new Thread(t, "一号窗口");
Thread t2=new Thread(t, "二号窗口");
t1.start();
t2.start();
}
@Override
public void run() {
while(tc>0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sale();
}
}
public /*synchronized*/ void sale() {
if(tc>0) {
System.out.println(Thread.currentThread().getName()+"正在出售第:"+(100-tc+1)+"张票。");
tc--;
}
}
}
运行结果:
一号窗口正在出售第:1张票。
二号窗口正在出售第:1张票。
一号窗口正在出售第:3张票。
二号窗口正在出售第:3张票。
二号窗口正在出售第:5张票。
一号窗口正在出售第:5张票。
一号窗口正在出售第:7张票。
二号窗口正在出售第:8张票。
二号窗口正在出售第:9张票。
一号窗口正在出售第:9张票。
二号窗口正在出售第:11张票。
一号窗口正在出售第:11张票。
一号窗口正在出售第:13张票。
二号窗口正在出售第:13张票。
二号窗口正在出售第:15张票。
一号窗口正在出售第:15张票。
一号窗口正在出售第:17张票。
二号窗口正在出售第:17张票。
二号窗口正在出售第:19张票。
一号窗口正在出售第:19张票。
一号窗口正在出售第:21张票。
二号窗口正在出售第:21张票。
一号窗口正在出售第:23张票。
二号窗口正在出售第:23张票。
一号窗口正在出售第:25张票。
二号窗口正在出售第:25张票。
二号窗口正在出售第:27张票。
一号窗口正在出售第:27张票。
一号窗口正在出售第:29张票。
二号窗口正在出售第:29张票。
一号窗口正在出售第:31张票。
二号窗口正在出售第:31张票。
二号窗口正在出售第:33张票。
一号窗口正在出售第:34张票。
一号窗口正在出售第:35张票。
二号窗口正在出售第:35张票。
一号窗口正在出售第:37张票。
二号窗口正在出售第:37张票。
一号窗口正在出售第:39张票。
二号窗口正在出售第:40张票。
二号窗口正在出售第:41张票。
一号窗口正在出售第:41张票。
一号窗口正在出售第:43张票。
二号窗口正在出售第:43张票。
一号窗口正在出售第:45张票。
二号窗口正在出售第:45张票。
一号窗口正在出售第:47张票。
二号窗口正在出售第:47张票。
一号窗口正在出售第:49张票。
二号窗口正在出售第:49张票。
一号窗口正在出售第:51张票。
二号窗口正在出售第:51张票。
二号窗口正在出售第:53张票。
一号窗口正在出售第:54张票。
一号窗口正在出售第:55张票。
二号窗口正在出售第:55张票。
一号窗口正在出售第:57张票。
二号窗口正在出售第:57张票。
一号窗口正在出售第:59张票。
二号窗口正在出售第:59张票。
二号窗口正在出售第:61张票。
一号窗口正在出售第:61张票。
二号窗口正在出售第:63张票。
一号窗口正在出售第:63张票。
一号窗口正在出售第:65张票。
二号窗口正在出售第:65张票。
一号窗口正在出售第:67张票。
二号窗口正在出售第:68张票。
一号窗口正在出售第:69张票。
二号窗口正在出售第:69张票。
一号窗口正在出售第:71张票。
二号窗口正在出售第:71张票。
二号窗口正在出售第:73张票。
一号窗口正在出售第:73张票。
一号窗口正在出售第:75张票。
二号窗口正在出售第:75张票。
二号窗口正在出售第:77张票。
一号窗口正在出售第:77张票。
二号窗口正在出售第:79张票。
一号窗口正在出售第:79张票。
二号窗口正在出售第:81张票。
一号窗口正在出售第:81张票。
二号窗口正在出售第:83张票。
一号窗口正在出售第:84张票。
二号窗口正在出售第:85张票。
一号窗口正在出售第:85张票。
二号窗口正在出售第:87张票。
一号窗口正在出售第:87张票。
一号窗口正在出售第:89张票。
二号窗口正在出售第:89张票。
二号窗口正在出售第:91张票。
一号窗口正在出售第:91张票。
一号窗口正在出售第:93张票。
二号窗口正在出售第:93张票。
一号窗口正在出售第:95张票。
二号窗口正在出售第:95张票。
一号窗口正在出售第:97张票。
二号窗口正在出售第:98张票。
二号窗口正在出售第:99张票。
一号窗口正在出售第:99张票。
这时发现运行结果很乱,因为volatile不能保证原子操作导致的,因此volatile不能代替synchronized。此外volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。它的原理是每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。
注意:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。 用final域,有锁保护的域和volatile域可以避免非同步的问题。
4、使用重入锁实现线程同步
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其功能。
ReenreantLock类的常用方法有:unlock() : 释放锁
package test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
*
* Title: 多线程模拟购票窗口
*
* Description: 熟练同步代码块和同步函数的使用
*
* @author Marmara
*
* @date 2018年5月11日
*/
public class TicketSale implements Runnable {
private /* volatile */ int tc = 100;
private Lock lock = new ReentrantLock();// 声明锁
// private Object o1=new Object();
public static void main(String[] args) {
TicketSale t = new TicketSale();
Thread t1 = new Thread(t, "一号窗口");
Thread t2 = new Thread(t, "二号窗口");
t1.start();
t2.start();
}
@Override
public void run() {
while (tc > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sale();
}
}
public /* synchronized */ void sale() {
lock.lock();
try {
if (tc > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第:" + (100 - tc + 1) + "张票。");
tc--;
}
} finally {
lock.unlock();
}
}
}
运行结果:
一号窗口正在出售第:1张票。
二号窗口正在出售第:2张票。
一号窗口正在出售第:3张票。
二号窗口正在出售第:4张票。
二号窗口正在出售第:5张票。
一号窗口正在出售第:6张票。
二号窗口正在出售第:7张票。
一号窗口正在出售第:8张票。
一号窗口正在出售第:9张票。
二号窗口正在出售第:10张票。
二号窗口正在出售第:11张票。
一号窗口正在出售第:12张票。
一号窗口正在出售第:13张票。
二号窗口正在出售第:14张票。
一号窗口正在出售第:15张票。
二号窗口正在出售第:16张票。
二号窗口正在出售第:17张票。
一号窗口正在出售第:18张票。
二号窗口正在出售第:19张票。
一号窗口正在出售第:20张票。
二号窗口正在出售第:21张票。
一号窗口正在出售第:22张票。
一号窗口正在出售第:23张票。
二号窗口正在出售第:24张票。
二号窗口正在出售第:25张票。
一号窗口正在出售第:26张票。
二号窗口正在出售第:27张票。
一号窗口正在出售第:28张票。
一号窗口正在出售第:29张票。
二号窗口正在出售第:30张票。
二号窗口正在出售第:31张票。
一号窗口正在出售第:32张票。
一号窗口正在出售第:33张票。
二号窗口正在出售第:34张票。
二号窗口正在出售第:35张票。
一号窗口正在出售第:36张票。
二号窗口正在出售第:37张票。
一号窗口正在出售第:38张票。
二号窗口正在出售第:39张票。
一号窗口正在出售第:40张票。
二号窗口正在出售第:41张票。
一号窗口正在出售第:42张票。
二号窗口正在出售第:43张票。
一号窗口正在出售第:44张票。
一号窗口正在出售第:45张票。
二号窗口正在出售第:46张票。
二号窗口正在出售第:47张票。
一号窗口正在出售第:48张票。
一号窗口正在出售第:49张票。
二号窗口正在出售第:50张票。
一号窗口正在出售第:51张票。
二号窗口正在出售第:52张票。
二号窗口正在出售第:53张票。
一号窗口正在出售第:54张票。
二号窗口正在出售第:55张票。
一号窗口正在出售第:56张票。
一号窗口正在出售第:57张票。
二号窗口正在出售第:58张票。
二号窗口正在出售第:59张票。
一号窗口正在出售第:60张票。
二号窗口正在出售第:61张票。
一号窗口正在出售第:62张票。
一号窗口正在出售第:63张票。
二号窗口正在出售第:64张票。
二号窗口正在出售第:65张票。
一号窗口正在出售第:66张票。
二号窗口正在出售第:67张票。
一号窗口正在出售第:68张票。
二号窗口正在出售第:69张票。
一号窗口正在出售第:70张票。
二号窗口正在出售第:71张票。
一号窗口正在出售第:72张票。
二号窗口正在出售第:73张票。
一号窗口正在出售第:74张票。
二号窗口正在出售第:75张票。
一号窗口正在出售第:76张票。
一号窗口正在出售第:77张票。
二号窗口正在出售第:78张票。
一号窗口正在出售第:79张票。
二号窗口正在出售第:80张票。
一号窗口正在出售第:81张票。
二号窗口正在出售第:82张票。
一号窗口正在出售第:83张票。
二号窗口正在出售第:84张票。
二号窗口正在出售第:85张票。
一号窗口正在出售第:86张票。
一号窗口正在出售第:87张票。
二号窗口正在出售第:88张票。
一号窗口正在出售第:89张票。
二号窗口正在出售第:90张票。
二号窗口正在出售第:91张票。
一号窗口正在出售第:92张票。
一号窗口正在出售第:93张票。
二号窗口正在出售第:94张票。
一号窗口正在出售第:95张票。
二号窗口正在出售第:96张票。
二号窗口正在出售第:97张票。
一号窗口正在出售第:98张票。
一号窗口正在出售第:99张票。
二号窗口正在出售第:100张票。
注意:如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁。 5.使用局部变量实现线程同步
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal 类的常用方法:
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
private static ThreadLocal tc=new ThreadLocal() {
protected Integer initialValue(){
return 100;
}
};
注意:ThreadLocal与同步机制
ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式 。
同步中嵌套同步,互相不释放。
举例如下:
package test;
/**
*
* Title: DeadLock
*
* Description: 死锁问题
*
* @author Marmara
*
* @date 2018年5月4日
*/
public class DeadLock implements Runnable {
public int flag = 1;
private static Object o1 = new Object();// 静态对象是类的所有对象共享的
private static Object o2 = new Object();
@Override
public void run() {
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
public static void main(String[] args) {
DeadLock d1 = new DeadLock();
DeadLock d2 = new DeadLock();
d1.flag = 1;
d2.flag = 0;
new Thread(d1).start();
new Thread(d2).start();
}
}
参考原文地址:http://www.cnblogs.com/yezhenhan/archive/2012/01/09/2317636.html
https://www.cnblogs.com/yjd_hycf_space/p/7526608.html
https://blog.csdn.net/cengjingyige/article/details/52382300