Java多线程

Posted 留有痕迹~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程相关的知识,希望对你有一定的参考价值。

什么是多线程

利用对象,可将一个程序分割成相互独立的区域。我们通常也需要将一个程序转换成多个独立运行的子任
务。象这样的每个子任务都叫作一个“线程”(Thread)。编写程序时,可将每个线程都想象成独立运行,而且
都有自己的专用CPU。一些基础机制实际会为我们自动分割CPU的时间。我们通常不必关心这些细节问题,
所以多线程的代码编写是相当简便的。引自《Java编程思想》第四版

生命周期

技术图片

如何开启一个线程

  • 继承Thread

Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:

public class StartThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 120; i++) {
            System.out.println("听歌。。。");
        }
    }

    public static void main(String[] args) {
        StartThread st = new StartThread();
        st.start();
        for (int i = 0; i < 120; i++) {
            System.out.println("敲代码。。。");
        }
    }
}
  • 实现Runnable接口

如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口,如下:

public class StartRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 120; i++) {
            System.out.println("听歌。。。");
        }
    }

    public static void main(String[] args) {
        StartRunnable sr = new StartRunnable();
        Thread t = new Thread(sr);
        t.start();
        for (int i = 0; i < 120; i++) {
            System.out.println("敲代码。。。");
        }
    }
}
  • 实现Callable接口

实现Callable接口实现线程开启相比较上两个步骤会多一些,是juc并发包下的一个接口。

public class StartCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        for (int i = 0; i < 120; i++) {
            System.out.println("听歌。。。");
        }
        return 1;
    }
    public static void main(String[] args) throws Exception {
        StartCallable sc = new StartCallable();
        ExecutorService es = Executors.newFixedThreadPool(1);
        Future<Integer> submit = es.submit(sc);
        Integer result = submit.get();
        es.shutdownNow();
        System.out.println(result);
        for (int i = 0; i < 120; i++) {
            System.out.println("敲代码。。。");
        }
    }
}

线程状态

线程在生命周期内,共五大状态
技术图片
具体说明:

  • 实例线程--->新生状态
  • 调用start()--->就绪状态
  • 操作系统CPU调度器分配好进行调用--->运行状态
  • 线程正常执行完毕或外部干涉--->死亡状态
  • IO文件读写、调用sleep()、调用wait()--->阻塞状态
    技术图片
    线程主要方法:
    技术图片

    线程终止

停止线程的stop()方法jdk已经过失,不推荐使用,如何优雅的停止线程呢?举例:

public class Study implements Runnable {
    // 线程类中,定义线程体使用的标识
    private boolean stop = true;

    private String name;

    Study(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        int i = 0;
        // 线程体使用该标识
        while (stop) {
            System.out.println(name + "线程正常运行。。。===>" + i++);
        }
    }
    // 对外提供方法改变标识
    public void stop() {
        this.stop = false;
    }

    public static void main(String[] args) {
        Study s = new Study("张三");
        Thread thread = new Thread(s);
        thread.start();
        for (int i = 0; i < 1000; i++) {
            if (i == 900) {
                s.stop();
                System.out.println("game over。。。。。。");
            }
            System.out.println("main====>" + i);
        }
    }
}

暂停 sleep()

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep()存在异常 InterruptedExcetion
  • sleep()时间到达后线程进入就绪状态
  • sleep()可以模拟网络延时、倒计时等
  • 每一个对象都有一个锁,sleep()不会释放锁
  • 举例:
// sleep() 模拟倒计时
public static void main(String[] args) throws InterruptedException {
    // 当前时间戳
    long millis = System.currentTimeMillis();
    // 当前时间加十秒
    Date endTime = new Date(millis + 1000 * 10);
    long end = endTime.getTime();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    for (; ; ) {
        System.out.println(sdf.format(endTime));
        // 睡眠一秒
        Thread.sleep(1000L);
        // 获取倒计时一秒后的时间
        endTime = new Date(endTime.getTime() - 1000L);
        if ((end - 10000) >= endTime.getTime()) {
            break;
        }
    }
}

yield()

  • 礼让线程,让当前正在执行的线程暂停
  • 不是阻塞线程,而是将线程从 运行状态 转入就绪状态
  • 让CPU调度器重新调度

举例:

public class Yield {
    public static void main(String[] args) {
        new Thread(()-> {
            for (int i = 0; i < 100; i++) {
                System.out.println("我是另一个线程。。。");
            }
        }).start();
        for (int i = 0; i < 100; i++) {
            if (i % 20 == 0) {
                System.out.println(i + "-->开始礼让");
                // main线程进行礼让
                Thread.yield();
            }
            System.out.println("main 线程-->" + i);
        }
    }
}

