JAVA多线程十一:多线程协调

朱小落

关注

阅读 26

2024-11-20

1.守护线程

Java语言中线程分为两大类:用户线程,守护线程(后台线程),其中具有代表性的就是:垃圾回收线程(守护线程)。

一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。(主线程main方法是一个用户线程。)

设置方法:. setDeamon(boolean)。

//测试守护线程
//国家守护你
public class TestDaemon {
    public static void main(String[] args) {
        China china = new China();
        You you = new You();
        Thread thread = new Thread(china);//让国家编程守护线程
        thread.setDaemon(true);//setDaemon返回值是Boolean 默认是false表示用户线程
        thread.start();//守护线程启动
        new Thread(you).start();//用户线程启动
    }
}

class China implements Runnable{  //守护线程
    @Override
    public void run() {
        while(true){//国家永远存在
            System.out.println("国家保护着你!");
        }
    }
}
class You implements Runnable{ //用户线程
 
    @Override
    public void run() {
        for (int i = 0; i < 30000; i++) {
            System.out.println("你开心的活着");
        }
        System.out.println("*******goodbye!*******");
    }
}

2.定时器

定时器的作用:间隔特定的时间,执行特定的程序。

在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用

案例:

import java.util.Timer;
import java.util.TimerTask;

public class DemoTimer {
    public static void main(String[] args) {
        Timer timer = new Timer();//创建Timer定时器类的对象
        //匿名内部类
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("我被执行了!~");
                System.gc();//告诉JVM运行完毕,可以把我回收
            }
        },5000);
    }
}

线程与定时器之间互不抢占CPU时间片

import java.util.Timer;
import java.util.TimerTask;
 
/**
 * @author Mr.乐
 * @Description 线程与定时器的执行轨迹不同
 */
public class DemoTimer {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
               for (int i = 0; i < 20; i++) {
                 System.out.println(Thread.currentThread().getName() + "<--->" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
               }
            }
        }).start();
 
        //定时器实现
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                   System.out.println(Thread.currentThread().getName() + "---" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.gc();//将编程垃圾的定时器进行回收
            }
        },5000);
    }
}

3.协调多个线程

线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知,实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。

场景:

生产者将产品交给店员,而消费者从店员处取走产品,店员一次只能持有固定数量的产品,如果生产者生产了过多的产品,店员会叫生产者等一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

在这个应用场景中,我们可以得出线程间通信,指的一个线程完成了自己的任务时,要通知另外一个线程去完成另外一个任务。要完成线程间通信,就需要控制多个线程按照一定的顺序轮流执行。

4. 三个Object类的方法

在Object类中提供了wait (), notify (), notifyAll ()方法用于解决线程间的通信问题,由于Java中所有类都是Object类的子类或间接子类,因此任何类的实例对象都可以直接使用这些方法。这三个方法的调用者不是线程对象,应该是同步锁对象,否则Java 虚拟机就会抛ILegalMonitorStateException 异常。

wait() / wait(long timeout):让当前线程进入等待状态。

notify() / notifyAll():唤醒在当前对象上等待的线程。

5. wait()方法作用


   Object o = new Object();
   o.wait();  // 让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
   

wait() 方法的调用,会让“正在o对象上活动的线程(当前线程)”进入等待状态,并且释放之前占有的o对象的锁。

6. wait()方法原理

调用 wait() 方法时,当前线程必须持有该对象的监视器锁(即 object 的锁)。

  1. 先释放锁(前提是需要先有同步锁,因此wait操作需要搭配synchronized来使用)。
  2. 进入等待池阻塞等待。
  3. 收到通知后会唤醒等待池中的一个或所有线程,重新尝试获取锁,被唤醒的线程将尝试重新获得锁,一旦获得锁,就会离开等待状态。

案例:


public class ThreadDemo9 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized (object) {
            System.out.println("wait之前");
           
            object.wait();
            
            System.out.println("wait之后");
        }
    }
}


//  调用 wait() 方法时,当前线程必须持有该对象的监视器锁(即 object 的锁),所以要搭配synchronized使用
//  当线程调用wait() 方法后,它会释放所持有的锁,并进入等待池,直到以下几种情况发生之一:

//  1. 另一个线程调用 object.notify() 或 object.notifyAll() 方法,这会唤醒等待池中的一个或所有线程。
//     被唤醒的线程将尝试重新获得锁,一旦获得锁,就会离开等待状态。
//  2. 等待的线程被中断。
//  3. 等待的线程超时。

//  在 wait() 调用之后的代码 System.out.println("wait之后"); 永远不会执行,
//  因为 wait() 导致了当前线程的阻塞,并且只有在线程被唤醒并成功获取锁后才会继续执行。
//  如果没有其他线程通过 synchronized(object) 代码块获取 object 的锁并调用 object.notify() 或 object.notifyAll(),
//  那么当前线程将一直处于等待状态。

7.notify() / notifyAll()


  Object o = new Object();
  o.notify(); // 唤醒正在o对象上等待的线程。
  o.notifyAll()  // 这个方法是唤醒o对象上处于等待的所有线程。

o.notify() / o.notifyAll() 方法只会通知唤醒o对象上处于等待的线程,不会释放之前占有的o对象的锁。

案例一:


 public static void main(String[] args) throws InterruptedException {
        Object object = new Object();

        //这个线程负责等待
        Thread t1 = new Thread(()->{
            System.out.println("t1  wait之前");
            try {
                synchronized (object){
                    object.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1  wait之后");
        });

        //这个线程负责notify
        Thread t2 = new Thread(()->{
            System.out.println("t2  notify之前");
            try {
                Thread .sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (object) {
                object.notify();//notify务必获取到锁,才能进行通知
            }
            System.out.println("t2  notify之后");
        });

        t1.start();
        Thread.sleep(500);
        t2.start();
    }
    

// 运行结果:

t1  wait之前    // 先执行了wait,发生阻塞,没有看到wait之后的打印
t2  notify之前  //  执行到了 t2
t2  notify之后  // t2 进行notify后把 t1 的wait唤醒
t1  wait之后  // t1 继续执行

Process finished with exit code 0

案例二:

// 有三个线程,分别只能打印A,B,C,控制三个线程固定按照ABC的顺序来打印。
public static void main(String[] args) throws InterruptedException {
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread t1 = new Thread(()->{
            System.out.println("A");

            synchronized (locker1) {
                locker1.notify();
            }

        });
        Thread t2 = new Thread(()->{

            // 一进来先阻塞等待
            synchronized (locker1) {
                try {
                    locker1.wait(); //注意防止先notify后wait,需要让t2的wait先于t1的notify执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("B");

            // t2 打印完“B”之后通知 t3 打印“C”
            synchronized (locker2) {
                locker2.notify();
            }
        });

        Thread t3 = new Thread(()->{

            // 一进来就先阻塞等待
            synchronized (locker2) {
                try {
                    locker2.wait();//注意防止先notify后wait,需要让t3的wait先于t2的notify执行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("C");
        });
        t2.start();
        t3.start();
        Thread.sleep(1000);
        t1.start();//让t1执行的慢一点
    }
}

8.wait 和 sleep 的对比

理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间。

  1. wait() 需要搭配 synchronized 使用. sleep 不需要
  2. wait 是 Object 的方法 sleep 是 Thread 的静态方法.
  3. 唯一的相同点就是都可以让线程放弃执行一段时间

精彩评论(0)

0 0 举报