多线程

113 阅读9分钟

14.1 线程的基本概念

  • 程序:为了完成某个任务和功能,选择一种编程语言编写的一组指令的集合。
  • 软件1个或多个应用程序+相关的素材和资源文件等构成一个软件系统。
  • 进程:是指一个内存中运行的应用程序,每个进程都有一份独立的内存空间,进程也时程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即使一个进程从创建、运行到消亡的过程。
  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的其中一个任务的执行,一个进程中至少有一个线程。一个进程中事一个可以有多个线程的,这个应用程序称之为多线程程序。

简而言之:一个软件中至少有一个应用程序,应用程序的一次运行就是一个进程,一个进程中至少有一个线程。

14.2 线程的创建方式

14.2.1 继承Thread类

1、开启main线程以外的其他线程的方式:
(1)继承Thread类
(2)实现Runnable接口
(3)实现Callable接口
(4)线程池
​
2、继承Thread类
步骤:
(1)编写一个线程类(有名或匿名)继承Thread类
(2)重写父类的一个方法public void run(){}
run方法的方法体又被称为线程体,就是该线程要独立完成的任务。
(3)创建该线程类的对象
(4)调用该线程类对象的start方法
注意:这里不是直接调用run方法,而是start,
    start方法的作用是启动线程,之后让CPU决定什么时候调用run方法。
​
    如果我们程序员手动调用run方法,就不再是多线程,还是main方法单线程。
    
    理解为:start方法是通知“线程调度器”帮你调用你写的run方法。
