多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

时间:2022-04-22 04:30:27

                      重难点梳理

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

                      知识点梳理

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

                          学习目标

1、能够知道什么是进程什么是线程(进程和线程的概述,多进程和多线程的意义)

2、能够掌握线程常见API的使用

3、能够理解什么是线程安全问题

4、能够知道什么是锁

5、能够知道什么是死锁

6、能够掌握线程3种创建方式(3种创建方式)

7、能够知道什么是等待唤醒机制

                          超详细讲义

==知识点==

  1. 多线程的概念

  2. 多线程的实现方式

  3. 线程类的常见方法

  4. 线程同步

  5. 死锁

  6. 生产者消费者

1.多线程的概念

1.1初步了解多线程【重点】

1.什么是多线程?

采用多线程技术可以同时执行多个任务(比如:迅雷同时下载多个任务),多线程需要硬件支持

1.2并发和并行【重点】

1.什么是并行?

在同一时刻,有多条线程在多个CPU上同时执行

2.什么是并发?

在一段时间内,有多条线程在单个CPU上交替执行

1.3进程和线程【重点】

(共4点)

1.什么是进程?

可以理解为正在运行的程序

2.什么是线程?

线程它是进程的一部分,是进程中的单个控制流,是一条执行路径(执行一项任务)

一个进程,可以有多条线程,至少有一条线程(线程才是CPU执行的最小单元)

线程只能在进程中运行

==3.为什么要学习多线程==

可以让程序同时执行多个任务,提高程序的执行效率(例如一次上传多张图片,同时打开多个网页)

2.多线程的实现方式

2.1实现多线程方式一:继承Thread类【重点】

1.实现多线程的方式有哪些?

  • 继承Thread类的方式进行实现

  • 实现Runnable接口的方式进行实现

  • 利用Callable和Future接口方式实现

2.方法介绍

方法名 说明
void run() 在线程开启后,此方法将被调用执行
void start() 使此线程开始执行,Java虚拟机会调用run方法()

3.实现步骤

  • 定义一个类MyThread继承Thread类

  • 在MyThread类中重写run()方法

  • 创建MyThread类的对象

  • 启动线程

代码演示

package com.itheima.threaddemo1;

public class MyThread extends Thread{
   @Override
   public void run() {
       //代码就是线程在开启之后执行的代码
       for (int i = 0; i < 100; i++) {
           System.out.println("线程开启了" + i);
      }
  }
}

package com.itheima.threaddemo1;

public class Demo {
   public static void main(String[] args) {
       //创建一个线程对象
       MyThread t1 = new MyThread();
       //创建一个线程对象
       MyThread t2 = new MyThread();

       //t1.run();//表示的仅仅是创建对象,用对象去调用方法,并没有开启线程.
       //t2.run();
       //开启一条线程
       t1.start();
       //开启第二条线程
       t2.start();
  }
}

2.2 多线程的实现方式-两个小问题【了解】

1.为什么要重写run()方法?

因为run()是用来封装被线程执行的代码

2.run()方法和start()方法的区别?

run():封装线程执行的代码,直接调用,相当于普通方法的调用

start():启动线程;然后由JVM调用此线程的run()方法

2.3实现多线程方式二:实现Runnable接口【重点】

(共3点)

1.Thread构造方法

方法名 说明
Thread(Runnable target) 分配一个新的Thread对象

2.实现步骤

  • 定义一个类MyRunnable实现Runnable接口

  • 在MyRunnable类中重写run()方法

  • 创建MyRunnable类的对象

  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数

  • 启动线程

代码演示

package com.itheima.threaddemo2;

public class MyRunnable implements Runnable{
   @Override
   public void run() {
       //线程启动后执行的代码
       for (int i = 0; i < 100; i++) {
           System.out.println(Thread.currentThread().getName() + "第二种方式实现多线程" + i);
      }
  }
}

package com.itheima.threaddemo2;

public class Demo {
   public static void main(String[] args) {
       //创建了一个参数的对象
       MyRunnable mr = new MyRunnable();
       //创建了一个线程对象,并把参数传递给这个线程.
       //在线程启动之后,执行的就是参数里面的run方法
       Thread t1 = new Thread(mr);
       //开启线程
       t1.start();


       MyRunnable mr2 = new MyRunnable();
       Thread t2 = new Thread(mr2);
       t2.start();

  }
}

==3.线程的执行是顺序是随机的==

2.4 实现多线程方式三: 实现Callable接口【重点】

1.方法介绍

方法名 说明
V call() 计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable<V> callable) 创建一个 FutureTask,一旦运行就执行给定的 Callable
V get() 如有必要,等待计算完成,然后获取其结果

