线程基础1

目录

一、 实现多线程方式

1.1继承Thread类

1.2实现Runnable接口

1.3实现Callable接口

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

2.1设置和获取线程名称

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

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

四、守护线程 

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

卖票案例的问题

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

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

同步方法的格式

静态同步方法 

 Lock锁

死锁

 等待唤醒机制

 利用阻塞队列实现等待唤醒机制

阻塞队列实现等待唤醒机制 



并发和并行
并行∶在同一时刻,有多个指令在多个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 MyRunnable implements Runnable {
    private  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;
        }
    }

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()唤醒正在等待对象监视器的所有线程

wait:假如某条线程抢到了执行权,遇到wait就会进入等待模式,让出cpu执行权。 

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

    }
}

优化 案例代码

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 +
                '}';
    }
}

public class Cooker extends Thread {

    private Desk desk;

    public Cooker(Desk desk) {
        this.desk = desk;
    }
//    生产者步骤:
//            1,判断桌子上是否有汉堡包
//    如果有就等待,如果没有才生产。
//            2,把汉堡包放在桌子上。
//            3,叫醒等待的消费者开吃。

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

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

    }
}

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

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

        Desk desk = new Desk();

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

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

    }
}

 利用阻塞队列实现等待唤醒机制

【使代码更加简洁,减少自己书写难度】

在两者之间创建一个队列【容器】,将元素都放入其中,在里面进行存取操作 

  • 阻塞队列继承结构

  • 常见BlockingQueue:

    ArrayBlockingQueue: 底层是数组,有界【表示阻塞队列里的内容是有限的】

    LinkedBlockingQueue: 底层是链表,无界.但不是真正的无界,最大为int范围的最大值65535

  • BlockingQueue的核心方法:

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

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

public class Demo02 {
    public static void main(String[] args) throws Exception {
        // 创建阻塞队列的对象,容量为 1【“这个通道”只有一个坑位,只能存一个内容】
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);

        // 存储元素
        arrayBlockingQueue.put("汉堡包");

        // 取元素
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take()); // 取不到东西,会阻塞(等着)【通道内只有一个内容,已经被取走了,程序进入阻塞状态】

        System.out.println("程序结束了");
    }
}

执行结果:
汉堡包
【程序结束了 并未被打印出来,程序进入等待状态,程序没有停下来】

阻塞队列实现等待唤醒机制 

public class Cooker extends Thread {

    private ArrayBlockingQueue<String> bd;

    public Cooker(ArrayBlockingQueue<String> bd) {
        this.bd = bd;
    }
//    生产者步骤:
//            1,判断桌子上是否有汉堡包
//    如果有就等待,如果没有才生产。
//            2,把汉堡包放在桌子上。
//            3,叫醒等待的消费者开吃。

    @Override
    public void run() {
        while (true) {
            try {
                bd.put("汉堡包");
                System.out.println("厨师放入一个汉堡包");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Foodie extends Thread {
    private ArrayBlockingQueue<String> bd;

    public Foodie(ArrayBlockingQueue<String> bd) {
        this.bd = bd;
    }

    @Override
    public void run() {
//        1,判断桌子上是否有汉堡包。
//        2,如果没有就等待。
//        3,如果有就开吃
//        4,吃完之后,桌子上的汉堡包就没有了
//                叫醒等待的生产者继续生产
//        汉堡包的总数量减一

        //套路:
        //1. while(true)死循环
        //2. synchronized 锁,锁对象要唯一
        //3. 判断,共享数据是否结束. 结束
        //4. 判断,共享数据是否结束. 没有结束
        while (true) {
            try {
                String take = bd.take();
                System.out.println("吃货将" + take + "拿出来吃了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

public class Demo {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值