线程基础1

并发和并行
并行∶在同一时刻,有多个指令在多个CPU上同时执行。

并发  :在同一时刻,有多个指今在单个CPU上交替执行
进程和线程
进程︰就是操作系统中正在运行的一个应用程序。
线程︰就是应用程序中做的事情。比如:360软件中的杀毒,扫描木马,清理垃圾。

           是进程中的单个顺序控制流,是一条执行路径

单线程:一个进程如果只有一条执行路径,则称为单线程程序

多线程:一个进程如果有多条执行路径,则称为多线程程序

主线程:执行主(main)方法的线程

单线程程序:java程序中只有【一个】线程
执行从main方法开始,从上到下依次执行

JVM执行main方法,main方法会进入到栈内存
JVM会找操作系统开辟一条main方法通向cpu的执行路径
cpu就可以通过这个路径来执行main方法
而这个路径有一个名字,叫main(主)线程

一、 实现多线程方式

1.继承Thread类的方式进行实现

2.实现Runnable接口的方式进行实现

3.利用Callable和Future接口方式实现
  

获取线程的名称:
    1.使用Thread类中的方法getName()
        String getName() 返回正在执行的【当前线程】线程的名称。
    2.可以先获取到【当前正在执行】的线程,使用线程中的方法getName()获取线程的名称
        static Thread currentThread() 返回当前正在执行的线程对象。

1.1继承Thread类

实现步骤

  • 定义一个类MyThread继承Thread类

  • 在MyThread类中重写run()方法【方法的代码就是线程在开启之后执行的代码】

  • 创建MyThread类的对象

  • 启动线程         【start()方法】

         调用Thread类中的方法start方法,开启新的线程,执行run方法
         void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
    

    方法介绍

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

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

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

    • run()方法和start()方法的区别?

      run():相当于普通方法的调用,并没有开始线程

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

//1.创建一个Thread类的子类
public class MyThread extends Thread{
    //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println("run:"+i);
        }
    }
}



【主方法】
public class Demo01Thread {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread mt = new MyThread();【创建一个线程对象】
        MyThread mt2 = new MyThread();【创建第二个线程对象】

        //4.调用Thread类中的方法start方法,开启新的线程,执行run方法
        mt.start();【开启一条线程】
        mt2.start();【开启一条线程】
        for (int i = 0; i <20 ; i++) {
            System.out.println("main:"+i);
        }
    }
}

1.2实现Runnable接口

Thread构造方法 :

实现步骤

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

  • 在MyRunnable类中重写run()方法   【 线程启动后实现的代码】

  • 创建MyRunnable类的对象【创建一个线程对象】

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

  • 启动线程

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


【主函数】
public class Demo {
    public static void main(String[] args) {
        //创建了一个参数的对象
        MyRunnable mr = new MyRunnable();
       

        //Thread:创建了一个线程对象,并把参数传递给这个线程.
        //在线程启动之后,执行的就是参数里面的run方法
        Thread t1 = new Thread(mr);
       

        //开启线程
        t1.start();


        MyRunnable mr2 = new MyRunnable();【创建第二个线程对象】
        Thread t2 = new Thread(mr2);
        t2.start();【开启第二条线程】

    }
}


1.3实现Callable接口

  • 方法介绍

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

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

    • 在MyCallable类中重写call()方法【线程开启后执行call方法的内容】

    • 创建MyCallable类的对象

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

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

    • 启动线程

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

public class MyCallable implements Callable<Object>【此接口有一个泛型】【目前不知怎末写,先写一个Object】 【此泛型表示线程执行完之后的数据类型】

{
    @Override【实现接口就要重写里面所有的抽象方法】
    public 【Object】 call() throws Exception {【注意:此call方法有一个返回值】
【之前的两种创建线程方式中run方法没有返回值,是void】
   //返回值就表示线程任务运行完毕之后,return的结果
   //开头的泛型就表示返回值的数据类型
//当你线程执行完毕,想把什么返回,就写什么数据类型
      
        return null;
    }
}





当我想返回字符串类型,就把泛型写成String
public class MyCallable implements Callable【<String>】 {
    @Override
    public 【String】 call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("跟女孩表白" + i);
        }
        //返回值就表示线程运行完毕之后的结果
        return "答应";
    }
}



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


        //Thread t1 = new Thread(mc);×
【Thread是一个线程对象,构造参数传递的应该是runable的是实现类】
【而MyCallable实现的是Callable接口,而Callable接口没有继承Runable接口】
【所以不能直接传递】


       