注意:yield是让线程进入就绪状态,不是阻塞状态。

插队 join()

join() 合并线程,待此线程执行完毕后,再执行其他线程,其他线程阻塞

举例:

public class Example {
    public static void main(String[] args) {
        System.out.println("买烟的故事。。。");
        new Thread(new Father()).start();
    }
}

class Father extends Thread {
    @Override
    public void run() {
        System.out.println("老爸抽烟,烟没了,让儿子买烟。");
        Thread t = new Thread(new Son());
        t.start();
        try {
            // Father 线程被阻塞
            t.join();
            System.out.println("儿子把烟买来了,交给老爸,老爸把剩余零钱给了儿子。");
        } catch (InterruptedException e) {
            System.out.println("儿子走丢了。。。");
        }
    }
}

class Son extends Thread {
    @Override
    public void run() {
        System.out.println("接过老爸的钱去买烟");
        System.out.println("路边有个游戏厅,玩了10秒");
        for (int i = 0; i < 10; i++) {
            System.out.println(i + "秒过去了。。。");
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("突然想起还要买烟,赶紧买烟。");
        System.out.println("手拿一包中华,回家了。");
    }
}

深度观察状态

/**
 * 观察线程状态
 */
public class AllState {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()-> {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("....");
            }
        });
        // 观察状态
        Thread.State state = t.getState();
        // 新创建处理新生状态,state为NEW
        System.out.println(state.toString());
        t.start();
        state = t.getState();
        // 线程就绪,就绪状态到运行状态是CPU控制的,所以一般调用了线程的 start()方法后,
        // state为RUNNABLE
        System.out.println(state.toString());

        for (; state != Thread.State.TERMINATED;) {
            Thread.sleep(200L);
            // 线程阻塞 state为TIMED_WAITING
            state = t.getState();
            System.out.println(state.toString());
        }
        // 线程运行结束 state为TERMINATED
        state = t.getState();
        System.out.println(state.toString());
    }
}

线程优先级

在多个线程同时执行的时候,线程调度器会根据优先级,优先调用级别高的线程。(只是决定优先调用,不代表先后顺序。)

  • MAX_PRIORITY(10,线程可以拥有的最大优先级)
  • MIN_PRIORITY(1,线程可以拥有的最小优先级)
  • NORM_PRIORITY(5,分配给线程的默认优先级)

举例:

public class PriorityTest {
    public static void main(String[] args) {
        TestPriority tp = new TestPriority();
        Thread t = new Thread(tp);
        t.setPriority(Thread.MAX_PRIORITY);
        t.start();
        Thread t1 = new Thread(tp);
        t1.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        Thread t2 = new Thread(tp);
        t2.setPriority(Thread.MAX_PRIORITY);
        t2.start();
        Thread t3 = new Thread(tp);
        t3.setPriority(Thread.MIN_PRIORITY);
        t3.start();
        Thread t4 = new Thread(tp);
        t4.setPriority(Thread.MIN_PRIORITY);
        t4.start();
        Thread t5 = new Thread(tp);
        t5.setPriority(Thread.MIN_PRIORITY);
        t5.start();
    }
}

class TestPriority implements Runnable {
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        String threadName = thread.getName();
        int threadPriority = thread.getPriority();
        System.out.println(threadName + "--->" + threadPriority);
        Thread.yield();
    }
}

如果优先级高的话,那么先执行的概率会大一些,但不是绝对。

守护线程

守护线程是为用户线程服务的,JVM停止不用等待守护线程执行完毕。

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如后台记录操作日志、监控内存使用等

默认情况下,线程都是用户线程,虚拟机等待所有用户线程执行完毕才会停止。

public class DaemonTest {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();
        Thread t = new Thread(god);
        // 将用户线程调整为守护线程(默认为false)
        t.setDaemon(true);
        t.start();
        new Thread(you).start();
        // God调整为守护线程时,当You线程结束后,God也随即结束。
    }
}
// 我
class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println("幸福的生活着。。。");
        }
        System.out.println("Game Over!");
    }
}
// 上帝
class God implements Runnable {
    @Override
    public void run() {
        for (; ;) {
            System.out.println("上帝守护你。。。");
        }
    }
}

其它常用方法

  • isAlive(),判断当前线程是否活着,即线程是否还未终止
  • setName(),给当前线程赋名字
  • getName(),获取当前线程名字
  • currentThread(),取得当前运行的线程对象,或者说是获取自己本身