​
​
public class MyThread extends Thread{
    @Override
    public void run() {
        //该线程的对象要完成打印1-100的偶数
        for(int i=2; i<=100; i+=2){
            System.out.println("自定义线程:" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
​
​
public class TestMyThread {
    public static void main(String[] args) {
        MyThread my = new MyThread();
//        my.run();//手动调用run,线性执行,先执行完my.run(),才能执行下面的输出1-100的奇数
        my.start();
​
        //在main中打印1-100的奇数
        for(int i=1; i<=100; i+=2){
            System.out.println("main线程:" + i);
        }
    }
}

14.2.2 实现Runnable接口

3、实现Runnable接口
步骤:
(1)编写一个线程类(有名或匿名)实现Runnable接口
(2)重写父接口的一个抽象方法public void run(){}
run方法的方法体又被称为线程体,就是该线程要独立完成的任务。
(3)创建该线程类的对象
(4)创建一个Thread类的对象,并且让它代理我们Runnable的线程类对象
(5)调用Thread类对象的start方法
​
        Thread t = new Thread(my);//my给t对象的target属性赋值
        t.start(); //t线程启动后,线程调度器会调用就t对象的run方法
​
分析:Thread类的run方法源码
    public void run() {
        if (target != null) {
            target.run();
        }
    }
​
理解为:start方法是通知“线程调度器”帮你调用你写的run方法。
​
​
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //该线程的对象要完成打印1-100的偶数
        for(int i=2; i<=100; i+=2){
            System.out.println("自定义线程:" + i);
        }
    }
}
​
​
​
public class TestMyRunnable {
    public static void main(String[] args) {
        MyRunnable my = new MyRunnable();
//        my.run();//错误
​
        Thread t = new Thread(my);
        t.start();
​
        System.out.println("----------------");
        //在main中打印1-100的奇数
        for(int i=1; i<=100; i+=2){
            System.out.println("main线程:" + i);
        }
    }
}
​

14.2.3 匿名内部类写法

​
import org.junit.Test;
​
public class TestCreateThread {
    public static void main(String[] args) {
        //匿名内部类继承Thread类
        new Thread(){
            @Override
            public void run() {
                for(int i=2; i<=1000; i+=2){
                    System.out.println("自定义线程偶数:" + i);
                }
            }
        }.start();
​
        //匿名内部类实现Runnable接口
        new Thread(new Runnable(){
            @Override
            public void run() {
                for(int i=1; i<=1000; i+=2){
                    System.out.println("自定义线程奇数:" + i);
                }
            }
        }).start();
​
        //这里有几个线程:3个
    }
​
    //测试多线程代码,不要用JUnit,使用main线程测试
    @Test
    public void test(){
        //匿名内部类继承Thread类
        new Thread(){
            @Override
            public void run() {
                for(int i=2; i<=1000; i+=2){
                    System.out.println("自定义线程偶数:" + i);
                }
            }
        }.start();
​
        //匿名内部类实现Runnable接口
        new Thread(new Runnable(){
            @Override
            public void run() {
                for(int i=1; i<=1000; i+=2){
                    System.out.println("自定义线程奇数:" + i);
                }
            }
        }).start();
    }
}
​

14.3 Thread类的API

14.3.1 构造方法

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新线程对象。
  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

14.3.2 常用方法

  • public void run() :此线程要执行的任务在此处定义代码。

  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。

  • public String getName() :获取当前线程名称。

  • public static Thread currentThread() :返回当前正在执行的线程对象的引用。

  • public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。

  • public final int getPriority() :返回线程优先级

  • public final void setPriority(int newPriority) :改变线程的优先级

    • 每个线程都有一定的优先级,优先级高的线程将获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级,其中setPriority方法需要一个整数,并且范围在[1,10]之间,通常推荐设置Thread类的三个优先级常量:
    • MAX_PRIORITY(10):最高优先级
    • MIN _PRIORITY (1):最低优先级
    • NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

  • public static void yield():yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。

  • void join() :等待该线程终止。

    void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。

    void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

public class TestThreadMethod1 {
    public static void main(String[] args) {
        try {
            Thread.sleep(1000);//让当前线程休眠1000毫秒再继续
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("继续");
    }
}
​
​
public class TestThreadMethod2 {
    public static void main(String[] args) {
        MyThread m1 = new MyThread();
        m1.start();
​
        MyThread m2 = new MyThread();
        m2.start();
​
        MyThread m3 = new MyThread();
        m3.setName("线程");
        m3.start();
​
        System.out.println(Thread.currentThread().getName()+"线程对象名");
        //这个语句一定是main方法执行,因为它在main方法体中
​
        MyRunnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
    }
}
​
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(getName()+"在执行...");
    }
}
​
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"在执行****");
    }
}
​
​
public class TestThreadMethod3 {
    public static void main(String[] args) {
/*        System.out.println(Thread.MAX_PRIORITY);//10
        System.out.println(Thread.MIN_PRIORITY);//1
        System.out.println(Thread.NORM_PRIORITY);//5*/
​
        ThreadDemo t = new ThreadDemo();
//        t.setPriority(1000);//java.lang.IllegalArgumentException(非法参数错误)
        t.setPriority(Thread.MAX_PRIORITY);
        t.start();
​
        ThreadExample e = new ThreadExample();
        e.setPriority(Thread.MIN_PRIORITY);
        e.start();
​
​
        /*Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        for(int i=1; i<=100; i+=2){
            System.out.println("主方法中的:"+Thread.currentThread().getName() + "线程偶数:" + i);
        }*/
    }
}
class ThreadDemo extends Thread{
    @Override
    public void run() {
        for(int i=1; i<=100; i+=2){
            System.out.println(getName() + "线程奇数:" + i);
        }
    }
}
class ThreadExample extends Thread{
    @Override
    public void run() {
        for(int i=2; i<=100; i+=2){
            System.out.println(getName() + "线程偶数:" + i);
        }
    }
}
​
​
public class TestThreadMethod4 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                for(int i=1; i<=500; i+=2){
                    System.out.println(getName() + "线程奇数:" + i);
                }
            }
        };
        t.start();