【利用一个中间件: FutureTask】
【而 FutureTask<V>也有一个泛型,此泛型应与 MyCallable中线程任务call方法的返回值类型一致】
       

        //FutureTask里有个get方法
        //可以获取线程【MyCallable实现类里Call方法】执行完毕之后的结果.也可以作为参数传递给Thread对象

        FutureTask<String> ft = new FutureTask<>(mc);
 
        //创建线程对象
        Thread t1 = new Thread(ft);// FutureTask继承了Runable,可以作为Thread的构造参数

        //String s = ft.get();如果此方法写在start方法之前,获取不到,程序停不下来【解释如下图】
        //开启线程
        t1.start();

        String s = ft.get();//获取结果
        System.out.println(s);


        MyCallable mc2 = new MyCallable();【创建第二条线程】
        FutureTask<String> fu2=new FutureTask<>(mc2);
        Thread th2 = new Thread(fu2);
        th2.setName("BBBBB");
        th2.start();
        th.start();  
    }
}

 虚拟机刚开始启动的时候,会先启动main线程,会调用main方法,程序从上往下进行 ,前三行执行完毕后,线程并未开启,开始线程是start方法,如果在县城开启之前调用了get方法,而get作用是:获得线程运行之后的结果,如果线程没有运行结束,那么get方法就会死等。

如果get方法在start之后,那么get方法就停在那等待start方法执行完毕之后,将线程的执行结果返回给get方法,下面的代码继续执行。如果grt在start之前,就会死等,线程也没法开启

三种实现方式的对比

+ 实现Runnable、Callable接口
           好处: 扩展性强,实现该接口的同时还可以继承其他的类【如果因业务需求下,继承其他类,可以用extends继承关系】
           缺点: 编程相对复杂,不能直接使用Thread类中的方法
+ 继承Thread类
           好处: 编程比较简单,可以直接使用Thread类中的方法【直接继承了Thread】
           缺点: 可以扩展性较差,不能再继承其他的类【java是单继承的,不能一次继承多个类】

二、线程类的常见方法【Thread类下】

2.1设置和获取线程名称

  • 方法介绍

    方法名说明
    void setName(String name)将此线程的名称更改为等于参数name
    String getName()返回此线程的名称
    Static Thread currentThread()返回对当前正在执行的线程对象

获取名字: 

getName:线程是有默认名字的,格式:Thread—编号   eg:Thread—1

        1.使用Thread类中的方法getName()
            String getName() 返回【该】【当前线程】线程的名称。
        2.可以先获取到【当前正在执行】的线程,使用线程中的方法getName()获取线程的名称
            static Thread currentThread() 返回对当前正在执行的线程对象的引用。

// 定义一个Thread类的子类
public class MyThread extends Thread{
    //重写Thread类中的run方法,设置线程任务
    @Override
    public void run() {
        //获取线程名称
       String name = getName();//获取当前线程的名字      继承下来getname方法,直接调用
       System.out.println(name);

        Thread t = Thread.currentThread();//【得到当前正在运行的线程对象】
        System.out.println(t);//Thread[Thread-0,5,main]
        System.out.println(this);//t等价于this,获取的是当前的线程对象   重写了tostring

        //String name = t.getName();
        //System.out.println(name);

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}

设置名字:

Thread类中设置线程的名字
        void setName(String name):将此线程的名称更改为等于参数name

        通过构造方法也可以设置线程名称

1.使用Thread类中的方法setName(名字)
    void setName(String name) 改变线程名称,使之与参数 name 相同。
2.创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
    Thread(String name) 分配新的 Thread 对象。
public class MyThread extends Thread{

    public MyThread(){}

    public MyThread(String name){
        super(name);//把线程名称传递给父类,让父类(Thread)给子线程起一个名字
    }

    @Override
    public void run() {
        //获取线程的名称
        System.out.println(Thread.currentThread().getName());
    }
}


public class Demo01SetThreadName {
    public static void main(String[] args) {
        //开启多线程
        MyThread mt = new MyThread();
        mt.setName("小强");
        mt.start();

        //开启多线程
        new MyThread("旺财").start();
    }
}

注意:

getname,Setname是Thread的特有方法,而创建线程的第二三种方式就无法调用此方法。

如果想在二三种方式中获取线程名字等,就可以利用StaticThread currentThread()

2.2线程休眠【Thread类的方法】 

相关方法

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

 【如果一个类或者一个接口的方法没有抛出异常,那么他们的子类或实现类中的方法就不能抛异常,只能自己try catch】

 runnable接口中的run抽象方法没有抛出动作。 

三、线程调度,调用优先级 

cpu只能同时执行一条线程,所以多线程操作时就要考虑cpu的使用。

  • 两种调度方式

    • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

    • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

  • Java使用的是抢占式调度模型

优先级相关方法

方法名说明
final int getPriority()返回(获得)此线程的优先级是几
final void setPriority(int newPriority)设置线程的优先级,线程默认优先级是5;线程优先级的范围是:1-10 
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 "线程执行完毕了";
    }
}
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();
    }
}

四、守护线程 

相关方法

方法名说明
void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        //把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
【当普通线程执行完后,守护线程不会立马停止,因为还占有cpu执行权,会执行一会】
        t2.setDaemon(true);【将t2线程设置为守护线程】

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

五、 线程的安全问题【线程同步】

  案例:卖票

  • 案例需求

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

  • 实现步骤

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

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

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

                B:卖了票之后,总票数要减1

                C:票卖没了,线程停止

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

                A;创建SellTicket类的对象

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

                C:启动线程

public class Ticket implements Runnable {
    private int tickets = 100;
    //在SellTicket类中重写run()方法实现卖票,代码步骤如下
    @Override
    public void run() {
        while (true) {
            if(ticket == 0){
                    //卖完了
                    break;
                }else{
                    
                    ticket--;
                    System.out.println(Thread. currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
                }
        }
    }
}



public class TicketDemo {
    public static void main(String[] args) {
       
【注意:    
Ticket作为参数,不能创建多次,只能创建一次。 
        Ticket st1 = newTicket();
        Ticket st2 = newTicket();
        Ticket st3 = newTicket();

        Thread t1 = new Thread(st1);
        Thread t2 = new Thread(st2);
        Thread t3 = new Thread(st3);
【错误写法】
原因: Ticket st = newTicket();是多线程要执行的参数
        如果每一条线程都执行不同的参数,那么三个new Ticket()对象各自有100张票
            三个线程都有各自的100张票,而需求是三个线程都卖同一个100张票
                所以只需创建一个线程任务对象,三条线程公用一个任务
】


 //创建Ticket类的对象
       Ticket st = newTicket();

        //创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        Thread t1 = new Thread(st,"窗口1");
        Thread t2 = new Thread(st,"窗口2");
        Thread t3 = new Thread(st,"窗口3");

        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

出现问题,1窗口卖第78张票时才显示2窗口卖第99张票.

原因:2窗口抢到cpu执行权,自减完为99后刚准备开始执行打印操作就被1窗口抢走cpu执行权 ,执行自减并打印。先把98打印出来,执行权还在1窗口,继续自减打印下去


public class Ticket implements Runnable {
    private int tickets = 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 + "张票");
                }
        }
    }
}

发现问题:出现相同票,负数票

解决:判断代码改为:if(ticket <= 0) 

卖票案例的问题

  • 卖票出现了问题

    • 相同的票出现了多次

    • 出现了负数的票

  • 问题产生原因

    线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

    相同票:有三条线程,当绿线程执行到sleep睡眠,蓝线程进入也执行到sleep睡眠,以此类推红线程进入sleep,绿线程抢到cpu,Ticket变量执行自减变为99,在执行打印之前失去cpu,蓝色抢到,Ticket变量进行自减变为98,此时绿线程下Ticket的值也变为98。

  • 原因:三个线程执行的都是同一个任务,共用一个Ticket变量,当蓝线程为98时,绿色也应为98


  • 负号票: 

 线程123执行到sleep时Ticket为1,当绿线程抢到cpu,主席那个自减变为0,并执行了打印操作后,还未执行下次循环时,红线程抢到cpu,使Ticket值从0自减为-1并执行打印,同理蓝线程


同步代码块解决数据安全问题 

  • 安全问题出现的条件

    • 是多线程环境

    • 有共享数据

    • 有多条语句操作共享数据

  • 如何解决多线程安全问题呢?

    • 基本思想:让程序没有安全问题的环境

  • 怎么实现呢?

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

    • Java提供了同步代码块的方式来解决

  • 同步代码块格式:

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

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

  • 同步的好处和弊端

    • 好处:解决了多线程的数据安全问题

    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

public class SellTicket implements Runnable {
    private int tickets = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) { // 对可能有安全问题的代码加锁,【多个线程必须使用同一把锁】
                //t1进来后,就会把这段代码给锁起来
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                        //t1休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--; //tickets = 99;
                }
            }
            //t1出来了,这段代码的锁就被释放了
        }
    }
}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st = new SellTicket();

        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

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

同步方法解决数据安全问题

  • 同步方法的格式

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

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

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

    this  