举例 :

public class OtherMethod {
    public static void main(String[] args) throws Exception {
        Thread thread = Thread.currentThread();
        System.out.println(thread);
        System.out.println(thread.isAlive());
        System.out.println(thread.getName());

        MyInfo myInfo = new MyInfo("张三");
        Thread t = new Thread(myInfo);
        t.setName("李四");
        t.start();
        Thread.sleep(2000L);
        System.out.println(t.isAlive());
    }
}

class MyInfo implements Runnable {
    private String name;
    MyInfo(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->" + name);
    }
}

线程同步

讲到线程同步就要理解什么是并发了,并发就是同一个对象同时被多个线程访问。一旦出现并发就会线程不安全最终就可能导致数据结果不准确等一些问题。

线程同步本质就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的 等待池形成队列 ,等待前面的线程使用完毕后,先一个线程再使用。

如何保证线程安全:

队列形式,排队执行,通过锁的方式,标志某个线程正在占用某个资源,处理完毕后释放锁让队列中其它线程继续执行。
技术图片

synchronized

synchronized 关键字包括两种用法,synchronized 方法 和 synchronized 代码块

synchronized 方法控制对“成员变量|类变量”对象的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。

synchronized方法
/**
 * 模拟抢票
 * 保证线程安全,并发时保证数据的正确性、效率尽可能高
 */
public class SyncWeb12306 implements Runnable {
    // 票数
    private int ticketNums = 10;
    private boolean flag = true;
    @Override
    public void run() {
        for (; ; ) {
            if (!flag) {
                break;
            }
            test();
        }
    }
    // 使用 synchronized 关键字 锁住资源(this)
    public synchronized void test() {
        if (ticketNums <= 0) {
            this.flag = false;
            return;
        }
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
    }
    public static void main(String[] args) {
        // 一份资源
        SyncWeb12306 web = new SyncWeb12306();
        // 多个线程同时访问
        new Thread(web, "张三").start();
        new Thread(web, "李四").start();
        new Thread(web, "王五").start();
    }
}
synchronized代码块
/**
 * 模拟并发操作取钱
 */
public class SyncDrawMoney {
    int money;  // 金额
    String name;    // 名称
    SyncDrawMoney(int money, String name) {
        this.money = money;
        this.name = name;
    }
    public static void main(String[] args) {
        SyncDrawMoney drawMoney = new SyncDrawMoney(100, "买车");
        SyncDrawing you = new SyncDrawing(drawMoney, 100, "张三");
        SyncDrawing wife = new SyncDrawing(drawMoney, 100, "张三老婆");
        you.start();
        wife.start();
    }
}

// 模拟取款
class SyncDrawing extends Thread {
    SyncDrawMoney drawMoney;    // 取钱账户
    int drawingMoney;   // 取的钱数
    int packetTotal;   // 口袋里的钱
    public SyncDrawing(SyncDrawMoney drawMoney, int drawingMoney, String name) {
        super(name);
        this.drawMoney = drawMoney;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        test();
    }

    private void test() {
        if (drawMoney.money > 0) {
            // 锁住具体要操作的对象
            synchronized (drawMoney) {
                if (drawMoney.money < drawingMoney) {
                    System.out.println(Thread.currentThread().getName() + "取" + drawingMoney + "但是,余额还有" + drawMoney.money);
                    System.out.println("钱不够了。。。");
                    return;
                }
                if (drawMoney.money - drawingMoney < 0) {
                    System.out.println(Thread.currentThread().getName() + "取" + drawingMoney + "但是,余额还有" + drawMoney.money);
                    System.out.println("没钱了。。。");
                    return;
                }
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                drawMoney.money -= drawingMoney;
                packetTotal += drawingMoney;
                System.out.println(Thread.currentThread().getName() + "--->账户余额为:" + drawMoney.money);
                System.out.println(Thread.currentThread().getName() + "--->口袋钱为:" + packetTotal);
            }
        } else {
            System.out.println("账户没钱了。。。");
        }
    }
}

性能分析

public class PropertyWeb12306 implements Runnable {

    // 票数
    private Integer ticketNums = 10;

    private boolean flag = true;

    @Override
    public void run() {
        for (; ; ) {
            if (!flag) {
                break;
            }
            test3();
        }
    }