2.实现步骤

  • 定义一个类MyCallable实现Callable接口

  • 在MyCallable类中重写call()方法

  • 创建MyCallable类的对象

  • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数

  • 创建Thread类的对象,把FutureTask对象作为构造方法的参数

  • 启动线程

  • 再调用get方法,就可以获取线程结束之后的结果。

3.注意事项

get()方法的调用一定要在Thread类对象调用start()方法之后

代码演示

package com.itheima.threaddemo3;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
   @Override
   public String call() throws Exception {
       for (int i = 0; i < 100; i++) {
           System.out.println("跟女孩表白" + i);
      }
       //返回值就表示线程运行完毕之后的结果
       return "答应";
  }
}


package com.itheima.threaddemo3;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Demo {
   public static void main(String[] args) throws ExecutionException, InterruptedException {
       //线程开启之后需要执行里面的call方法
       MyCallable mc = new MyCallable();

       //Thread t1 = new Thread(mc);

       //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
       FutureTask<String> ft = new FutureTask<>(mc);

       //创建线程对象
       Thread t1 = new Thread(ft);

       String s = ft.get();
       //开启线程
       t1.start();

       //String s = ft.get();
       System.out.println(s);
  }
}

2.5 三种实现方式的对比【重点】

1.实现Runnable、Callable接口

  • 好处: 扩展性强,实现该接口的同时还可以继承其他的类

  • 缺点: 编程相对复杂,不能直接使用Thread类中的方法

2.继承Thread类

  • 好处: 编程比较简单,可以直接使用Thread类中的方法

  • 缺点: 可以扩展性较差,不能再继承其他的类

3.线程类中的常见方法

3.1设置和获取线程名称【重点】

方法介绍

方法名 说明
void setName(String name) 将此线程的名称更改为等于参数name
String getName() 返回此线程的名称

代码演示

package com.itheima.threaddemo4;

public class MyThread extends Thread {

   public MyThread() {
}

   public MyThread(String name) {
       super(name);
  }

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println(getName() + "@@@" + i);
      }
  }
}

package com.itheima.threaddemo4;

public class Demo {
   //1,线程是有默认名字的,格式:Thread-编号
   public static void main(String[] args) {
       MyThread t1 = new MyThread("小蔡");
       MyThread t2 = new MyThread("小强");

       //t1.setName("小蔡");
       //t2.setName("小强");

       t1.start();
       t2.start();
  }
}

3.2 Tread方法-获得当前线程对象【重点】

方法名 说明
static Thread currentThread() 返回对当前正在执行的线程对象的引用
package com.itheima.threaddemo5;
public class Demo {
   public static void main(String[] args) {
       String name = Thread.currentThread().getName();
       System.out.println(name);
  }
}

3.3 线程休眠【应用】【重点】

1.相关方法

方法名 说明
static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数

代码演示

package com.itheima.threaddemo6;

public class MyRunnable implements Runnable {
   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           try {
               Thread.sleep(100);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }

           System.out.println(Thread.currentThread().getName() + "---" + i);
      }
  }
}
package com.itheima.threaddemo6;

public class Demo {
   public static void main(String[] args) throws InterruptedException {
       /*System.out.println("睡觉前");
       Thread.sleep(3000);
       System.out.println("睡醒了");*/

       MyRunnable mr = new MyRunnable();

       Thread t1 = new Thread(mr);
       Thread t2 = new Thread(mr);

       t1.start();
       t2.start();
  }
}

3.4 线程优先级【了解】

1.优先级相关方法

方法名 说明
final int getPriority() 返回此线程的优先级
final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10

代码演示

package com.itheima.threaddemo7;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
   @Override
   public String call() throws Exception {
       for (int i = 0; i < 100; i++) {
           System.out.println(Thread.currentThread().getName() + "---" + i);
      }
       return "线程执行完毕了";
  }
}
package com.itheima.threaddemo7;

import java.util.concurrent.FutureTask;

public class Demo {
   public static void main(String[] args) {
       //优先级: 1 - 10 默认值:5
       MyCallable mc = new MyCallable();

       FutureTask<String> ft = new FutureTask<>(mc);

       Thread t1 = new Thread(ft);
       t1.setName("飞机");
       t1.setPriority(10);
       //System.out.println(t1.getPriority());//5
       t1.start();

       MyCallable mc2 = new MyCallable();

       FutureTask<String> ft2 = new FutureTask<>(mc2);

       Thread t2 = new Thread(ft2);
       t2.setName("坦克");
       t2.setPriority(1);
       //System.out.println(t2.getPriority());//5
       t2.start();
  }
}

3.5 守护线程【了解】

