注意: 所有的车共享这条套路。
原来的执行程序为一条路径,现在加入多线程则存在多
条执行路径。
1.1. 一条路
方法间的调用为一条路径,无论怎么调用,我们总能顺
着一条线找到每一行代码的执行顺序:
public class OneRoute{
public static void main(String[] args){
System.out.pritnln("只有");
int result = a();
System.out.println("--->" + result);
}
public static int a(){
System.out.println("main");
return b()*100;
}
public static int b(){
System.out.println("一条路径");
return 1;
}
}
1.2. 两条路
开辟新的线程,为两条执行路径,两条并行的,存在随
机性,我们也不能确定代码的先后顺序。
public class ThreadRoute{
public static void main(String[] args){
System.out.println("多条路");
new Route().start();
System.out.println("main-->");
}
}
class Route extends Thread{
public void run(){
a();
}
public int a(){
System.out.println("thread");
2. 常用概念
2.1. 程序
Java源程序和字节码文件被称为“程序” ( Program ),是
一个静态的概念。
return b()*100;
}
public int b(){
System.out.println("一条路");
return 1;
}
}
2. 常用概念
2.1. 程序
Java源程序和字节码文件被称为“程序” ( Program ),是
一个静态的概念。
return b()*100;
}
public int b(){
System.out.println("一条路");
return 1;
}
}
计算机程序(Computer Program),港、台译做电脑
程式。计算机程序是一组计算机能识别和执行的指
令,运行于电子计算机上,满足人们某种需求的信息
化工具。
为了能够达到效果,我们需要电脑去执行读取程序,并
执行相应的操作。
2.2. 进程
执行中的程序叫做进程(Process),是一个动态的概念。
为了使计算机程序得以运行,计算机需要加载代码,同
时也要加载数据。
进程是程序的一次动态执行过程, 占用特定的地址空
间。
每个进程由3部分组成:cpu,data,code。每个进程都
是独立的,保有自己的cpu时间,代码和数据,即便
用同一份程序产生好几个进程,它们之间还是拥有自
己的这3样东西。
多任务(Multitasking)操作系统将CPU时间动态地划分
给每个进程,操作系统同时执行多个进程,每个进程
独立运行。以进程的观点来看,它会以为自己独占
Cpu的使用权
进程的查看:
Windows系统: Ctrl+Alt+Del
Unix系统: ps or top
2.3. 线程
线程(英语:thread)是操作系统能够进行运算调度的
最小单位。它被包含在进程之中,是进程中的实际运作
单位。一条线程指的是进程中一个单一顺序的控制流,
一个进程中可以并发多个线程,每条线程并行执行不同
的任务。
Threads run at the same time, independently of one
another
一个进程可拥有多个并行的(concurrent)线程
一个进程中的线程共享相同的内存单元/内存地址空
间可以访问相同的变量和对象,而且它们从同一堆中
分配对象通信、数据交换、同步操作
由于线程间的通信是在同一地址空间上进行的,所以
不需要额外的通信机制,这就使得通信更简便而且信
息传递的速度也更快。
程序是指令的集合,代码的集合 ; 而进程是动态的概
念,当程序在执行时,系统分配进程 ; 多线程是在同一
进程下,充分利用资源 ,多条执行路径,共享资源 (cpu
data code) 。
注意:有的多线程是模拟出来的,真正的多线程是指有多
个 cpu,即多核,如服务器。如 果是模拟出来的多线程,
即一个 cpu 的情况下,在同一个时间点,cpu 只能执行
一个代码, 因为切换的很快,所以就有同时执行的错
觉。
2.4. 多线程优缺点
2.4.1. 优点
资源利用率更好;程序设计在某些情况下更简单;程序响
应更快
2.4.2. 缺点
设计更复杂,虽然有一些多线程应用程序比单线程的
应用程序要简单,但其他的一般都更复杂。在多线程
访问共享数据的时候,这部分代码需要特别的注意。
线程之间的交互往 往非常复杂。不正确的线程同步产
生的错误非常难以被发现,并且重现以修复。
上下文切换的开销 当 CPU 从执行一个线程切换到执
行另外一个线程的时候,它需要 先存储当前线程的本
地的数据,程序 指针等,然后载入另一个线程的本地
数据,程序指针 等,最后才开始执行。这种切换称
为“上下文切 换”(“context switch”)。CPU 会在一 个上
下文中执行一个线程,然后切换到另外一个上下文中
执 行另外一个线程。上下文切换 并不廉价。如果没
有必要,应该减少上下文切换的发生。
2.5. 多线程地位
线程在 java 中的地位非常重要,但是比较难以理解,庆
幸的是在实际工作中,直接编 写线程的代码不多;线程
在手写服务器用的很多,当然现在的服务器都是其他公
司写好的, 直接拿来用即可,如果真的想把多线程学
好,需要掌握很多知识,如操作系统原理、并发机 制
等。 对于目前的大家来说,线程掌握以下即可:
1、创建线程的方式
2、线程的状态
3、sleep
4、停止线程
5、 Thread.currentThread
6、简单的 synchronized2
3. 创建线程
编写多线程程序是为了实现多任务的并发执行,从而能
够更好地与用户交互。一般有四种方法,
Thread , Runnable , Callable ,使用 Executor 框架来
创建线程池。
3.1. 继承Thread类实现
- 创建线程类: 继承 Thread类 +重写 run() 方法
- 构造线程类对象: 创建 子类的对象
- 启动线程: 通过子类对象调用 start() 方法
创建 Thread 子类的一个实例并重写 run 方法, run
方法会在调用 start() 方法之后自动被执行
public class TestThread {
public static void main(String[] args) {
// 创建线程类对象
SomeThread oneThread = new
SomeThread();
// 启动线程
oneThread.start();
}
}
// 创建线程类
class SomeThead extends Thread{
@Override
public void run()
{
//do something here
}
}
至此,一个线程就创建完成了。
这种方式的特点:那就是如果我们的类已经从一个类继
承(如小程序必须继承自 Applet 类),则无法再继承
Thread 类,异常只能捕获。
3.2. 实现Runnable接口实现
4. 创建实现 Runnable 接口的实现类 + 重写 run() 方
法
public class TestThread {
public static void main(String[] args) {
// 创建线程类对象
SomeThread oneThread = new
SomeThread();
// 启动线程
oneThread.start();
}
}
// 创建线程类
class SomeThead extends Thread{
@Override
public void run()
{
//do something here
}
}
- 创建一个实现类对象
- 利用实现类对象创建Thread类对象
- 启动线程
public class TestThread2 implements Runnable {
SomeRunnable r1 = new SomeRunnable();
Thread thread1 = new Thread(r1);
thread1.start();
Thread thread2 = new Thread(new
SomeRunnable());
thread2.start();
}
// 创建Runnable子类
class SomeRunnable implements Runnable{
@Override
public void run()
{
//do something here
}
}
至此,一个线程就创建完成了。
线程的执行流程很简单,当执行代码oneThread.start();
时,就会执行oneRunnable对象中的void run();方法,该
方法执行完成后,线程就消亡了。
public class TestThread2 implements Runnable {
SomeRunnable r1 = new SomeRunnable();
Thread thread1 = new Thread(r1);
thread1.start();
Thread thread2 = new Thread(new
SomeRunnable());
thread2.start();
}
// 创建Runnable子类
class SomeRunnable implements Runnable{
@Override
public void run()
{
//do something here
}
}
3.3. 实现Callable接口实现(了解)
- 创建实现 Callable 接口的实现类 + 重写 call() 方
法 - 创建一个实现类对象
- 由 Callable 创建一个 FutureTask 对象
- 由 FutureTask 创建一个 Thread 对象
- 启动线程
public class CallAbleTest {
public static void main(String[] args)
throws Exception{
MyCallable callable = new
MyCallable();
// 将Callable包装成FutureTask,
FutureTask也是一种Runnable
FutureTask<Integer> futureTask = new
FutureTask<>(callable);
// 将FutureTask包装成Thread
new Thread(futureTask).start();
System.out.println(futureTask.isDone());
System.out.println(futureTask.get());
}
}
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
}
}
Thread: 继承方式, 不建议使用, 因为Java是单继承的,
继承了Thread就没办法继承其它类了,不够灵活
Runnable: 实现接口,比Thread类更加灵活,没有单
继承的限制
Callable: Thread和Runnable都是重写的run()方法并且
没有返回值,Callable是重写的call()方法并且有返回
值并可以借助FutureTask类来判断线程是否已经执行
完毕或者取消线程执行
当线程不需要返回值时使用Runnable,需要返回值时
就使用Callable,一般情况下不直接把线程体代码放
到Thread类中,一般通过Thread类来启动线程
Thread类是实现Runnable,Callable封装成
FutureTask,FutureTask实现RunnableFuture,
RunnableFuture继承Runnable,所以Callable也算是一
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
}
}
种Runnable,所以三种实现方式本质上都是Runnable
实现
3.4. 使用线程池创建(了解)
在Java5之后,并发编程引入了一堆新的启动、调度和管
理线程的API。Executor框架便是Java 5中引入的,其内
部使用了线程池机制,它在 java.util.cocurrent 包
下,通过该框架来控制线程的启动、执行和关闭,可以
简化并发编程的操作
6. 使用 Executors 类中的
newFixedThreadPool(int num) 方法创建一个线
程数量为num的线程池
7. 调用线程池中的 execute() 方法执行由实现
Runnable 接口创建的线程;调用 submit() 方法执
行由实现 Callable 接口创建的线程
8. 调用线程池中的 shutdown() 方法关闭线程池
public class ThreadPoolTest {
public static void main(String[]
args)throws Exception {
Thread.currentThread().setName("主线
程");
System.out.println(Thread.currentThread().get
Name() + ": 输出的结果" );
// 通过线程池工厂创建线程数量为2的线程池
ExecutorService service =
Executors.newFixedThreadPool(2);
//执行线程,execute()适用于实现Runnable接
口创建的线程
service.execute(new MyThread());
//submit()适用于实现Callable接口创建的线程
Future<Integer> task =
service.submit(new MyCallable());
System.out.println(task.get());
// 关闭线程池
service.shutdown();
}
}
3.5. 线程池
3.5.1. 线程池的概念
线程池就是首先创建一些线程,它们的集合称为线程
池。使用线程池可以很好地提高性能,线程池在系统启
动时即创建大量空闲的线程,程序将一个任务传给线程
池,线程池就会启动一条线程来执行这个任务,执行结
束以后,该线程并不会死亡,而是再次返回线程池中成
为空闲状态,等待执行下一个任务。
ExecutorService service =
Executors.newFixedThreadPool(2);
//执行线程,execute()适用于实现Runnable接
口创建的线程
service.execute(new MyThread());
//submit()适用于实现Callable接口创建的线程
Future task =
service.submit(new MyCallable());
System.out.println(task.get());
// 关闭线程池
service.shutdown();
}
}
3.5.2. 线程池的工作机制
在线程池的编程模式下,任务是提交给整个线程池,
而不是直接提交给某个线程,线程池在拿到任务后,
就在内部寻找是否有空闲的线程,如果有,则将任务
交给某个空闲的线程。
一个线程同时只能执行一个任务,但可以同时向一个
线程池提交多个任务。
3.5.3. 使用线程池的原因
多线程运行时间,系统不断的启动和关闭新线程,成本
非常高,会过渡消耗系统资源,以及过渡切换线程的危
险,从而可能导致系统资源的崩溃。这时,线程池就是
最好的选择了
4. 线程的五种状态
我们在现实生活中,思考问题、发现问题、处理问题,
这是一个完成一件事或者处理一个问题经历的中间过
程。在程序世界也一样,要完成一件事情线程从出生到
消亡会经历一个流程,中间会有不同状态的转换。
- 新建状态
使用 new 关键字和 Thread 类或其子类建立一个线程
对象后,该线程对象就处于新建状态。它保持这个状
态直到程序 start() 这个线程。 - 就绪状态
当线程对象调用了start()方法之后,该线程就进入就
绪状态。就绪状态的线程处于就绪队列中,要等待
JVM里线程调度器的调度。 - 运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行
run(),此时线程便处于运行状态。处于运行状态的线
程最为复杂,它可以变为阻塞状态、就绪状态和死亡
状态。 - 阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂
起)等方法,失去所占用资源之后,该线程就从运行
状态进入阻塞状态。在睡眠时间已到或获得设备资源
后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,
使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized同步锁失败
(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出
了 I/O请求时,线程就会进入到阻塞状态。当
sleep() 状态超时,join() 等待线程终止或超时,或
者 I/O 处理完毕,线程重新转入就绪状态。 - 死亡状态
一个运行状态的线程完成任务或者其他终止条件发生
时,该线程就切换到终止状态。
4.1. 停止线程
死亡状态是线程生命周期中的最后一个阶段。线程死亡
的原因有两个。一个是正常运行的线程完成了它的全部
工作;另一个是线程被强制终止,如通过执行 stop 或
destroy 方法来终止一个线程。但是,不要调用 stop,
destory 方法 ,太暴力,一盆冷水让其停止。
1、正常执行完毕,循环 次数已经到达
2、外部干涉
线程中加入标识 -->属性
线程体中 使用改标识 -->死循环
对外提供改变改标识的方法 setXxx() terminate()
a()…
外部根据适当的时机调用该方法
public class TestThreadCiycle implements
Runnable {
String name;
boolean live = true;
public TestThreadCiycle(String name) {
super();
this.name = name;
}
public void run() {
int i=0;
while(live){
System.out.println(name+(i++));
}
}
public void terminate(){
live = false;
}
public static void main(String[] args) {
TestThreadCiycle ttc = new
TestThreadCiycle("线程A:");
Thread t1 = new Thread(ttc); //新生状
态
t1.start(); //就绪状态
for(int i=0;i<1000;i++){
System.out.println(i);
}
ttc.terminate();
System.out.println("ttc stop!");
}
}
4.2. 阻塞状态
处于运行状态的线程在某些情况下,如执行了sleep(睡
眠)方法,或等待I/O设备等资源,将让出CPU并暂时停
止自己的运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞
的原因消除时,如睡眠时间已到,或等待的I/O设备空闲
下来,线程便转入就绪状态,重新到就绪队列中排队等
待,被系统选中后从原来停止的位置开始继续运行。
有三种方法可以让我们暂停Thread执行:
sleep方法:sleep() 方法需要指定等待的时间,它可
以让当前正在执行的线程在指定的时间内暂停执行,
进入阻塞状态,该方法既可以让其他同优先级或者高
优先级的线程得到执行的机会,也可以让低优先级的
线程得到执行机会。但是 sleep() 方法不会释放“锁标
t1.start(); //就绪状态
for(int i=0;i<1000;i++){
System.out.println(i);
}
ttc.terminate();
System.out.println(“ttc stop!”);
}
}
志”,也就是说如果有 synchronized 同步块,其他线
程仍然不能访问共享数据。
yield方法: yield() 方法和 sleep() 方法类似,也不会释
放“锁标志”,区别在于,它没有参数,即 yield() 方法
只是使当前线程重新回到可执行状态,所以执行
yield() 的线程有可能在进入到可执行状态后马上又被
执行。让出CPU的使用权,从运行态直接进入就绪
态。让CPU重新挑选哪一个线程进入运行状态。
join方法: 方法会使当前线程等待调用 join() 方法的线
程执行结束之后,才会继续往后执行。
public class TestThreadState {
public static void main(String[] args) {
//使用继承方式实现多线程
// StateThread thread1 = new
StateThread();
// thread1.start();
// StateThread thread2 = new
StateThread();
// thread2.start();
System.out.println("爸爸和儿子买烟故事");
Thread father = new Thread(new
FatherThread());
father.start();
}
}
class StateThread extends Thread {
public void run(){
for(int i=0;i<100;i++){
System.out.println(this.getName()+":"+i);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.yield();
}
}
}
class FatherThread implements Runnable {
public void run() {
System.out.println("爸爸想抽烟,发现烟抽完
了");
System.out.println("爸爸让儿子去买包红塔
山");
Thread son = new Thread(new
SonThread());
son.start();
System.out.println("爸爸等儿子买烟回来");
try {
son.join();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("爸爸出门去找儿子
跑哪去了");
System.exit(1);//结束JVM。如果是0则表
示正常结束;如果是非0则表示非正常结束
}
System.out.println("爸爸高兴的接过烟开始
抽,并把零钱给了儿子");
}
}
class SonThread implements Runnable {
public void run() {
System.out.println("儿子出门去买烟");
System.out.println("儿子买烟需要10分钟");
try {
for (int i = 1; i <=10;i++) {
System.out.println("第" + i +
"分钟");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("儿子买烟回来了");
}
}
4.3. 线程的基本信息
Java提供一个线程调度器来监控程序中启动后进入就绪
状态的所有线程。线程调度器按照 线程的优先级 决定应
调度哪个线程来执行。
线程的优先级用数字表示,范围从 1 到 10
一个线程的缺省优先级是5
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
使用下述线程方法获得或设置线程对象的优先级
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
注意:优先级低只是意味着获得调度的概率低。并不是
绝对先调用优先级高后调用优先级低的线程。
void setPriority(int newPriority);
void setPriority(int newPriority);
public class ThreadTest4 {
public static void main(String[] args) {
Thread t1 = new Thread(new Thread7(),
"t1");
Thread t2 = new Thread(new Thread7(),
"t2");
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
class Thread7 extends Thread {
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().get
Name() + ": " + i);
// yield();
}
}
}
5. 线程同步和死锁问题
5.1. 线程安全
在一般情况下,创建一个线程是不能提高程序的执行效
率的,所以要创建多个线程。但是多个线程同时运行的
时候可能调用线程函数,在多个线程同时对同一个内存
地址进行写入,由于CPU时间调度上的问题,写入数据
会被多次的覆盖,所以就有可能造成数据的不准确。
/**
* 测试同步问题
* @author Administrator
*
*/
public class TestSync {
public static void main(String[] args) {
Account a1 = new Account(100,"高");
Drawing draw1 = new Drawing(80,a1);
Drawing draw2 = new Drawing(80,a1);
draw1.start(); //你取钱
draw2.start(); //你老婆取钱
}
}
/*
* 简单表示银行账户, 将来打算多个线程共用的资源
*/
class Account {
int money;
String aname;
public Account(int money, String aname) {
super();
this.money = money;
this.aname = aname;
}
}
/**
* 模拟提款操作
* @author Administrator
*
*/
class Drawing extends Thread{
int drawingNum; //取多少钱
Account account; //要取钱的账户
int expenseTotal; //总共取的钱数
public Drawing(int drawingNum,Account
account) {
super();
this.drawingNum = drawingNum;
this.account = account;
}
@Override
public void run() {
if(account.money-drawingNum<0){
return;
}
try {
Thread.sleep(1000); //判断完后阻
塞。其他线程开始运行。
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=drawingNum;
expenseTotal+=drawingNum;
System.out.println(this.getName()+"--
账户余额:"+account.money);
System.out.println(this.getName()+"--
总共取了:"+expenseTotal);
}
}
结果:
Thread-0--账户余额:20
Thread-1--账户余额:-60
Thread-0--总共取了:80
Thread-1--总共取了:80
5.2. 线程同步 synchronized
线程同步:即当有一个线程在对内存进行操作时,其他
线程都不可以对这个内存地址进行操作,直到该线程完
成操作, 其他线程才能对该内存地址进行操作,而其他
线程又处于等待状态。
同步就是协同步调,按预定的先后次序进行运行。如:
你说完,我再说。
“同”字从字面上容易理解为一起动作
其实不是,“同”字应是指协同、协助、互相配合。
在Java里面,通过 synchronized 进行同步的保证。它
包括两种用法:synchronized 方法和 synchronized 块
5.2.1. synchronized 方法
通过在方法声明中加入 synchronized关键字来声明
synchronized 方法。如:
public synchronized void accessVal(int
newVal);
synchronized 方法控制对类成员变量的访问:每个对象
对应一把锁,每个 synchronized 方法都必须获得调用该
方法的对象的锁方能执行,否则所属线程阻塞,方法一
旦执行,就独占该锁,直到从该方法返回时才将锁释
放,此后被阻塞的线程方能获得 该锁,重新进入可执行
状态。
synchronized 方法的缺陷:若将一个大的方法声明为
synchronized 将会大大影响效率,典型地,若将线程类
的方法 run() 声明为 synchronized ,由于在线程的整个
生命期内它一直在运行,因此将导致它对本类任何
synchronized 方法的调用都永远不会成功。当然我们可
以通过将访问类成员变量的代码放到专门的方法中,将
其声明为 synchronized ,并在主方法中调用来解决这一
问题,但是 Java 为我们提供了更好的解决办法,那就是
synchronized 块。
5.2.2. synchronized 块
在代码块前加上 synchronized 关键字,并指定加锁的
对象
synchronized(syncObject){
//允许访问控制的代码
}
/**
* 测试同步问题
* @author Administrator
*
*/
public class TestSync {
public static void main(String[] args) {
Account a1 = new Account(100,"高");
Drawing draw1 = new Drawing(80,a1);
Drawing draw2 = new Drawing(80,a1);
draw1.start(); //你取钱
draw2.start(); //你老婆取钱
}
}
/*
* 简单表示银行账户
*/
class Account {
int money;
String aname;
public Account(int money, String aname) {
super();
this.money = money;
this.aname = aname;
}
}
/**
* 模拟提款操作
* @author Administrator
*
*/
class Drawing extends Thread{
int drawingNum; //取多少钱
Account account; //要取钱的账户
int expenseTotal; //总共取的钱数
public Drawing(int drawingNum,Account
account) {
super();
this.drawingNum = drawingNum;
this.account = account;
}
@Override
public void run() {
draw();
}
// 改进使用 双重检查
void draw(){
synchronized (account) {
if(account.money-drawingNum<0){
return;
}
try {
Thread.sleep(1000); //判断完后
阻塞。其他线程开始运行。
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money-=drawingNum;
expenseTotal+=drawingNum;
}
System.out.println(this.getName()+"--
账户余额:"+account.money);
System.out.println(this.getName()+"--
总共取了:"+expenseTotal);
}
}
上面这种方式叫做:互斥锁原理。利用互斥锁解决临界
资源问题。
6. 死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞
争资源或者由于彼此通信而造成的一种阻塞的现象,若
无外力作用,它们都将无法推进下去。此时称系统处于
死锁状态或系统产生了死锁,这些永远在互相等待的线
程称为死锁线程。
// 口红
class Lipstick{
}
// 镜子
class Mirror{
}
class Makeup extends Thread {
int flag;
String girl;
static Lipstick lipstick=new Lipstick();
static Mirror mirror= new Mirror();
@Override
public void run() {
// TODO Auto-generated method stub
doMakeup();
}
void doMakeup(){
if(flag==0){
synchronized (lipstick) {
System.out.println(girl+"拿着口
红!");
try {
Thread.sleep(1000);
} catch (InterruptedException
e) {
e.printStackTrace();
}
synchronized (mirror) {
System.out.println(girl+"拿着镜子!");
}
}
}
else{
synchronized (mirror) {
System.out.println(girl+"拿着镜
子!");
try {
Thread.sleep(2000);
} catch (InterruptedException
e) {
e.printStackTrace();
}
synchronized (lipstick) {
System.out.println(girl+"拿着口红!");
}
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
Makeup m1 = new Makeup(); m1.girl="大
丫"; m1.flag=0;
Makeup m2 = new Makeup(); m2.girl="小
丫"; m2.flag=1;
m1.start();
m2.start();
}
}
如何解决死锁问题:
- 往往是程序逻辑的问题。需要修改程序逻辑。
- 尽量不要同时持有两个对象锁。 如修改成如下:
void doMakeup(){
if(flag==0){
synchronized (lipstick) {
System.out.println(girl+"拿着口
红!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mirror) {
System.out.println(girl+"拿着镜
子!");
}
}
else{
synchronized (mirror) {
System.out.println(girl+"拿着镜
子!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lipstick) {
System.out.println(girl+"拿着口
红!");
}
}
}
}
7. 生产者/消费者模式(了解)
在常见的多线程问题解决中,同步问题的典型示例是“生
产者-消费者”模型,也就是生产者线程只负责生产,消
费者线程只负责消费,在消费者发现无内容可消费时则
睡觉
同步解决问题的另一种典型方式:生产者/消费者模式:
在常见的多线程问题解决中,同步问题的典型示例是“生
产者-消费者”模型,也就是生产者线程只负责生产,消
费者线程只负责消费,在消费者发现无内容可消费时则
睡觉。下面举一个例子。
public class Demo004Synchronized4 {
public static void main(String[] args) {
SyncStack stack = new SyncStack();
ShengChan sc = new ShengChan(stack);
Chi chi = new Chi(stack);
sc.start();
chi.start();
}
}
// 馒头类
class Mantou {
}
// 有生产、有消费功能的工厂
class SyncStack{
List<Mantou> list = new ArrayList<Mantou>
(); // 容器,10就满了
// 生产馒头的方法
public synchronized void push(Mantou
mantou){
// 我认为现在已经放满了
if(list.size()==10){
try {
// 通知别人可以来买了
this.notify();//如果不唤醒的话。
以后这两个线程都会进入等待线程,没有人唤醒。
// 满了之后就停止生产
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
list.add(mantou);
System.out.println("生产第" +
+list.size()+ "个馒头");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch
block
e.printStackTrace();
}
}
}
// 吃馒头的方法
public synchronized void pop(){
// 吃完了
if(list.size()==0){
try {
// 通知别人该生产了
this.notify(); //唤醒在当前对象等
待池中等待的第一个线程。notifyAll叫醒所有在当前对象等
待池中等待的所有线程。
// 停下吃的动作
this.wait();
//wait后,线程会将持有的锁释放。
sleep是即使睡着也持有互斥锁。
} catch (InterruptedException e) {
// TODO Auto-generated catch
block
e.printStackTrace();
}
}
list.remove(list.size()-1);
System.out.println("现在还剩下 " +
list.size() + " 个馒头");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// 生产馒头的线程
class ShengChan extends Thread{
SyncStack stack;
public ShengChan(SyncStack stack){
this.stack = stack;
}
@Override
public void run() {
// 一直生产馒头
while(true){
Mantou mantou = new Mantou();
stack.push(mantou);
}
}
}
class Chi extends Thread{
SyncStack stack;
public Chi(SyncStack stack){
this.stack = stack;
}
@Override
public void run() {
// 一直吃馒头
while(true){
this.stack.pop();
}
}
}
寄 语
我们与成功之间并不遥远,
许多非凡的成就只不过是简单坚持的结果, 关键
是要守住你的初心