    // 尽可能锁定合理范围(合理范围不是指代码,指的是数据的完整性)
    void test3() {
        if (ticketNums <= 0) {
            this.flag = false;
            return;
        }
        synchronized (this) {
            if (ticketNums <= 0) {
                this.flag = false;
                return;
            }
            try {
                Thread.sleep(200L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
        }
    }

    // 同步代码块范围小,锁不住,导致线程不安全
    void test2() {
        synchronized (this) {
            if (ticketNums <= 0) {
                this.flag = false;
                return;
            }
        }
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
    }

    // 同步代码块,锁住当前对象,范围大,虽然实现了线程安全,但是性能低下。
    void test1() {
        synchronized (this) {
            if (ticketNums <= 0) {
                this.flag = false;
                return;
            }
            try {
                Thread.sleep(200L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
        }
    }

    // 线程安全,同步
    synchronized void test() {
        if (ticketNums <= 0) {
            this.flag = false;
            return;
        }
        try {
            Thread.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
    }

    public static void main(String[] args) {
        // 一份资源
        PropertyWeb12306 web = new PropertyWeb12306();
        // 多个线程同时访问
        new Thread(web, "张三").start();
        new Thread(web, "李四").start();
        new Thread(web, "王五").start();
    }
}

注意:实际开发中,尽量多用同步代码块,少用同步方法

线程同步小案例

购买电影票小案例

/**
 * 快乐影院
 */
public class HappyCinema {
    public static void main(String[] args) {
        // 可用位置
        List<Integer> available = new ArrayList<>();
        available.add(1);
        available.add(2);
        available.add(4);
        available.add(6);
        available.add(5);
        available.add(7);
        available.add(3);
        // 影院
        Cinema c = new Cinema(available, "夜上海影院");
        // 来两个顾客
        List<Integer> seats01 = new ArrayList<>();
        seats01.add(1);
        seats01.add(2);
        seats01.add(4);
        new Thread(new Customer(c, seats01), "张三").start();

        List<Integer> seats02 = new ArrayList<>();
        seats02.add(4);
        seats02.add(6);
        seats02.add(7);
        new Thread(new Customer(c, seats02), "李四").start();
    }
}

/**
 * 顾客
 */
class Customer implements Runnable {
    // 电影院
    Cinema cinema;
    // 需要几个位置
    List<Integer> seats;
    Customer(Cinema cinema, List<Integer> seats) {
        this.cinema = cinema;
        this.seats = seats;
    }
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        synchronized (cinema) {
            boolean flag = cinema.bookTickets(this.seats);
            if (flag) {
                System.out.println(thread.getName() + "-->购票成功。。。" + "位置为:" + this.seats);
            } else {
                System.out.println(thread.getName() + "-->购票失败***" + "位置不够");
            }
        }
    }
}

/**
 * 影院
 */
class Cinema {
    List<Integer> available;  // 可用位置
    String name;    // 名称
    Cinema(List<Integer> available, String name) {
        this.available = available;
        this.name = name;
    }
    // 购票
    boolean bookTickets(List<Integer> seats) {
        System.out.println("欢迎光临【" + this.name + "】当前可用的位置为" + available);
        System.out.println(Thread.currentThread().getName() + "-->想要购买" + seats);
        List<Integer> copy = new ArrayList<>(available);
        // 相减
        copy.removeAll(seats);
        // 判断大小
        if (seats.size() != (available.size() - copy.size())) {
            return false;
        }
        available = copy;
        return true;
    }
}

购买火车票小案例

/**
 * 购买火车票  线程安全版
 */
public class Happy12306 {
    public static void main(String[] args) {
        Web12306 web = new Web12306(4, "杭州");
        new Passenger(web, "张三", 2).start();
        new Passenger(web, "李四", 1).start();
    }
}

// 乘客
class Passenger extends Thread {
    int seats;
    public Passenger(Runnable target, String name, int seats) {
        super(target, name);
        this.seats = seats;
    }
}

// 火车票网站
class Web12306 implements Runnable {
    int available;  // 可用位置
    String name;    // 名称

    public Web12306(int available, String name) {
        this.available = available;
        this.name = name;
    }

    @Override
    public void run() {
        Passenger p = (Passenger) Thread.currentThread();
        boolean flag = this.bookTickets(p.seats);
        if (flag) {
            System.out.println("出票成功" + p.getName() + "位置为:" + p.seats);
        } else {
            System.out.println("出票失败" + p.getName() + "位置不够");
        }
    }

    // 购票
    synchronized boolean bookTickets(int seats) {
        System.out.println("可用位置为:" + this.available);
        if (seats > this.available) {
            return false;
        }
        available -= seats;
        return true;
    }
}

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

通俗一些就是:你给钱,我给货。我想让你先给货,你想让我先给钱,一直僵持着。

过多的同步有可能会造成死锁

如何避免:

不要再同步一个代码块同时持有多个对象的锁

并发协作

生产者消费者模式

本质是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

  • 对于生产者而言,没有生产产品之前,要通知消费者等待。而生产了之后,需要马上通知消费者
  • 对于消费者而言,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费
  • 在生产者消费者问题中,仅有 synchronized 时不够的,还需要等待和通知等操作
    • synchronized 可阻止并发更新同一个共享资源,实现同步
    • synchronized 不能用来实现不同线程之间的消息传递(通信)
      技术图片
      Java提供了3个方法解决线程之间的通信问题
方法名 作用
final void wait() 表示线程一直等待,直到其他线程通知,与sleep()不同,会释放锁
final void wait(long timeout) 指定等待的毫秒数
final void notifiy() 唤醒一个处于等待状态的线程
final void notifiyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

注意:以上方法都只能在同步方法或者同步代码块中使用,否则会抛出异常

实现方式一:管程法
技术图片

/**
 * 协作模型:
 * 生产者消费者模式方式一:管程法
 */
public class CoTest01 {
    public static void main(String[] args) {
        SyncContainer container = new SyncContainer();
        // 生产者
        new Productor(container).start();
        // 消费者
        new Consumer(container).start();
    }
}
// 生产者
class Productor extends Thread {
    SyncContainer container;

    public Productor(SyncContainer container) {
        this.container = container;
    }

    @Override
    public void run() {
        // 开始生产
        for (int i = 0; i < 100; i++) {
            System.out.println("生产第" + i + "个馒头。。。");
            container.push(new SteamedBun(i));
        }
    }
}

// 消费者
class Consumer extends Thread {
    SyncContainer container;

    public Consumer(SyncContainer container) {
        this.container = container;
    }
    @Override
    public void run() {
        // 开始消费
        for (int i = 0; i < 100; i++) {
            System.out.println("消费第" + container.pop().id + "个馒头。。。");
        }
    }
}

// 缓冲区
class SyncContainer {
    SteamedBun[] steamedBuns = new SteamedBun[10];  // 存储容器
    int count = 0;  // 计数器

    // 存储  生产
    synchronized void push(SteamedBun steamedBun) {
        // 不能生产
        if (count == steamedBuns.length) {
            try {
                this.wait();    // 线程阻塞 消费者通知生产解除
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        steamedBuns[count] =steamedBun;
        count++;
        // 存在数据了,可以通知消费
        this.notifyAll();
    }

    // 获取 消费
    synchronized SteamedBun pop() {
        if (count == 0) {
            try {
                this.wait();    // 线程阻塞 生产者通知消费,解除阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        this.notifyAll();   // 存在空间 唤醒所有
        return steamedBuns[count];
    }
}

// 数据,举例为馒头
class SteamedBun {
    int id;

    public SteamedBun(int id) {
        this.id = id;
    }
}

实现方式二:信号灯法
技术图片

/**
 * 协作模型:
 * 生产者消费者模式方式一:信号灯法
 * 借助标注位
 */
public class CoTest02 {
    public static void main(String[] args) {
        Tv tv = new Tv();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}
// 生产者 演员
class Player extends Thread {
    Tv tv;

    public Player(Tv tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if ((i % 2) == 0) {
                this.tv.play("奇葩说。。。");
            } else {
                this.tv.play("直播广告。。。");
            }
        }
    }
}
// 消费者观众
class Watcher extends Thread {
    Tv tv;

    public Watcher(Tv tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}

// 同一个资源 电视
class Tv {
    String voice;
    boolean flag = true; // 信号灯,如果为 true 演员表演观众观看,否则观众观看演员等待

    // 表演
    synchronized void play(String voice) {
        // 演员等待
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.voice = voice;
        // 表演
        System.out.println("表演了:" + this.voice);
        this.notifyAll();   // 唤醒
        this.flag = !this.flag;
    }
    // 观看
    synchronized void watch() {
        // 观众等待
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 观看
        System.out.println("听到了:" + this.voice);
        this.notifyAll();   // 唤醒
        this.flag = !this.flag;
    }
}

以上是关于Java多线程的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程与并发库高级应用-工具类介绍

多线程 Thread 线程同步 synchronized

Java多线程具体解释

自己开发的在线视频下载工具,基于Java多线程

什么是JAVA的多线程?

多个用户访问同一段代码