1.什么是守护线程?

守护线程是程序运行的时候在后台提供一种通用服务的线程(保镖与主人)

2.守护线程的特点?

被守护的线程结束,不会立即结束,挣扎一会儿才结束

3.如何使用守护线程?

相关方法

方法名 说明
void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

代码演示

package com.itheima.threaddemo8.example;


public class MyThread1 extends Thread{

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println(this.getName()+"---"+i);
      }
  }
}
package com.itheima.threaddemo8.example;


public class MyThread1 extends Thread{

   @Override
   public void run() {
       for (int i = 0; i < 100; i++) {
           System.out.println(this.getName()+"---"+i);
      }
  }
}
package com.itheima.threaddemo8.example;


public class Test {

   public static void main(String[] args) {
       MyThread1 t1 = new MyThread1();//保镖
       t1.setDaemon(true);
       MyThread2 t2 = new MyThread2();//主人
      t1.setName("保镖");
      t2.setName("主人");
      t2.start();
      t1.start();
  }
}

4.线程同步

4.1卖票【难点】

  • 案例需求

    某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

  • 实现步骤

    • 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;

    • 在SellTicket类中重写run()方法实现卖票,代码步骤如下

    • 判断票数大于0,就卖票,并告知是哪个窗口卖的

    • 卖了票之后,总票数要减1

    • 票卖没了,线程停止

    • 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下

    • 创建SellTicket类的对象

    • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称

    • 启动线程

  • 代码实现

    package com.itheima.threaddemo9;

    public class Ticket implements Runnable {
       //票的数量
       private int ticket = 100;

       @Override
       public void run() {
           while(true){
               
                   if(ticket <= 0){
                       //卖完了
                       break;
                  }else{
                       try {
                           Thread.sleep(100);
                      } catch (InterruptedException e) {
                           e.printStackTrace();
                      }
                       ticket--;
                       System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                  }
              }
           
    }
    }

    package com.itheima.threaddemo9;

    public class Demo {
       public static void main(String[] args) {
           Ticket ticket = new Ticket();

           Thread t1 = new Thread(ticket);
           Thread t2 = new Thread(ticket);
           Thread t3 = new Thread(ticket);

           t1.setName("窗口一");
           t2.setName("窗口二");
           t3.setName("窗口三");

           t1.start();
           t2.start();
           t3.start();
      }
    }

4.2 线程安全问题-原因分析【难点】

1.卖票出现了问题

  • 相同的票出现了多次

    多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

  • 出现了负数的票

    多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

    出现问题的根本原因?

    线程执行的随机,在卖票的过程中CPU随机的执行了多条线程(也可以说是在卖票的过程中多条线程操作了共享数据ticket)

4.3同步代码块解决数据安全问题【重点】

1.如何解决上述问题呢?

  • 任意时刻只有一条线程可以操作共享变量

  • Java中如何解决?

同步代码块格式:

synchronized(任意对象) { 
多条语句操作共享数据的代码
}

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

2.synchronized同步代码块的特点?

默认情况下是打开的,只要有一个线程进去执行代码了,锁就会关闭

当线程执行完出来时,锁才会自动打开

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

3.什么样的情况下,会有线程安全问题

3.1 多线程环境

3.2 有共享数据

3.3 有对共享数据的操作(增、删、改(除了long、double类的基本数据类型直接赋值)、查)

代码演示

package com.itheima.threaddemo9;
public class Ticket implements Runnable {
   //票的数量
   private int ticket = 100;
   private Object obj = new Object();

   @Override
   public void run() {
       while(true){
           synchronized (obj){//多个线程必须使用同一把锁.
               if(ticket <= 0){
                   //卖完了
                   break;
              }else{
                   try {
                       Thread.sleep(100);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
                   ticket--;
                   System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
              }
          }
      }
  }
}

package com.itheima.threaddemo9;

public class Demo {
   public static void main(String[] args) {
       /*Ticket ticket1 = new Ticket();
       Ticket ticket2 = new Ticket();
       Ticket ticket3 = new Ticket();

       Thread t1 = new Thread(ticket1);
       Thread t2 = new Thread(ticket2);
       Thread t3 = new Thread(ticket3);*/

       Ticket ticket = new Ticket();

       Thread t1 = new Thread(ticket);
       Thread t2 = new Thread(ticket);
       Thread t3 = new Thread(ticket);

       t1.setName("窗口一");
       t2.setName("窗口二");
       t3.setName("窗口三");

       t1.start();
       t2.start();
       t3.start();
  }
}

4.4 线程安全问题-锁对象唯一【重点】

1.锁对象为什么要唯一?

不同线程如果锁的不是同一个对象,就解决不了线程的安全问题

package com.itheima.threaddemo10;

public class MyThread extends Thread {
   private static int ticketCount = 100;
   private static Object obj = new Object();

