Java 多线程间通信

时间:2022-12-28 17:30:38

JDK 1.5 以后, 将同步和锁封装成了对象, 并将操作锁的隐式方法定义到了该对象中, 将隐式动作变成了显示动作.

Lock 接口

  1. Lock 接口, 位于 java.util.concurrent.locks 包中, 使用该接口需要导包.
  2. Lock 接口的出现替代了同步代码块或者同步函数, 因为同步代码块对于锁的操作(获取或释放)是隐式的.
    Lock 接口将同步的隐式锁操作变成显式锁操作. 同时,更为灵活, 可以在一个锁上加上多个监视器.
  3. Lock 接口中的方法:
    • lock(): 获取锁
    • unlock(): 释放锁, 这个动作是必须要完成的, 所以通常需要定义在 finally 代码块中
  4. 格式:
Lock lock = new ReentrantLock(); // Lock 接口的实现类
void show()
{
    try
    {
        lock.lock(); //获取锁
        // 执行代码...
    }
    finally
    {
        lock.unlock(); // 释放锁
    }
}

Condition 接口

  1. Condition 接口的出现替代了 Object 类中的 wait(), notify(), notifyAll()方法,将这些
    监视器方法单独进行了封装, 变成 Condition 监视器对象, 可以与任意锁进行组合.
  2. 常用方法:
    • await(): 让线程处于冻结状态
    • signal(): 唤醒一个等待线程
    • signalAll(): 唤醒所有等待线程
  3. 格式:
    Condition c1 = lock.newCondition(); // 新建一个监视器对象

JDK 升级以后的多生产者/多消费者

class Resource
{
    private String name;
    private int count = 1; // 记录烤鸭的编号
    private boolean flag = false;

    // 创建一个锁对象
    Lock lock = new ReentrantLock();

    // 通过已有的锁获取两组监视器, 一组监视生产者, 一组监视消费者
    Condition producer_con = lock.newCondition();
    Condition consumer_con = lock.newCondition();

    public void set(String name)
    {
        lock.lock(); //获取锁
        try
        {
            while(flag)
                try{producer_con.wait();}catch(InterruptedException e){}
            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
            flag = true;
            consumer_con.signal();
        }
        finally
        {
            lock.unlock(); //释放锁
        }
    }

    public void out()
    {
        lock.lock(); //获取锁
        try
        {
            while(!flag)
                try{consumer_con.wait();}catch(InterruptedException e){}
            Sytem.out.println(Thread.currentThread().getName()+ "...消费者.."+ this.name);
            flag = false;
            producer_con.signal();
        }
        finally
        {
            lock.unlock(); //释放锁
        }
    }
}

class Producer implements Runnable
{
    Resource r;
    Producer(Resource r)
    {
        this.r = r;
    }

    public void run()
    {
        while(true)
        {
            r.set("烤鸭");
        }
    }
}

class Consumer implements Runnable
{
    Resource r;
    Consumer(Resource r)
    {
        this.r = r;
    }
    public void run()
    {
        while(true)
        {
            r.out();
        }
    }
}

class ProducerConsumerDemo
{
    public static void main(String[] args)
    {
        // 创建资源
        Resource r = new Resource();

        // 创建任务
        Producer pro = new Producer(r);
        Consumer con = new Consumer(r);

        // 多生产者
        Thread t0 = new Thread(pro);
        Thread t1 = new Thread(pro);

        // 多消费者
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(con);
        Thread t4 = new Thread(con);
        Thread t5 = new Thread(con);

        // 开启线程
        t0.start();
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

wait() 和 sleep() 的区别

  1. wait() 可以指定时间也可以不指定时间
    sleep() 必须指定时间.
  2. 在同步中, 对 CPU 的执行权和锁的处理不同
    • wait(): 释放执行权, 释放锁
    • sleep(): 释放执行权, 不释放锁

停止线程

  1. run() 方法结束
  2. 怎么控制线程的任务结束呢?
    • 任务中都会有循环结构, 只要控制住循环就可以结束任务.
    • 控制循环通常就用定义标记(条件)来完成.
class StopThread implements Runnable
{
    // 定义标记
    private boolean flag = true;
    public void run()
    {
        while(flag)
        {
            System.out.println(Thread.currentThread().getName()+".....");
        }
    }

    // 对外提供改变标记的方法
    public void setFlag()
    {
        flag = false;    
    }
}

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

        Thread t0 = new Thread(st);
        Thread t1 = new Thread(st);

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

        int num = 1;
        for(;;)
        {
            if(++num==50)
            {
                st.setFlag(); // 更改标记
                break;
            }
            System.out.println("main....."+num);
        }

        System.out.println("over");
    }
}
  1. 如果线程处于冻结状态, 无法读取标记, 如何结束呢?
    • 可以使用 interrupt() 方法将线程从冻结状态强制恢复到运行状态, 让线程具备 CPU 的执行资格.
    • 该强制动作会发生 InterruptedException, 需要处理.
class StopThread implements Runnable
{
    // 定义标记
    private boolean flag = true;
    public synchronized void run()  //此处将函数变为同步函数
    {
        while(flag)
        {
            try
            {
                wait();//t0, t1 线程执行到这句时, 都会被处于冻结状态
            }
            catch(InterruptedException e)
            {
                System.out.println(Thread.currentThread().getName()+"..."+e);
                flag = false; // 更改标记
            }
            System.out.println(Thread.currentThread().getName()+".....");
        }
    }

    // 对外提供改变标记的方法
    public void setFlag()
    {
        flag = false;    
    }
}

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

        Thread t0 = new Thread(st);
        Thread t1 = new Thread(st);

        t0.start();
        t1.start();

        int num = 1;
        for(;;)
        {
            if(++num==50)
            {
                t0.interrupt(); // 清除中断状态
                t1.interrupt(); // 清除中断状态
                break;
            }
            System.out.println("main....."+num);
        }

        System.out.println("over");
    }
}

线程类的其他方法

  1. setDaemon(boolean b) : 将该线程标记为守护线程(后台线程).
  2. join() : 临时加入一个线程运算时, 可以使用 join() 方法, 需要处理 InterruptedException
class Demo implements Runnable
{
    public void run()
    {
        for(int x=0; x<50; x++)
        {
            System.out.println(Thread.currentThread().getName()+"......"+x);
        }
    }
}

class JoinDemo
{
    public static void main(String[] args) throws Exception
    {
        Demo d = new Demo();

        Thread t1 = new Thread(d);
        Thread t2 = new Thread(d);

        t1.start();
        t1.join(); // 临时加入 t1 线程, 执行权移交给 t1, 必须等 t1 运行完成后, 其他线程才运行
        t2.start();

        for(int x=0; x<50; x++)
        {
            System.out.println(Thread.currentThread().getName()+"..."+x);
        }
    }
}
  1. toString() : 返回该线程的字符串表示形式, 包括线程名称, 优先级和线程组.
    • 优先级: 获取 CPU 执行权的机率, 分为 1~10个数字, 其中:
    • 最高优先级: MAX_PRIORITY , 代表数值 10
    • 最低优先级: MIN_PRIORITY , 代表数值 1
    • 默认优先级: NORM_PRIORITY , 代表数值 5
  2. yield(): 暂停当前正在执行的线程对象, 并执行其他线程.