- 程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
- 进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。
- 线程
- 线程由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程
- 并行:多个CPU同一时刻执行多个任务
- 并发:一个CPU同一时间段(采用时间片)同时执行多个任务
- 注意:对于一个多核的电脑,并发和并行是可以同时存在的
案例引入
提出需求:
1.编写程序,开启一个线程,该线程每隔1秒,在控制台输出"猫咪学习java中"
2.当输出80次"猫咪学习java中"时结束该进程
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
|
package com.zjh;
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
new Cat().start();
System.out.println("主线程继续执行 "+Thread.currentThread().getName());
for (int i = 0; i < 5; i++) {
System.out.println("主线程 i="+i);
//让主线程休眠1秒
Thread.sleep(1000);
}
}
}
class Cat extends Thread{
@Override
public void run() {
int times = 0;
while(true){
System.out.println("小猫咪" + (++times) + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times == 7) break;
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
主线程继续执行 main
主线程 i=0
小猫咪1Thread-0
主线程 i=1
小猫咪2Thread-0
主线程 i=2
小猫咪3Thread-0
主线程 i=3
小猫咪4Thread-0
主线程 i=4
小猫咪5Thread-0
小猫咪6Thread-0
小猫咪7Thread-0
Process finished with exit code 0
|
以cat案例为例

当我们运行程序时,就相当于启动了一个进程,然后,程序马上会进入我们的main方法中,进入main方法后,这时进程就开启了一个主线程叫做main线程。在这个主线程中,我们创建了一个Cat对象,由于这个Cat对象继承了Thread类,所以我们可以将其当作线程使用,因此当我们调用cat.start();时,我们的主线程就也创建了一个子线程叫做Thread-0线程,并且不会导致主线程阻塞(即主线程不会等到cat.start();方法执行完毕后,才继续执行后面代码)
如果我们运行程序,就会看到主线程和Thread-0线程交替执行,直到某一个线程先消亡,系统才会只执行那个未消亡的线程(但此时应用程序(进程)并未结束,只有所有线程都消亡了,我们的应用程序(进程)才会结束)
start才能启动线程
run只是main主线程调用了Cat对象中的run方法,并没有开启一个新的线程,此时会引发阻塞现象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
小猫咪1main
小猫咪2main
小猫咪3main
小猫咪4main
小猫咪5main
小猫咪6main
小猫咪7main
主线程继续执行 main
主线程 i=0
主线程 i=1
主线程 i=2
主线程 i=3
主线程 i=4
Process finished with exit code 0
|
start
- 作用:1.启动当前线程 2.调用当前线程的重写的run方法
- 调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。
run
- 在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。
- 调用线程中的run方法,只调用run方法,并不新开线程
1.当我们调用cat.start();时,系统会进入public synchronized void start() {} 这个方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public synchronized void start() {
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
|
核心代码:start0();
2.接着public synchronized void start() {} 这个方法会调用其中的核心方法start0();
start0();是native方法,即本地方法,由JVM调用,底层由c/c++实现,因此真正实现多线程效果的是start0()方法,而不是run()方法
1
2
3
4
5
6
7
|
private native void start0();
@Override
public void run() {
if (target != null) {
target.run();
}
}
|
由于Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类来创建线程是不可能的了,因此Java设计者们提供了实现Runnable接口的方法来创建线程
案例演示
提出需求:
请编写程序,该程序可以每隔1秒,在控制台输出"hi",当输出10次后,自动退出
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
|
package com.zjh;
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
Thread thread = new Thread(dog);
thread.start();
}
}
class Dog implements Runnable{
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("狗叫" + (++count) + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
|
注意:
- 开启Runnable实现的线程不可以用对象名.start(),Runnable接口中只有run方法
- 不可以对象名.run(),这样就是等于和前面讲的一样,用main主线程直接调用方法,而不是开启线程
- 解决方法:创建Thread对象,把我们需要当作线程的对象(实现Runnable接口)放入Thread中然后再通过调用Thread对象中的start()方法完成线程的创建【代理模式】

代理简析:把Dog对象注入到Thread中的target
调用thread.start()实则本地方法调用start0()再调用target.run()方法执行注入进来的dog对象借口里的run方法代码体
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
|
package com.hspedu.threaduse;
/**
* @author 韩顺平
* @version 1.0
* 通过实现接口Runnable 来开发线程
*/
public class Thread02 {
public static void main(String[] args) {
Tiger tiger = new Tiger();//实现了 Runnable
ThreadProxy threadProxy = new ThreadProxy(tiger);
threadProxy.start();
}
}
class Animal {
}
class Tiger extends Animal implements Runnable {
@Override
public void run() {
System.out.println("老虎嗷嗷叫....");
}
}
//线程代理类 , 模拟了一个极简的Thread类
class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy
private Runnable target = null;//属性,类型是 Runnable
@Override
public void run() {
if (target != null) {
target.run();//动态绑定(运行类型Tiger)
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start() {
start0();//这个方法时真正实现多线程方法
}
public void start0() {
run();
}
}
|
- 从java设计角度看是没区别,Thread类实现了Runnable接口
- Runnable接口更适合多线程共享一个资源的情况,避免单继承的限制
特点
- call方法可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
实现方法
- 创建一个实现callable的实现类
- 实现call方法,将此线程需要执行的操作声明在call()中
- 创建callable实现类的对象
- 将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动(通过FutureTask的对象调用方法get获取线程中的call的返回值)
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
|
//实现callable接口的call方法
class NumThread implements Callable{
private int sum=0;//
//可以抛出异常
@Override
public Object call() throws Exception {
for(int i = 0;i<=100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args){
//new一个实现callable接口的对象
NumThread numThread = new NumThread();
//通过futureTask对象的get方法来接收futureTask的值
FutureTask futureTask = new FutureTask(numThread);
Thread t1 = new Thread(futureTask);
t1.setName("线程1");
t1.start();
try {
//get返回值即为FutureTask构造器参数callable实现类重写的call的返回值
Object sum = futureTask.get();
System.out.println(Thread.currentThread().getName()+":"+sum);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
|
- setName //设置线程名称,使之与参数name相同
- getName //返回该线程的名称
- start /使该线程开始执行; Java虚拟机底层调用该线程的start0方法
- run //直接调用线程对象run方法;
- setPriority //更改线程的优先级
- getPriority //获取线程的优先级
- sleep
- 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
- 运行态-->阻塞(就绪)态
- 单位是ms 1s=1000ms
- interrupt //中断线程
- 相当于唤醒操作
- 休眠态-->就绪态
- stop停止
线程进行一般有一个标志量flag while(flag==true)进行
stop的原理就是 flag等于false终止循环
- yiled礼让
- 会让运行中的线程切换到就绪状态,重新争抢cpu的时间片,争抢时是否获取到时间片看cpu的分配。
- 如果此时cpu资源多的话,yield可能没效果
- join
- 待指定线程执行完成之后,再执行其他线程,(其他线程被阻塞,等待这个线程先执行)
- 强行插队,一定会插队成功
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
|
package com.zjh;
public class Thread03 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("主" + "吃了第" + i + "个包子");
Thread.sleep(1000);
if(i == 5) {
System.out.println("子线程先执行");
// t.join();
Thread.yield();
}
}
}
}
class T extends Thread{
@Override
public void run() {
for (int j = 0; j < 100; j++) {
System.out.println("子" + "吃了第" + j + "个包子");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
|
线程分为用户线程(如main)和守护线程(如gc)
守护线程,当所有的非守护线程都结束后,即使它没有执行完,也会强制结束。一般是为工作线程服务的
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
|
package com.zjh;
public class Thread04 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.setDaemon(true);
t2.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程工作" + i);
Thread.sleep(1000);
}
}
}
class T2 extends Thread{
@Override
public void run() {
for (; ;) {
System.out.println("守护线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
|
一旦main线程结束,T2线程不再工作


- NEW 线程对象被创建
- Runnable 线程调用了start()方法后进入该状态,该状态包含了三种情况
- 就绪状态 :等待cpu分配时间片
- 运行状态:进入Runnable方法执行任务
- Blocked 没获取到锁时的阻塞状态
- WAITING 调用wait()、join()等方法后的状态
- TIMED_WAITING 调用 sleep(time)、wait(time)、join(time)等方法后的状态
- TERMINATED 线程执行完成或抛出异常后的状态

- 初始状态:创建线程对象时的状态
- 可运行状态(就绪状态):调用start()方法后进入就绪状态,也就是准备好被cpu调度执行
- 运行状态:线程获取到cpu的时间片,执行run()方法的逻辑
- 阻塞状态: 线程被阻塞,放弃cpu的时间片,等待解除阻塞重新回到就绪状态争抢时间片
- 终止状态: 线程执行完成或抛出异常后的状态
局限性:导致程序的执行效率要降低
线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据。
解释:当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式有个高尚的名称叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后。
保证在任何一个时刻,只能有一个线程访问该对象。
synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能)
- 修饰非静态方法,作用于当前实例加锁,默认锁对象为this
- 修饰代码块,指定加锁对象,对给定对象加锁
- 修饰静态方法,作用于当前类对象加锁,默认锁对象:当前类.class
注意:用synchronized修饰实例对象或者代码块时,多个线程需要访问的是同一个实例对象(一把锁),否则仍然线程不安全,此时就应该使用的是静态方法(修饰类对象)
ReentrantLock实现Lock接口
synchronized和ReentrantLock都是可重入锁
可重入锁:可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class)
ReentrantLock的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
|
private static final ReentrantLock LOCK = new ReentrantLock();
private static void m() {
LOCK.lock();
try {
log.info("begin");
// 调用m1()
m1();
} finally {
// 注意锁的释放
LOCK.unlock();
}
}
|
锁的部分放在try中
锁的释放放在finally中
基本需求:3个售票口一起卖10张票
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
|
package com.zjh;
public class Ticket {
public static void main(String[] args) {
SellTicket sellTicket = new SellTicket();
new Thread(sellTicket).start();
new Thread(sellTicket).start();
new Thread(sellTicket).start();
}
}
//Runnable方式
class SellTicket implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
break;
}
//休眠50毫秒, 交叉运行
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
}
}
|

解决:加锁 synchronized
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
|
package com.zjh;
public class Ticket {
public static void main(String[] args) {
SellTicket sellTicket = new SellTicket();
Thread thread = new Thread(sellTicket);
Thread thread1 = new Thread(sellTicket);
Thread thread2 = new Thread(sellTicket);
thread.setName("0");
thread1.setName("1");
thread2.setName("2");
thread.start();
thread1.start();
thread2.start();
}
}
//Runnable方式
class SellTicket implements Runnable {
private static int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public void run() {
while (ticketNum > 0) {
sell();
}
}
public synchronized void sell(){
if (ticketNum <= 0) {
System.out.println("售票结束...");
return;
}
//休眠50毫秒, 交叉运行
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
}
|
注意不能直接在run方法上加synchronized
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
|
package com.zjh;
public class Ticket {
public static void main(String[] args) {
//Runnable方式
SellTicket sellTicket = new SellTicket();
Thread thread = new Thread(sellTicket);
Thread thread1 = new Thread(sellTicket);
Thread thread2 = new Thread(sellTicket);
thread.setName("0");
thread1.setName("1");
thread2.setName("2");
thread.start();
thread1.start();
thread2.start();
}
}
//Runnable方式
class SellTicket implements Runnable {
private static int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public void run() {
while (ticketNum > 0) {
synchronized (this){
if (ticketNum <= 0) {
System.out.println("售票结束...");
return;
}
//休眠50毫秒, 交叉运行
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
}
}
}
|
注意:对于Runnable实现方式,只要在创建线程时new一个SellTicket类,然后在几个Thread类中都放同一个SellTicket类,就可以保证锁的唯一性,因此同步代码块中的synchronized可以直接放this,同步方法也可以使用非静态,而对于Thread实现方式则有所不同
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
|
package com.zjh;
public class Ticket {
public static void main(String[] args) {
//Thread方式
new SellTicket0().start();
new SellTicket0().start();
new SellTicket0().start();
}
}
//Thread方式
class SellTicket0 extends Thread{
private static int ticketNum = 100;//让多个线程共享 ticketNum
private static Object object = new Object();
@Override
public void run() {
while (ticketNum > 0) {
sell();
}
}
public synchronized static void sell(){
if (ticketNum <= 0) {
System.out.println("售票结束...");
return;
}
//休眠50毫秒, 交叉运行
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
}
|
注意:一定要使用synchronized static锁同步方法,即锁整个class对象,因为Thread方式实现方式是new三个不同的SellTicket0对象,必须用static保证SellTicket0对象的唯一性
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
|
package com.zjh;
public class Ticket {
public static void main(String[] args) {
//Thread方式
new SellTicket0().start();
new SellTicket0().start();
new SellTicket0().start();
}
}
//Thread方式
class SellTicket0 extends Thread{
private static int ticketNum = 100;//让多个线程共享 ticketNum
private static Object object = new Object();
@Override
public void run() {
while (ticketNum > 0) {
synchronized (object){
if (ticketNum <= 0) {
System.out.println("售票结束...");
return;
}
//休眠50毫秒, 交叉运行
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
}
}
}
|
注意:在此使用了一个private static Object object放进synchronized锁的对象,这样才可以保证锁的唯一性
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生。
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
|
package xianchen06;
public class DeadLock {
public static void main(String[] args) {
DeadLockDemo a1 = new DeadLockDemo(true);
DeadLockDemo a2 = new DeadLockDemo(false);
a1.start();
a2.start();
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object(); //保证多线程,共享一个对象,这里使用static
static Object o2= new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
public void run() {
//1.如果为flag为 T,线程A就会先得到/持有01对象锁,然后尝试获取o2对象锁
//2如果线程a1得不到02,就会Blocked
//1.如果为flag为 F,线程B就会先得到/持有02对象锁,然后尝试获取o1对象锁
//2如果线程B得不到01,就会Blocked
if (flag) {
synchronized (o1) {
System.out.println(Thread.currentThread().getName()+" 进入1");
synchronized (o2) { //这里获得li对象的监视权
System.out.println(Thread.currentThread().getName()+" 进入2");
}
}
}
else{
synchronized (o2) {
System.out.println(Thread.currentThread().getName()+" 进入3");
synchronized (o1) { //这里获得li对象的监视权
System.out.println(Thread.currentThread().getName()+" 进入4");
}
}
}
}
}
|
产生死锁的必要条件:
- 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
- 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
- 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。
等待/通知机制,是指线程A调用了对象O的wait()方法进入等待状态,而线程B调用了对象O的notify()/notifyAll()方法,线程A收到通知后退出等待队列,进入可运行状态,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()方法和notify()/notifyAll()方法的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
- wait():使调用该方法的线程释放共享资源锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。 【进入WAITING】
- wait(long):超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。【进入TIMED_WAITING】
- wait(long,int):对于超时时间更细力度的控制,单位为纳秒。【进入TIMED_WAITING】
- notify():随机唤醒等待队列中等待同一共享资源的一个线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知一个线程。
- notifyAll():使所有正在等待队列中等待同一共享资源的全部线程退出等待队列,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。(有多个线程需要用时推荐使用)
注意:
- wait使当前线程阻塞,前提是必须先获得锁,所以只能在synchronized锁范围内里使用wait、notify/notifyAll方法,而sleep可以在任何地方使用。
- notify和wait的顺序不能错,只能notify被已经wait的线程
- 当线程执行sleep方法时,不会释放当前的锁(如果当前线程进入了同步锁),也不会让出CPU。sleep(milliseconds)可以用指定时间使它自动唤醒过来,如果时间不到只能调用interrupt方法强行打断。
- 当线程执行wait方法时,会释放当前的锁,然后让出CPU,进入WAITING状态。只有当notify/notifyAll被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized代码块的代码或是中途遇到wait() ,再次释放锁。
需求:生产100只鸡,最多库存是10只鸡,边生产边消费
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
package com.zjh;
//解决线程同步,管程法
/*
生产者只管生产
消费者只管消费
鸡: 实体类
容器 :
容器添加数据.
要判断容器是否满 , 满了等待消费者消费
没有满,生产者生产,通知消费者消费
容器减少数据
判断还有没有数据, 没有数据的话,等待生产者生产
消费完毕,通知生产者生产
*/
public class PV_tube {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Productor(synContainer).start();
new Consumer(synContainer).start();
}
}
//生产者
class Productor extends Thread{
//需要向容器中加入产品
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
//生产者添加产品
container.push(new Chicken(i));
System.out.println("生产者生产了"+i+"鸡");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 1; i < 100; i++) {
//消费者拿走产品
Chicken chicken = container.pop();
System.out.println("消费者消费了"+chicken.id+"鸡");
}
}
}
//缓冲区-->容器
class SynContainer{
//容器,最多10个
Chicken[] chickens = new Chicken[10];
//容器的计数器
int num = 0;
//生产者放入产品
public synchronized void push(Chicken chicken) {
//假如容易已经满了,就不用放,等待消费者消费
if (num>=chickens.length){
//等待消费者消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//假如容器没有满, 生产者继续生成
chickens[num] = chicken;
System.out.println("容器此时有多少个元素"+num);
num++;
//通知消费者消费
this.notifyAll();
}
//消费者拿走产品
public synchronized Chicken pop(){
//假如容器空的,等待
if (num<=0){
//等待生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取走产品
num--;
Chicken chicken = chickens[num];
//通知生产者生产
this.notifyAll();
return chicken;
}
}
//产品->鸡
class Chicken {
int id;
public Chicken(int id) {
this.id = id;
}
}
|
基本需求:
演员说话,观众等待
观众说话,演员等待
竞争资源:电视,只有一个,可以用信号量标志法,flag初始值为true,使得play先进行,watch后进行
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
|
package com.zjh;
public class PV_flag {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
this.tv.play("节目:快乐大本营播放中");
System.out.println();
}else {
this.tv.play("广告:抖音,记录美好生活");
}
}
}
}
//消费者
class Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//电视
class TV{
//演员说话 , 观众等待
//观众观看 , 演员等待
boolean flag = true;
//说话
String voice;
//表演
public synchronized void play(String voice){
//演员等待
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表演了"+voice);
this.voice = voice;
//通知观众观看
this.notifyAll();
this.flag = !this.flag;
}
//观看
public synchronized void watch(){
//观众等待
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众听到了: "+voice);
//通知演员说话
this.notifyAll();
this.flag = !this.flag;
}
}
|
优点:
- 以thread为操作对象更符合阻塞线程的直观定义。
- 操作更精准,可以准确地唤醒特定线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。
- park/unpark的设计原理核心是“许可”(permit):park是等待一个许可,unpark是为某线程提供一个许可。permit不能叠加,也就是说permit的个数要么是0,要么是1。也就是不管连续调用多少次unpark,permit也是1个。线程调用一次park就会消耗掉permit,再一次调用park又会阻塞住。如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。
- unpark可以先于park调用。在使用park和unpark的时候可以不用担心park的时序问题造成死锁。相比之下,wait/notify存在时序问题,wait必须在notify调用之前调用,否则虽然另一个线程调用了notify,但是由于在wait之前调用了,wait感知不到,就造成wait永远在阻塞。
- park和unpark调用的时候不需要获取同步锁。
- Thread.sleep()
- Thread.yield()
- suspend()挂起
- 正常执行结束
- 线程在同步代码块,同步方法中遇到break,return。
- 线程出现了未处理的Error或Exception,导致异常结束
- Thread.wait()