   @Override
   public void run() {
       while(true){
           synchronized (obj){ //就是当前的线程对象.
               if(ticketCount <= 0){
                   //卖完了
                   break;
              }else{
                   try {
                       Thread.sleep(100);
                  } catch (InterruptedException e) {
                       e.printStackTrace();
                  }
                   ticketCount--;
                   System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
              }
          }
      }
  }
}

package com.itheima.threaddemo010;

public class Demo {
   public static void main(String[] args) {
       MyThread t1 = new MyThread();
       MyThread t2 = new MyThread();

       t1.setName("窗口一");
       t2.setName("窗口二");

       t1.start();
       t2.start();
  }
}

4.5同步方法解决数据安全问题【重点】

(共4点)

1.同步方法的格式

同步方法:就是把synchronized关键字加到方法上

修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}

2.同步方法的锁对象是什么呢?

this

public class MyRunnableCommon implements Runnable {
private static int ticketCount = 100;
@Override
public void run() {
while(true){
if("窗口一".equals(Thread.currentThread().getName())){
//同步方法
boolean result = synchronizedMthod();
if(result){
break;
}
} if("窗口二".equals(Thread.currentThread().getName())){
//同步代码块
synchronized (this){
if(ticketCount == 0){
break;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
}
}
} }
} private synchronized boolean synchronizedMthod() {
if(ticketCount == 0){
return true;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
return false;
}
}
}

3.静态同步方法的格式

同步静态方法:就是把synchronized关键字加到静态方法上

修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}

4.同步静态方法的锁对象是什么呢?

类名.class

代码演示

package com.itheima.threaddemo011;

public class MyRunnable implements Runnable {
private static int ticketCount = 100; @Override
public void run() {
while(true){
if("窗口一".equals(Thread.currentThread().getName())){
//同步方法
boolean result = synchronizedMthod();
if(result){
break;
}
} if("窗口二".equals(Thread.currentThread().getName())){
//同步代码块
synchronized (MyRunnable.class){
if(ticketCount == 0){
break;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
}
}
} }
} private static synchronized boolean synchronizedMthod() {
if(ticketCount == 0){
return true;
}else{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketCount--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张票");
return false;
}
}
} package com.itheima.threaddemo011; public class Demo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr); t1.setName("窗口一");
t2.setName("窗口二"); t1.start();
t2.start();
}
}

4.6 Lock锁【重点】(视频19 5‘’)

1.如何手动开关锁呢?

使用lock

2.如何使用Lock?

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

ReentrantLock构造方法

方法名 说明
ReentrantLock() 创建一个ReentrantLock的实例

加锁解锁方法

方法名 说明
void lock() 获得锁
void unlock() 释放锁

代码演示

package com.itheima.threaddemo012;

import java.util.concurrent.locks.ReentrantLock;

public class Ticket implements Runnable {
//票的数量
private int ticket = 100;
private Object obj = new Object();
private ReentrantLock lock = new ReentrantLock(); @Override
public void run() {
while (true) {
//synchronized (obj){//多个线程必须使用同一把锁.
try {
lock.lock();
if (ticket <= 0) {
//卖完了
break;
} else {
Thread.sleep(100);
ticket--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
// }
}
}
} package com.itheima.threaddemo012; public class Demo {
public static void main(String[] args) {
/*Ticket ticket1 = new Ticket();
Ticket ticket2 = new Ticket();
Ticket ticket3 = new Ticket(); Thread t1 = new Thread(ticket1);
Thread t2 = new Thread(ticket2);
Thread t3 = new Thread(ticket3);*/ Ticket ticket = new Ticket(); Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket); t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三"); t1.start();
t2.start();
t3.start();
}
}

4.7 死锁【了解】

1.什么是死锁?(吃饭)

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

代码演示

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

5.生产者消费者

5.1 生产者和消费者思路分析【难点】

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

5.2生产者和消费者案例【难点】

Object类的等待和唤醒方法

方法名 说明
void wait() 导致当前线程等待同时释放锁,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify() 唤醒正在等待单个线程,并不立即释放锁
void notifyAll() 唤醒正在等待所有线程,并不立即释放锁

案例需求

  • 桌子类(Desk):定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量

  • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

    1.判断是否有包子,决定当前线程是否执行

    2.如果有包子,就进入等待状态,如果没有包子,继续执行,生产包子

    3.生产包子之后,更新桌子上包子状态,唤醒消费者消费包子

  • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

    1.判断是否有包子,决定当前线程是否执行