​
        while(true){
            if(t.isAlive()){
                System.out.println("t线程还活着");
            }else{
                System.out.println("t线程已经停止");
                break;
            }
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
​
public class TestThreadMethod5 {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for(int i=2; i<=30; i+=2){
                    System.out.println("偶数:" + i);
                    //要求每隔1秒打印1个偶数
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t1.start();
​
        Thread t2 = new Thread(){
            @Override
            public void run() {
                for(int i=1; i<=100; i+=2){
                    System.out.println("奇数:" + i);
                    if(i==5){
//                        Thread.yield();
                        try {
//                            t1.join();
                            /*
                            当前线程是t2线程,被加塞,被阻塞
                            t1加入进来,t1先执行
                             */
                            t1.join(10*1000);
                            /*
                            当前线程是t2线程,被加塞,被阻塞
                            t1加入进来,t1先执行10秒,然后t1,t2再一起
                             */
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };
        t2.start();
    }
}

14.3.3 线程停止

一个线程如何让另一个线程提前结束呢?

线程的死亡有两种:

自然死亡:当一个线程的run方法执行完,线程自然会停止。

意外死亡:当一个线程遇到未捕获处理的异常,也会挂掉。

我们肯定希望是让线程自然死亡更好。

  • public final void stop():强迫线程停止执行。 该方法具有固有的不安全性,已经标记为@Deprecated==(已过时、已废弃)==不建议再使用,那么我们就需要通过其他方式来停止线程了,其中一种方式是使用变量的值的变化来控制线程是否结束。
  • 标记法
​
​
public class TestThreadMethod6 {
    public static void main(String[] args) {
        PrintEvenThread even = new PrintEvenThread();
//        PrintOddThread odd = new PrintOddThread(even);
        PrintOddThread odd = new PrintOddThread();
​
        even.start();
        odd.start();
​
        //如果odd线程完事了,让even停下来
        try {
            odd.join();
            /*
            被阻塞的线程是main线程
            加入的线程是odd线程
            main线程让odd线程先执行,注意,此时和even无关。
            即odd和even是并列关系。
​
            main线程要等odd完全结束之后,下面的代码才能走
             */
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        even.setFlag(false);
    }
}
​
class PrintEvenThread extends Thread{
    private boolean flag = true;
    @Override
    public void run() {
        for(int i=2; i<=100 && flag; i+=2){
            System.out.println("偶数:" + i);
            //要求每隔1毫秒打印1个偶数
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
​
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
​
class PrintOddThread extends Thread{
    @Override
    public void run() {
        for(int i=1; i<=100; i+=2){
            System.out.println("奇数:" + i);
        }
    }
}
/*
class PrintOddThread extends Thread{
    private PrintEvenThread evenThread;
​
    public PrintOddThread(PrintEvenThread evenThread) {
        this.evenThread = evenThread;
    }
​
    @Override
    public void run() {
        for(int i=1; i<=100; i+=2){
            System.out.println("奇数:" + i);
        }
        evenThread.setFlag(false);
    }
}*/
​
​
public class TestThreadMethod7 {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for(int i=2; i<=100; i+=2){
                    System.out.println("偶数:" + i);
                    //要求每隔1毫秒打印1个偶数
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        t1.start();
​
        Thread t2 = new Thread() {
            @Override
            public void run() {
                for (int i = 1; i <= 100; i += 2) {
                    System.out.println("奇数:" + i);
                }
            }
        };
        t2.start();
​
        try {
            t2.join();
            /*
            被阻塞的线程是main线程
            加入的线程是odd线程
            main线程让odd线程先执行,注意,此时和even无关。
            即odd和even是并列关系。
​
            main线程要等odd完全结束之后,下面的代码才能走
             */
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();//中断,会发生意外
    }
​
​
}
​

14.3.4 守护线程

有一种线程,它是在后台运行的,它的任务是为其他线程提供服务的,这种线程被称为“守护线程”。JVM的垃圾回收线程就是典型的守护线程。
​
守护线程有个特点,就是如果所有非守护线程都死亡,那么守护线程自动死亡。
​
调用setDaemon(true)方法可将指定线程设置为守护线程。必须在线程启动之前设置,否则会报IllegalThreadStateException异常。
​
调用isDaemon()可以判断线程是否是守护线程。
​
​
​
public class TestThreadMethod8 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                for(int i=2; i<=200 ; i+=2){
                    System.out.println("偶数:" + i);
                }
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t.start();
​
​
        MyDaemon my = new MyDaemon();
        my.setDaemon(true);//让my线程称为守护线程的角色
        my.start();
​
    }
}
class MyDaemon extends Thread{
    @Override
    public void run() {
        while(true){
            System.out.println("我默默的守护你,不独活于世界!!!");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

14.4 线程安全

14.4.1 什么是线程安全问题

1、什么是线程安全问题?
当多个线程同时操作一个共享数据(这个共享数据可以是一个变量、一个对象、一个文件、一条数据库记录等),
并且这多个线程对这个共享数据有读有写操作,就会有线程安全问题。
​
涉及问题:
    (1)什么样的数据可以共享?
    讨论哪些变量,哪些对象可以共享?
​
    局部变量不共享。
    不同线程使用不同对象的实例变量,它们是无法共享。
    不同线程使用同一个对象的实例变量,它们是可以共享。
    不同线程访问同一个类的静态变量可以共享。
​
    (2)线程安全问题的表现是什么?
    多个线程访问共享数据的情况有点错乱。
​
经典案例:卖票问题
    3个窗口同时卖票,总票数是10张。

1、局部变量不共享

​
​
public class TestUnShare {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
​
        w1.start();
        w2.start();
        w3.start();
    }
}
​
class Window extends Thread{
​
    @Override
    public void run() {
        int total = 10;//局部变量不共享
        while(total>0){
            total--;
            System.out.println(getName()+"卖出1张票,剩余:" + total);
        }
    }
}
​

2、多个线程使用不同对象的实例变量(不共享)

​
​
public class TestUnShare2 {
    public static void main(String[] args) {
        TicketWindow t1 = new TicketWindow();
        TicketWindow t2 = new TicketWindow();
        TicketWindow t3 = new TicketWindow();
​
        t1.start();
        t2.start();
        t3.start();
    }
}
class TicketWindow extends Thread{
    private int total = 10;//实例变量,3个TicketWindow对象的total是独立的
​
    @Override
    public void run() {
        while(total>0){
            total--;
            System.out.println(getName()+"卖出1张票,剩余:" + total);
        }
    }
}
​

3、同一个类的静态变量(可以共享)

​
​
public class TestUnsafe {
    public static void main(String[] args) {
       TicketWindow t1 = new TicketWindow();
        TicketWindow t2 = new TicketWindow();
        TicketWindow t3 = new TicketWindow();
​
        t1.start();
        t2.start();
        t3.start();
    }
}
class TicketWindow extends Thread{
    private static int total = 10;//静态变量
​
    @Override
    public void run() {
        while(total>0){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            total--;
            System.out.println(getName()+"卖出1张票,剩余:" + total);
        }
    }
}

4、多个线程使用同一个对象的实例变量(可以共享)

​
​
public class TestUnSafe2 {
    public static void main(String[] args) {
        TicketRunnable runnable = new TicketRunnable();
​
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);
​
        t1.start();
        t2.start();
        t3.start();
    }
}
​
class TicketRunnable implements Runnable{//实现接口
    private int total = 10;//实例变量,TicketRunnable的对象只有1个
​
    @Override
    public void run() {
        while(total>0){
            total--;
            System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余:" + total);
        }
    }
}

14.4.2 解决线程安全问题

2、如何解决线程安全问题?
生活中:
    大小李子共用同一个卫生间,会有线程安全。
    解决:加锁
​
代码中:
    解决:加锁,给代码加锁
        某一段代码当一个线程在执行时,其他线程只能等待。
​
同步锁:synchronized,给代码加锁的关键字
形式:
(1)同步方法:锁整个方法
(2)同步代码块:锁方法体中的一小段代码
​
同步锁的原理:
    锁是什么?
    在Java中一切皆对象,同步锁也是一个对象。
    每一个Java对象在对象头中都有一个“锁”标记位,标记哪个线程占用这段代码。
    所以,必须保证使用共享数据的这多个线程使用“同一个”同步锁对象。
​
    锁对象是谁?
    如果是同步方法,静态方法的话,默认选择当前类的Class对象当同步锁对象。
                非静态方法的话,默认选择this对象当前同步锁对象。
​
注意:
(1)必须保证使用共享数据的这多个线程使用“同一个”同步锁对象。
(2)锁的代码范围不能太大,别的线程没有机会
(3)锁的代码范围不能太小,安全性没有彻底解决
​
代码范围原则:一次任务的完整代码
    比如:卖票:
        检查票数,减票数等都是一次任务的代码

1、同步锁原理

image-20230406155217814.png

2、静态同步方法

​
​
public class TestSafe1 {
    public static void main(String[] args) {
        TicketWindow t1 = new TicketWindow();
        TicketWindow t2 = new TicketWindow();
        TicketWindow t3 = new TicketWindow();
​
        t1.start();
        t2.start();
        t3.start();
    }
}
class TicketWindow extends Thread{
    private static int total = 1000;//静态变量
​
    @Override
    public void run() {
        while (total>0) {
            saleOneTicket();
        }
    }
​
    public synchronized static void saleOneTicket() {
        if (total > 0) {
     /*       try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            total--;
            System.out.println(Thread.currentThread().getName() + "卖出1张票,剩余:" + total);
        }
    }
}
/*class TicketWindow extends Thread{
    private static int total = 100;//静态变量
​
    @Override
    public void run() {
        while (total>0) {//锁的范围太小了
            saleOneTicket();
        }
    }
​
    public synchronized static void saleOneTicket(){
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        total--;
        System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余:" + total);
    }
}*/
/*class TicketWindow extends Thread{
    private static int total = 10;//静态变量
​
​
    @Override
    public  void run() {
        saleOneTicket();
    }
​
​
//    saleOneTicket()方法是静态方法,默认的锁对象是TicketWindow类的Class对象。
//    每一个类被加载到内存中之后,JVM都会用一个Class对象来表示这个类。
//    只要是同一个类,Class就只有一个。
    //锁的范围太大了
    public synchronized static void saleOneTicket(){
        while(total>0){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            total--;
            System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余:" + total);
        }
    }
}*/
/*
class TicketWindow extends Thread{
    private static int total = 10;//静态变量
​
​
//    run()方法是非静态方法,默认锁对象是this,
//    这里三个TicketWindow的this对象不是同一个
    @Override
    public synchronized void run() {
        while(total>0){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            total--;
            System.out.println(getName()+"卖出1张票,剩余:" + total);
        }
    }
}*/
​

3、非静态同步方法

​
​
public class TestSafe2 {
    public static void main(String[] args) {
        TicketRunnable runnable = new TicketRunnable();
​
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        Thread t3 = new Thread(runnable);
​
        t1.start();
        t2.start();
        t3.start();
    }
}
class TicketRunnable implements Runnable{//实现接口
    private int total = 1000;//实例变量
​
    //run方法是非静态方法,默认锁对象是this
    //这里TicketRunnable类的对象只有1个,同一个锁对象
    @Override
    public  void run() {
        while(total>0){
            saleOneTicket();
        }
    }
​
    public synchronized void saleOneTicket(){
        if(total>0){
            total--;
            System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余:" + total);
        }
    }
}
/*
class TicketRunnable implements Runnable{//实现接口
    private int total = 1000;//实例变量
​
    //run方法是非静态方法,默认锁对象是this
    //这里TicketRunnable类的对象只有1个,同一个锁对象
    //锁的范围太大了
    @Override
    public synchronized void run() {
        while(total>0){
            total--;
            System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余:" + total);
        }
    }
}*/

4、同步代码块

3、同步方法
【其他修饰符】 synchronized 返回值类型 方法名(【形参列表】)【throws 异常类型列表】{
​
}
​
​
4、同步代码块
    synchronized(锁对象){
        需要加锁的一小段代码
    }
​
​
​
public class TestSafe3 {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
​
        w1.start();
        w2.start();
        w3.start();
    }
}
​
class Window extends Thread{
    private static int total = 1000;
    private static Object lock = new Object();
​
    @Override
    public void run() {
        while(total>0){
            synchronized (Window.class) {
//            synchronized (lock) {
//            synchronized ("") {
                if(total>0) {
                    total--;
                    System.out.println(getName() + "卖了一张票,剩余:" + total);
                }
            }
        }
    }
}
package com.atguigu.safe.safe;
​
public class TestSafe4 {
    public static void main(String[] args) {
        SaleRunnable s1 = new SaleRunnable(10,"Java讲座");
        SaleRunnable s2 = new SaleRunnable(5,"ChatGPT讲座");
​
        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(s1);
        Thread t3 = new Thread(s1);
​
        Thread t4 = new Thread(s2);
        Thread t5 = new Thread(s2);
        Thread t6 = new Thread(s2);
​
        t1.start();
        t2.start();
        t3.start();
​
        t4.start();
        t5.start();
        t6.start();
    }
}
​
class SaleRunnable implements Runnable{
    private int total;
    private String title;
​
    public SaleRunnable(int total, String title) {
        this.total = total;
        this.title = title;
    }
​
    @Override
    public void run() {
        while(total>0){
//            synchronized (SaleRunnable.class) {
            synchronized (this) {
                if(total>0) {
                    total--;
                    System.out.println(Thread.currentThread().getName() + "卖了一张"+title+"票,剩余:" + total);
                }
            }
        }
    }
}

5、抽取资源类

​
​
public class Ticket {
    private String title;
    private int total;
​
    public Ticket(String title, int total) {
        this.title = title;
        this.total = total;
    }
​
    public String getTitle() {
        return title;
    }
​
    public void setTitle(String title) {
        this.title = title;
    }
​
    public int getTotal() {
        return total;
    }
​
    public void setTotal(int total) {
        this.total = total;
    }
​
    @Override
    public String toString() {
        return "Ticket{" +
                "title='" + title + ''' +
                ", total=" + total +
                '}';
    }
​
    //锁对象:this
    public synchronized void saleOneTicket(){
        if(total>0) {
            total--;
            System.out.println(this);
        }
    }
}
​
​
​
public class TestTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket("JavaSE课程",10);
        MyTicketThread ticketThread = new MyTicketThread(ticket);
​
        Thread t1 = new Thread(ticketThread);
        Thread t2 = new Thread(ticketThread);
        Thread t3 = new Thread(ticketThread);
​
        t1.start();
        t2.start();
        t3.start();
    }
}
​
class MyTicketThread implements Runnable{
    private Ticket ticket;
​
    public MyTicketThread(Ticket ticket) {
        this.ticket = ticket;
    }
​
    @Override
    public void run() {
        while(ticket.getTotal()>0){
            /*synchronized (ticket) {//共享资源对象当锁对象
                if(ticket.getTotal()>0) {
                    ticket.setTotal(ticket.getTotal() - 1);
                    System.out.println(ticket);
                }
            }*/
           ticket.saleOneTicket();
        }
    }
}

14.5 线程通信

1、什么是生产者与消费者问题?
当多个线程使用同一个共享的数据缓冲区,
    其中一个/些线程是给增加共享数据缓冲区添加数据,(比喻生产过程)==>生产者线程
    而另一个/些线程是从共享的数据缓冲区取走数据。(比喻消费过程)==>消费者线程
​
问题:
(1)多个线程,有共享数据,有读和写 ==> 线程安全问题 ==> JavaSE加同步锁
(2)数据缓冲区通常会有大小的限制,
当数据缓冲区满的时候,生产过程就要“暂停等待”,当消费者线程消费了一些数据之后,再“通知”生产者线程再继续。
反过来,
当数据缓冲区空的时候,消费过程就要“暂停等待”,当生产者线程生产了一些数据之后,再“通知”消费者线程再继续。
​
案例:
    李世杰在附近开了一个餐馆,一开始,店比较小。
    李世杰找了对象,一起干。
    一个在厨房,一个在大堂。
    厨房和大堂之间有个小窗口,传菜的。这个窗口上有一个小平台,可以放5份菜。
​
2、等待与唤醒机制:线程通信机制
依赖于Object类中的几个方法:
void wait()
void wait(long mill)
void wait(long mill, long na)
void notify()
void notifyAll()
​
以上几个方法,必须由“同步锁/线程的监视器”对象调用,否则就会报IllegalMonitorStateException

一个生产者一个消费者

​
​
public class Window {
    private int total;//记录窗台上面放的菜的数量
    private Object lock = new Object();
​
    //put方法是非静态方法,默认锁对象是this
    /*public  void put(){
        synchronized(lock) {
            if (total >= 5) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            total++;
            System.out.println(Thread.currentThread().getName() + "炒了一份菜,台上:" + total);
        }
    }*/
 /*   public  void put(){
        synchronized(this) {
            if (total >= 5) {
                try {
                    wait();//this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            total++;
            System.out.println(Thread.currentThread().getName() + "炒了一份菜,台上:" + total);
        }
    }*/
    public synchronized void put() {
        if (total >= 5) {
            try {
                wait();//this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        total++;
        System.out.println(Thread.currentThread().getName() + "炒了一份菜,台上:" + total);
        notify();
    }
​
    public synchronized void take() {
        if (total <= 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        total--;
        System.out.println(Thread.currentThread().getName() + "取走一份菜,台上:" + total);
        notify();
    }
}
​
​
​
public class TestWindow {
    public static void main(String[] args) {
        Window w = new Window();
​
        new Thread(){
            @Override
            public void run() {
                while(true){
                   /* try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/
                    w.put();
                }
            }
        }.start();
​
        new Thread(){
            @Override
            public void run() {
                while(true){
                    w.take();
                }
            }
        }.start();
    }
}
​

多个生产者多个消费者

​
​
public class Window {
    private final int MAX_TOTAL = 5;
    private int total;//记录窗台上面放的菜的数量
    private Object lock = new Object();
​
    //put方法是非静态方法,默认锁对象是this
    /*public  void put(){
        synchronized(lock) {
            if (total >= 5) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            total++;
            System.out.println(Thread.currentThread().getName() + "炒了一份菜,台上:" + total);
        }
    }*/
 /*   public  void put(){
        synchronized(this) {
            if (total >= 5) {
                try {
                    wait();//this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            total++;
            System.out.println(Thread.currentThread().getName() + "炒了一份菜,台上:" + total);
        }
    }*/
    public synchronized void put() {
        while (total >= MAX_TOTAL) {
            try {
                wait();//this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        total++;
        System.out.println(Thread.currentThread().getName()
                + "炒了一份菜,台上:" + total);
//        notify();
        notifyAll();
    }
​
    public synchronized void take() {
        while (total <= 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        total--;
        System.out.println(Thread.currentThread().getName()
                + "取走一份菜,台上:" + total);
//        notify();
        notifyAll();
    }
}
​
​
​
public class TestWindow {
    public static void main(String[] args) {
        Window w = new Window();
        Cook c1 = new Cook("光头强",w);
        Cook c2 = new Cook("翠花",w);
​
        Waiter w1 = new Waiter("熊大",w);
        Waiter w2 = new Waiter("熊二",w);
​
        c1.start();
        c2.start();
        w1.start();
        w2.start();
    }
}
​
class Cook extends Thread{
    private Window w;
​
    public Cook(String name, Window w) {
        super(name);
        this.w = w;
    }
​
    @Override
    public void run() {
        while(true){
            w.put();
        }
    }
}
class Waiter extends Thread{
    private Window w;
​
    public Waiter(String name, Window w) {
        super(name);
        this.w = w;
    }
​
    @Override
    public void run() {
        while(true){
            w.take();
        }
    }
}