Fork me on GitHub

Java 多线程间通信

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(): 暂停当前正在执行的线程对象, 并执行其他线程.



posted @ 2017-08-31 15:36  小a的软件思考  阅读(1257)  评论(0编辑  收藏  举报