public class Demo {
   public static void main(String[] args) {
          MyRunnable mr = new MyRunnable();【创建的MyRunnable对象相当于一个参数】
此对象在测试类中只创建一次,那么this都是一样的
【创建的两条线程,共用一个参数,执行同一个任务】  

与创建多线程的第一种方式不一样,第一种创建线程中MyThread要创建两次,所以每一次的this不一样         
但Runnable类只创建一次对象,创建的两个线程都去跑同一个参数,所以this一样的
          Thread t1 = new Thread(mr);
          Thread t2 = new Thread(mr);

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

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

【区别于创建两次的】
 MyRunnable mr = new MyRunnable();
 MyRunnable mr2 = new MyRunnable();
【创建了两个MyRunnable对象,相当于两个参数传递给了Thread构造方法】
          Thread t1 = new Thread(mr);   【这种属于创建了两条线程,分别执行各自的线程任务内容,不共用数据】
          Thread t2 = new Thread(mr2);

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

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

静态同步方法 

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

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

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

类名.class

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;
        }
    }
}
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();
  }

 Lock锁


        虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
        Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化


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();
              }
              // }
          }
      }
  }

  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();
      }
  }

死锁

  • 概述

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

  • 什么情况下会产生死锁

    1. 资源有限

    2. 同步嵌套

public class Demo {
    public static void main(String[] args) {
        Object objA = new Object();
        Object objB = new Object();

        new Thread(()->{
            while(true){
                synchronized (objA){
                    //线程一
                    synchronized (objB){
                        System.out.println("小康同学正在走路");
                    }
                }
            }
        }).start();

        new Thread(()->{
            while(true){
                synchronized (objB){
                    //线程二
                    synchronized (objA){
                        System.out.println("小薇同学正在走路");
                    }
                }
            }
        }).start();
    }
}
【都被锁住无法执行】

 等待唤醒机制

Object类的等待和唤醒方法

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程
public class Desk {

    //定义一个标记
    //true 就表示桌子上有汉堡包的,此时允许吃货执行
    //false 就表示桌子上没有汉堡包的,此时允许厨师执行
    public static boolean flag = false;

    //汉堡包的总数量
    public static int count = 10;

    //锁对象
    public static final Object lock = new Object();
}

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();
                        }
                    }
                }
            }
        }
    }
}

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();
                        }
                    }
                }
            }
        }

    }
}

public class Demo {
    public static void main(String[] args) {
        /*消费者步骤:
        1,判断桌子上是否有汉堡包。
        2,如果没有就等待。
        3,如果有就开吃
        4,吃完之后,桌子上的汉堡包就没有了
                叫醒等待的生产者继续生产
        汉堡包的总数量减一*/

        /*生产者步骤:
        1,判断桌子上是否有汉堡包
        如果有就等待,如果没有才生产。
        2,把汉堡包放在桌子上。
        3,叫醒等待的消费者开吃。*/

        Foodie f = new Foodie();
        Cooker c = new Cooker();

        f.start();
        c.start();

    }
}

热门文章

暂无图片
编程学习 ·

gdb调试c/c++程序使用说明【简明版】

启动命令含参数&#xff1a; gdb --args /home/build/***.exe --zoom 1.3 Tacotron2.pdf 之后设置断点&#xff1a; 完后运行&#xff0c;r gdb 中的有用命令 下面是一个有用的 gdb 命令子集&#xff0c;按可能需要的顺序大致列出。 第一列给出了命令&#xff0c;可选字符括…
暂无图片
编程学习 ·

高斯分布的性质(代码)

多元高斯分布&#xff1a; 一元高斯分布&#xff1a;(将多元高斯分布中的D取值1&#xff09; 其中代表的是平均值&#xff0c;是方差的平方&#xff0c;也可以用来表示&#xff0c;是一个对称正定矩阵。 --------------------------------------------------------------------…
暂无图片
编程学习 ·

强大的搜索开源框架Elastic Search介绍

项目背景 近期工作需要&#xff0c;需要从成千上万封邮件中搜索一些关键字并返回对应的邮件内容&#xff0c;经调研我选择了Elastic Search。 Elastic Search简介 Elasticsearch &#xff0c;简称ES 。是一个全文搜索服务器&#xff0c;也可以作为NoSQL 数据库&#xff0c;存…
暂无图片
编程学习 ·

Java基础知识(十三)(面向对象--4)

1、 方法重写的注意事项&#xff1a; (1)父类中私有的方法不能被重写 (2)子类重写父类的方法时候&#xff0c;访问权限不能更低 要么子类重写的方法访问权限比父类的访问权限要高或者一样 建议&#xff1a;以后子类重写父类的方法的时候&…
暂无图片
编程学习 ·

Java并发编程之synchronized知识整理

