多线程简介

在这里插入图片描述
注意: 所有的车共享这条套路。
原来的执行程序为一条路径,现在加入多线程则存在多
条执行路径。
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类实现

  1. 创建线程类: 继承 Thread类 +重写 run() 方法
  2. 构造线程类对象: 创建 子类的对象
  3. 启动线程: 通过子类对象调用 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
 }
}
  1. 创建一个实现类对象
  2. 利用实现类对象创建Thread类对象
  3. 启动线程
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接口实现(了解)

  1. 创建实现 Callable 接口的实现类 + 重写 call() 方
  2. 创建一个实现类对象
  3. 由 Callable 创建一个 FutureTask 对象
  4. 由 FutureTask 创建一个 Thread 对象
  5. 启动线程
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. 线程的五种状态
我们在现实生活中,思考问题、发现问题、处理问题,
这是一个完成一件事或者处理一个问题经历的中间过
程。在程序世界也一样,要完成一件事情线程从出生到
消亡会经历一个流程,中间会有不同状态的转换。
在这里插入图片描述

  1. 新建状态
    使用 new 关键字和 Thread 类或其子类建立一个线程
    对象后,该线程对象就处于新建状态。它保持这个状
    态直到程序 start() 这个线程。
  2. 就绪状态
    当线程对象调用了start()方法之后,该线程就进入就
    绪状态。就绪状态的线程处于就绪队列中,要等待
    JVM里线程调度器的调度。
  3. 运行状态
    如果就绪状态的线程获取 CPU 资源,就可以执行
    run(),此时线程便处于运行状态。处于运行状态的线
    程最为复杂,它可以变为阻塞状态、就绪状态和死亡
    状态。
  4. 阻塞状态
    如果一个线程执行了sleep(睡眠)、suspend(挂
    起)等方法,失去所占用资源之后,该线程就从运行
    状态进入阻塞状态。在睡眠时间已到或获得设备资源
    后可以重新进入就绪状态。可以分为三种:
    等待阻塞:运行状态中的线程执行 wait() 方法,
    使线程进入到等待阻塞状态。
    同步阻塞:线程在获取 synchronized同步锁失败
    (因为同步锁被其他线程占用)。
    其他阻塞:通过调用线程的 sleep() 或 join() 发出
    了 I/O请求时,线程就会进入到阻塞状态。当
    sleep() 状态超时,join() 等待线程终止或超时,或
    者 I/O 处理完毕,线程重新转入就绪状态。
  5. 死亡状态
    一个运行状态的线程完成任务或者其他终止条件发生
    时,该线程就切换到终止状态。
    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();
 }
}

如何解决死锁问题:

  1. 往往是程序逻辑的问题。需要修改程序逻辑。
  2. 尽量不要同时持有两个对象锁。 如修改成如下:
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();
   }
 }
}

寄 语
我们与成功之间并不遥远,
许多非凡的成就只不过是简单坚持的结果, 关键
是要守住你的初心

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值