    2.如果没有包子,就进入等待状态,如果有包子,就消费包子

    3.消费包子后,更新桌子上包子状态,唤醒生产者生产包子

  • 测试类(Demo):里面有main方法,main方法中的代码步骤如下

    创建生产者线程和消费者线程对象

    分别开启两个线程

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

代码实现

package com.itheima.threaddemo014;

public class Desk {

    //定义一个标记
//true 就表示桌子上有汉堡包的,此时允许吃货执行
//false 就表示桌子上没有汉堡包的,此时允许厨师执行
public static boolean flag = false; //汉堡包的总数量
public static int count = 10; //锁对象
public static final Object lock = new Object();
} package com.itheima.threaddemo014; public class Cooker extends Thread {
// 生产者步骤:
// 1,判断桌子上是否有汉堡包
// 如果有就等待,如果没有才生产。
// 2,把汉堡包放在桌子上。
// 3,叫醒等待的消费者开吃。
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
if(!Desk.flag){
//生产
System.out.println("厨师正在生产汉堡包");
Desk.flag = true;
Desk.lock.notifyAll();
}else{
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
} package com.itheima.threaddemo014; public class Foodie extends Thread {
@Override
public void run() {
// 1,判断桌子上是否有汉堡包。
// 2,如果没有就等待。
// 3,如果有就开吃
// 4,吃完之后,桌子上的汉堡包就没有了
// 叫醒等待的生产者继续生产
// 汉堡包的总数量减一 //套路:
//1. while(true)死循环
//2. synchronized 锁,锁对象要唯一
//3. 判断,共享数据是否结束. 结束
//4. 判断,共享数据是否结束. 没有结束
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else{
if(Desk.flag){
//有
System.out.println("吃货在吃汉堡包");
Desk.flag = false;
Desk.lock.notifyAll();
Desk.count--;
}else{
//没有就等待
//使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
} }
}

5.3生产者和消费者案例优化【难点】

  • 需求

    • 将Desk类中的变量,采用面向对象的方式封装起来

    • 生产者和消费者类中构造方法接收Desk类对象,之后在run方法中进行使用

    • 创建生产者和消费者线程对象,构造方法中传入Desk类对象

    • 开启两个线程

  • 代码实现

    package com.itheima.threaddemo015;
    
    public class Desk {
    
        //定义一个标记
    //true 就表示桌子上有汉堡包的,此时允许吃货执行
    //false 就表示桌子上没有汉堡包的,此时允许厨师执行
    //public static boolean flag = false;
    private boolean flag; //汉堡包的总数量
    //public static int count = 10;
    //以后我们在使用这种必须有默认值的变量
    // private int count = 10;
    private int count; //锁对象
    //public static final Object lock = new Object();
    private final Object lock = new Object(); public Desk() {
    this(false,10);
    } public Desk(boolean flag, int count) {
    this.flag = flag;
    this.count = count;
    } public boolean isFlag() {
    return flag;
    } public void setFlag(boolean flag) {
    this.flag = flag;
    } public int getCount() {
    return count;
    } public void setCount(int count) {
    this.count = count;
    } public Object getLock() {
    return lock;
    } @Override
    public String toString() {
    return "Desk{" +
    "flag=" + flag +
    ", count=" + count +
    ", lock=" + lock +
    '}';
    }
    } package com.itheima.threaddemo015; public class Cooker extends Thread { private Desk desk; public Cooker(Desk desk) {
    this.desk = desk;
    }
    // 生产者步骤:
    // 1,判断桌子上是否有汉堡包
    // 如果有就等待,如果没有才生产。
    // 2,把汉堡包放在桌子上。
    // 3,叫醒等待的消费者开吃。 //套路:
    //1. while(true)死循环
    //2. synchronized 锁,锁对象要唯一
    //3. 判断,共享数据是否结束. 结束
    //4. 判断,共享数据是否结束. 没有结束
    @Override
    public void run() {
    while(true){
    synchronized (desk.getLock()){
    if(desk.getCount() == 0){
    break;
    }else{
    //System.out.println("验证一下是否执行了");
    if(!desk.isFlag()){
    //生产
    System.out.println("厨师正在生产汉堡包");
    desk.setFlag(true);
    desk.getLock().notifyAll();
    }else{
    try {
    desk.getLock().wait();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }
    }
    } package com.itheima.threaddemo015; public class Desk { //定义一个标记
    //true 就表示桌子上有汉堡包的,此时允许吃货执行
    //false 就表示桌子上没有汉堡包的,此时允许厨师执行
    //public static boolean flag = false;
    private boolean flag; //汉堡包的总数量
    //public static int count = 10;
    //以后我们在使用这种必须有默认值的变量
    // private int count = 10;
    private int count; //锁对象
    //public static final Object lock = new Object();
    private final Object lock = new Object(); public Desk() {
    this(false,10);
    } public Desk(boolean flag, int count) {
    this.flag = flag;
    this.count = count;
    } public boolean isFlag() {
    return flag;
    } public void setFlag(boolean flag) {
    this.flag = flag;
    } public int getCount() {
    return count;
    } public void setCount(int count) {
    this.count = count;
    } public Object getLock() {
    return lock;
    } @Override
    public String toString() {
    return "Desk{" +
    "flag=" + flag +
    ", count=" + count +
    ", lock=" + lock +
    '}';
    }
    } package com.itheima.threaddemo015; public class Foodie extends Thread {
    private Desk desk; public Foodie(Desk desk) {
    this.desk = desk;
    } @Override
    public void run() {
    // 1,判断桌子上是否有汉堡包。
    // 2,如果没有就等待。
    // 3,如果有就开吃
    // 4,吃完之后,桌子上的汉堡包就没有了
    // 叫醒等待的生产者继续生产
    // 汉堡包的总数量减一 //套路:
    //1. while(true)死循环
    //2. synchronized 锁,锁对象要唯一
    //3. 判断,共享数据是否结束. 结束
    //4. 判断,共享数据是否结束. 没有结束
    while(true){
    synchronized (desk.getLock()){
    if(desk.getCount() == 0){
    break;
    }else{
    //System.out.println("验证一下是否执行了");
    if(desk.isFlag()){
    //有
    System.out.println("吃货在吃汉堡包");
    desk.setFlag(false);
    desk.getLock().notifyAll();
    desk.setCount(desk.getCount() - 1);
    }else{
    //没有就等待
    //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
    try {
    desk.getLock().wait();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }
    } }
    }

5.4阻塞队列基本使用【重点】

(共4点)

1.什么是阻塞队列?

阻塞队列是一个在队列基础上又支持了两个附加操作的队列

2.附加的方法

put(anObject): 将参数放入队列,如果放不进去会阻塞

take(): 取出第一个数据,取不到会阻塞

3.阻塞队列继承结构

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

4.常见BlockingQueue实现类

ArrayBlockingQueue: 底层是数组,有界

LinkedBlockingQueue: 底层是链表,*.但不是真正的*,最大为int的最大值

代码示例

package com.itheima.threaddemo016;

import java.util.concurrent.ArrayBlockingQueue;

public class Demo {
public static void main(String[] args) throws InterruptedException {
// 创建阻塞队列的对象,容量为 1
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1); // 存储元素
arrayBlockingQueue.put("汉堡包"); // 取元素
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞 System.out.println("程序结束了");
}
}

5.5阻塞队列实现等待唤醒机制【难点】

案例需求

  • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

    1.构造方法中接收一个阻塞队列对象

    2.在run方法中循环向阻塞队列中添加包子

    3.打印添加结果

  • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

    1.构造方法中接收一个阻塞队列对象

    2.在run方法中循环获取阻塞队列中的包子

    3.打印获取结果

  • 测试类(Demo):里面有main方法,main方法中的代码步骤如下

    创建阻塞队列对象

    创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象

    分别开启两个线程

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

代码实现

package com.itheima.threaddemo016;

import java.util.concurrent.ArrayBlockingQueue;

public class Demo {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);
Foodie f = new Foodie(bd);
Cooker c = new Cooker(bd);
f.start();
c.start();
}
}
package com.itheima.threaddemo016;

import com.itheima.threaddemo015.Desk;

import java.util.concurrent.ArrayBlockingQueue;

public class Cooker extends Thread {

    private ArrayBlockingQueue<String> bd;

    public Cooker(ArrayBlockingQueue<String> bd) {
this.bd = bd;
}
@Override
public void run() {
while (true) {
try {
bd.put("汉堡包");
System.out.println("厨师放入一个汉堡包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.itheima.threaddemo016;

import com.itheima.threaddemo015.Desk;

import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread {
private ArrayBlockingQueue<String> bd; public Foodie(ArrayBlockingQueue<String> bd) {
this.bd = bd;
} @Override
public void run() {
while (true) {
try {
String take = bd.take();
System.out.println("吃货将" + take + "拿出来吃了");
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
}

总结:

在java中如何实现等待和唤醒机制?

1.wait和notify()或notifyAll()

wait:让线程等待,同时立即释放锁 --->sleep():让线程休眠,但是不会释放锁

notify()或notifyAll(): 唤醒等待的线程,但是不会立即释放锁

2.阻塞队列

put:存,如果队例满的话,会阻塞(等待)

take:取,如果取不到,会阻塞(等待)

                        扩展练习

题目1

编写程序,创建两个线程对象,一根线程循环输出“播放背景音乐”,另一根线程循环输出“显示画面”; 要求:

1: 1个线程使用Runnable接口的匿名内部类实现

2: 另一个线程使用lambda实现

效果:

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

参考答案:

package day13.No_1;

public class Demo {
   public static void main(String[] args) {
       //匿名内部类实现
       new Thread(new Runnable() {
           @Override
           public void run() {
               while (true) {
                   System.out.println("播放背景音乐!");
              }
          }
      }).start();
       //lambda实现
       new Thread(() -> {
           while (true) {
               System.out.println("显示画面!");
          }
      }).start();
  }
}

题目2

3.请使用继承Thread类的方式定义一个线程类,在run()方法中循环10次,每1秒循环1次,每次循环按“yyyy-MM-dd HH:mm:ss”的格式打印当前系统时间。 请定义测试类,并定义main()方法,启动此线程,观察控制台打印。

要求:

1: 使用匿名内部类配合SimpleDateFormat和Date实现

2: 使用lambda配合LocalDateTime和DateTimeFormatter实现

效果:

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

参考答案:

package day13.No_2;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;

public class Demo {
   public static void main(String[] args) {
 /*
 //方式一内部类实现
       new Thread(new Runnable() {
           @Override
           public void run() {
               for (int i = 0; i < 10; i++) {
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   Date date = new Date();
                   SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                   String format = sdf.format(date);
                   System.out.println(format);
               }
           }
       }).start();
       */
 //方式二:lambda实现
       new Thread(()->{
           for (int i = 0; i < 10; i++) {
               try {
                   Thread.sleep(1000);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               LocalDateTime now = LocalDateTime.now();
               DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
               String format = dateTimeFormatter.format(now);
               System.out.println(format);
          }
      }).start();
  }
}

运行效果:

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

题目3

请编写多线程应用程序,模拟多个人通过一个山洞: (1).这个山洞每次只能通过一个人,每个人通过山洞的时间为1秒; (2).创建10个线程,同时准备过此山洞,并且定义一个变量用于记录通过隧道的人数。显示每次通过山洞人的姓名,和通过顺序;

要求:

保证安全问题,不能出现多个人同时通过山洞的现象;(必须逐一通过)

效果:

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

参考答案:

线程文件:

package day13.No_3;

public class MyRunnable implements Runnable {
   private static int count = 10;
   private final static Object obj = new Object();
   private static int i = 1;

   @Override
   public void run() {
       synchronized (obj) {
           try {
               Thread.sleep(1000);
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
           count--;
           System.out.println(Thread.currentThread().getName() + "通过山洞,他是第" + i + "个通过");
           i++;
      }
  }
}


测试文件:

package day13.No_3;

public class Demo {
   public static void main(String[] args) {
     //此处可以使用for循环创建线程
       MyRunnable mr = new MyRunnable();
       Thread tr1 = new Thread(mr, "线程1");
       tr1.start();
       Thread tr2 = new Thread(mr, "线程2");
       tr2.start();
       Thread tr3 = new Thread(mr, "线程3");
       tr3.start();
       Thread tr4 = new Thread(mr, "线程4");
       tr4.start();
       Thread tr5 = new Thread(mr, "线程5");
       tr5.start();
       Thread tr6 = new Thread(mr, "线程6");
       tr6.start();
       Thread tr7 = new Thread(mr, "线程7");
       tr7.start();
       Thread tr8 = new Thread(mr, "线程8");
       tr8.start();
       Thread tr9 = new Thread(mr, "线程9");
       tr9.start();
       Thread tr10 = new Thread(mr, "线程10");
       tr10.start();

  }
}

 运行效果:

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

题目4

拼手速抽奖案例.

1.现有一个集合装了10个奖品在里面,分别是:{"电视机","电冰箱","电脑","游戏机","洗衣机","空调","手机","平板电脑","电动车","电饭煲"};

2.假如有3个人同时去抽这10个奖品.最后打印出来.三个人各自都抽到了什么奖品.

例如:

张三: “电视机”,”电冰箱”,”电脑”,”游戏机”,”洗衣机”

李四: ”空调”,”手机”,”平板电脑”,

王五: ”电动车”,”电饭煲

要求:

1:3个人同时开始抽奖,每次抽奖需要使用0.5秒才能完成抽奖;

2:需要控制住同一个奖项不能同时被多个人抽走;

效果:

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

参考答案:

线程类

package day13.No_4;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class MyRunnable implements Runnable {
   private static ArrayList<String>list=new ArrayList<>(List.of("电视机","电冰箱","电脑","游戏机","洗衣机","空调","手机","平板电脑","电动车","电饭煲"));
   private static Object object=new Object();
   @Override
   public void run() {
       while (list.size()>0){
           synchronized (object){
               if (list.size()==0){
                   return;
              }
               try {
                   Thread.sleep(500);
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
               String name = list.remove(new Random().nextInt(list.size()));
               System.out.println(Thread.currentThread().getName()+"抽到了"+name);
          }
      }
  }
}

测试类:

package day13.No_4;

public class Demo {
   public static void main(String[] args) {
       MyRunnable mr = new MyRunnable();
       Thread t1 = new Thread(mr, "张三");
       Thread t2 = new Thread(mr, "李四");
       Thread t3 = new Thread(mr, "王五");
       t1.start();
       t2.start();
       t3.start();
  }
}

运行效果:

 

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?

多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?的更多相关文章

  1. 0036 Java学习笔记-多线程-创建线程的三种方式

    创建线程 创建线程的三种方式: 继承java.lang.Thread 实现java.lang.Runnable接口 实现java.util.concurrent.Callable接口 所有的线程对象都 ...

  2. Java并发编程:Java创建线程的三种方式

    目录 引言 创建线程的三种方式 一.继承Thread类 二.实现Runnable接口 三.使用Callable和Future创建线程 三种方式的对比 引言 在日常开发工作中,多线程开发可以说是必备技能 ...

  3. java创建线程的三种方式及其对比

    第一种方法:继承Thread类,重写run()方法,run()方法代表线程要执行的任务.第二种方法:实现Runnable接口,重写run()方法,run()方法代表线程要执行的任务.第三种方法:实现c ...

  4. AJPFX总结java创建线程的三种方式及其对比

    Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行 ...

  5. java创建线程的三种方式及其对照

    Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类.并重写该类的run方法,该run方法的方法体就代表了线程要完毕的任务.因此把run()方法称为运行 ...

  6. Java实现线程的三种方式和区别

    Java实现线程的三种方式和区别 Java实现线程的三种方式: 继承Thread 实现Runnable接口 实现Callable接口 区别: 第一种方式继承Thread就不能继承其他类了,后面两种可以 ...

  7. 如何实现有返回值的多线程 JAVA多线程实现的三种方式

    可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口.执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable ...

  8. Java创建线程的三种方式

    一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行体. (2)创建Thread子类的实 ...

  9. Java线程的三种方式

    创建线程有三种方式: 1.继承Thread类 2.实现Runnable接口 3.使用Callable和Future创建线程 三种方式详解如下: ---------------------------- ...

随机推荐

  1. OpenStack nova VM migration &lpar;live and cold&rpar; call flow

    OpenStack nova compute supports two flavors of Virtual Machine (VM) migration: Cold migration -- mig ...

  2. DBA&lowbar;Oracle AWR Report性能监控报表(案例)

    2014-08-22 Created By BaoXinjian

  3. jq实现地址级联效果

    (function ($) { $.fn.Address = function (options) { var defaults = { divid: "Address", cal ...

  4. 【intellij】异常信息汇总

    Application Server was not connected before run configuration stop, reason: javax.management.Instanc ...

  5. SOJ 1210 二叉树

    1210. 二叉树 Constraints Time Limit: 1 secs, Memory Limit: 32 MB Description 在众多的数据结构中,二叉树是一种特殊而重要的结构,有 ...

  6. &period;NET平台的ORM分析工具

    众所周知,“ORM”和“性能问题”常常一起出现.ORM通过对开发人员隐藏SQL细节可以大大的提高生产力.然而,它们很容易产生一些未被发现的荒谬查询.通常情况下,数据库管理员可以通过交叉引用有问题的存储 ...

  7. GZIP压缩与解压

    public class GZIP { /** * 字符串的压缩 * * @param str * 待压缩的字符串 * @return 返回压缩后的字符串 * @throws IOException ...

  8. sublime text 3 ,React,html元素自动补全方法(用Emmet写法写jsx中的html)

    1. 安装emmet: Preferences -> Package Control -> Install Package -> emmet 2. 配置emmet: Preferen ...

  9. Laravel篇二之本地版本库关联github

    以往的工作中都是使用svn作为版本控制,对git分布式的有些陌生,本篇主要记录的本地存储myWeb-laravel的git版本库与github建立关联. 1.首先进入本地myWeb-laravel,执 ...

  10. Android相关 博客收藏

    #1 Android 网络编程 参考博客 :http://blog.csdn.net/kieven2008/article/details/8210737 #2 Could not find com. ...