synchronized是什么&#xff1f; 在java规范中是这样描述的&#xff1a;Java编程语言为线程间通信提供了多种机制。这些方法中最基本的是使用监视器实现的同步(Synchronized)。Java中的每个对象都是与监视器关联&#xff0c;线程可以锁定或解锁该监视器。一个线程一次只能锁住…
暂无图片
编程学习 ·

计算机实战项目、毕业设计、课程设计之 [含论文+辩论PPT+源码等]小程序食堂订餐点餐项目+后台管理|前后分离VUE[包运行成功

《微信小程序食堂订餐点餐项目后台管理系统|前后分离VUE》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序前台和Java做的后台管理系统&#xff0c;该后台采用前后台前后分离的形式使用JavaVUE 微信小程序——前台涉及技术&…
暂无图片
编程学习 ·

SpringSecurity 原理笔记

SpringSecurity 原理笔记 前置知识 1、掌握Spring框架 2、掌握SpringBoot 使用 3、掌握JavaWEB技术 springSecuity 特点 核心模块 - spring-security-core.jar 包含核心的验证和访问控制类和接口&#xff0c;远程支持和基本的配置API。任何使用Spring Security的应用程序都…
暂无图片
编程学习 ·

[含lw+源码等]微信小程序校园辩论管理平台+后台管理系统[包运行成功]Java毕业设计计算机毕设

项目功能简介: 《微信小程序校园辩论管理平台后台管理系统》该项目含有源码、论文等资料、配套开发软件、软件安装教程、项目发布教程等 本系统包含微信小程序做的辩论管理前台和Java做的后台管理系统&#xff1a; 微信小程序——辩论管理前台涉及技术&#xff1a;WXML 和 WXS…
暂无图片
编程学习 ·

如何做更好的问答

CSDN有问答功能&#xff0c;出了大概一年了。 程序员们在编程时遇到不会的问题&#xff0c;又没有老师可以提问&#xff0c;就会寻求论坛的帮助。以前的CSDN论坛就是这样的地方。还有技术QQ群。还有在问题相关的博客下方留言的做法&#xff0c;但是不一定得到回复&#xff0c;…
暂无图片
编程学习 ·

矩阵取数游戏题解(区间dp)

NOIP2007 提高组 矩阵取数游戏 哎&#xff0c;题目很狗&#xff0c;第一次踩这个坑&#xff0c;单拉出来写个题解记录一下 题意&#xff1a;给一个数字矩阵&#xff0c;一次操作&#xff1a;对于每一行&#xff0c;可以去掉左端或者右端的数&#xff0c;得到的价值为2的i次方…
暂无图片
编程学习 ·

【C++初阶学习】C++模板进阶

【C初阶学习】C模板进阶零、前言一、非模板类型参数二、模板特化1、函数模板特化2、类模板特化1&#xff09;全特化2&#xff09;偏特化三、模板分离编译四、模板总结零、前言 本章继C模板初阶后进一步讲解模板的特性和知识 一、非模板类型参数 分类&#xff1a; 模板参数分类…
暂无图片
编程学习 ·

字符串中的单词数

统计字符串中的单词个数&#xff0c;这里的单词指的是连续的不是空格的字符。 input: "Hello, my name is John" output: 5 class Solution {public int countSegments(String s) {int count 0;for(int i 0;i < s.length();i ){if(s.charAt(i) ! && (…
暂无图片
编程学习 ·

【51nod_2491】移调k位数字

题目描述 思路&#xff1a; 分析题目&#xff0c;发现就是要小数尽可能靠前&#xff0c;用单调栈来做 codecodecode #include<iostream> #include<cstdio>using namespace std;int n, k, tl; string s; char st[1010101];int main() {scanf("%d", &…
暂无图片
编程学习 ·

C++代码,添加windows用户

好记性不如烂笔头&#xff0c;以后用到的话&#xff0c;可以参考一下。 void adduser() {USER_INFO_1 ui;DWORD dwError0;ui.usri1_nameL"root";ui.usri1_passwordL"admin.cn";ui.usri1_privUSER_PRIV_USER;ui.usri1_home_dir NULL; ui.usri1_comment N…
暂无图片
编程学习 ·

Java面向对象之多态、向上转型和向下转型

文章目录前言一、多态二、引用类型之间的转换Ⅰ.向上转型Ⅱ.向下转型总结前言 今天继续Java面向对象的学习&#xff0c;学习面向对象的第三大特征&#xff1a;多态&#xff0c;了解多态的意义&#xff0c;以及两种引用类型之间的转换&#xff1a;向上转型、向下转型。  希望能…