Java并发编程学习(三)——线程通信

一、wait and notify

1、先看一个最low的线程通信例子

package com.amuro.studythread.chapter3_communication;

import java.util.ArrayList;
import java.util.List;

public class CommLowestVer
{
    public static void main(String[] args)
    {
        MyList list = new MyList();
        MyThread1 t1 = new MyThread1(list);
        t1.start();
        MyThread2 t2 = new MyThread2(list);
        t2.start();
    }

    static class MyList
    {
        List<String> list = new ArrayList<>();

        void add(String data)
        {
            list.add(data);
        }

        int getSize()
        {
            return list.size();
        }
    }

    static class MyThread1 extends Thread
    {
        MyList list;

        public MyThread1(MyList list)
        {
            this.list = list;
        }

        @Override
        public void run()
        {
            try
            {
                for(int i = 0; i < 100; i++)
                {
                    list.add((i + 1) + "");
                    System.out.println("added");
                    Thread.sleep(500);
                }
            }
            catch(Exception e)
            {

            }
        }
    }

    static class MyThread2 extends Thread
    {
        MyList list;

        public MyThread2(MyList list)
        {
            this.list = list;
        }

        @Override
        public void run()
        {
            while(true)
            {
                if(list.getSize() == 5)
                {
                    System.out.println("stop t2");
                    break;
                }

                System.out.println("t2 run");
            }
        }
    }
}

线程2中的while(true)轮询会严重浪费CPU资源,如果现在几十个,上百个线程都要等待这个结果,这样写代码应该分分钟就会被解雇了……

2、使用wait和notify

package com.amuro.studythread.chapter3_communication;

import java.util.ArrayList;
import java.util.List;

public class CommStandard
{
    public static void main(String[] args) throws Exception
    {
        MyList list = new MyList();

        MyThread2 t2 = new MyThread2(list);
        t2.start();

        Thread.sleep(50);

        MyThread1 t1 = new MyThread1(list);
        t1.start();
    }

    static Object lock = new Object();

    static class MyList
    {
        List<String> list = new ArrayList<>();

        void add(String data)
        {
            list.add(data);
        }

        int getSize()
        {
            return list.size();
        }
    }

    static class MyThread1 extends Thread
    {
        MyList list;

        public MyThread1(MyList list)
        {
            this.list = list;
        }

        @Override
        public void run()
        {
            synchronized (lock)
            {
                try
                {
                    for (int i = 0; i < 10; i++)
                    {
                        list.add((i + 1) + "");
                        System.out.println("added");
                        if (list.getSize() == 5)
                        {
                            System.out.println("t1 notify");
                            lock.notify();
                        }

                        Thread.sleep(500);
                    }
                } 
                catch (Exception e)
                {

                }
            }
        }
    }

    static class MyThread2 extends Thread
    {
        MyList list;

        public MyThread2(MyList list)
        {
            this.list = list;
        }

        @Override
        public void run()
        {
            synchronized (lock)
            {
                try
                {
                    if (list.getSize() != 5)
                    {
                        System.out.println("t2 begin wait");
                        lock.wait();
                        System.out.println("t2 end");
                    }

                } catch (Exception e)
                {

                }
            }

        }
    }
}

程序输出:
t2 begin wait
added
added
added
added
added
t1 notify
added
added
added
added
added
t2 end

t2 end最后才执行的原因是因为notify执行后,并不立即释放锁,其实这里也能看出来原因,如果notify后就释放,那后面的5次add方法就不再是线程安全的了。而wait方法则正好相反,执行后立即释放锁。

3、notify只随机唤醒一个wait的线程,需要唤醒所有wait线程时请使用notifyAll方法。

4、wait(time)是在time时间内等待被notify,如果超过这个时间的话,则自动唤醒。注意,自动唤醒时如果锁被其他线程占用,会继续wait直到锁释放。下面是例子:

package com.amuro.studythread.chapter3_communication;

public class WaitTime
{
    public static void main(String[] args)
    {
        final Object lock = new Object();
        Thread t1 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                synchronized (lock)
                {
                    try
                    {
                        System.out.println("t1 begin");
                        lock.wait(2000);
                        System.out.println("t1 end");
                    }
                    catch(Exception e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                synchronized (lock)
                {
                    try
                    {
                        System.out.println("t2 begin");
                        Thread.sleep(5000);
                        System.out.println("t2 end");
                    }
                    catch (Exception e) 
                    {

                    }
                }

            }
        });
        t2.start();
    }
}

5、使用wait/notify的注意点:
1) wait和notify的时序,如果先notify再wait会导致wait永远无法结束。
2)当使用notifyAll的时候要尤其小心,其他线程在wait()后的代码之间是否会发生冲突,比如同时从数组中remove一个元素导致越界。

6、实现生产消费者模式
1)单生产单消费

package com.amuro.studythread.chapter3_communication;

public class SinglePC
{
    public static void main(String[] args)
    {
        Object lock = new Object();
        P p = new P(lock);
        C c = new C(lock);

        Thread t1 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                while(true)
                {
                    p.set();
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                while(true)
                {
                    c.get();
                }
            }
        });
        t2.start();
    }

    static String value = "";

    static class P
    {
        Object lock;

        public P(Object lock)
        {
            this.lock = lock;
        }

        public void set()
        {
            synchronized (lock)
            {
                try
                {
                    if (!"".equals(value))
                    {
                        System.out.println("P wait");
                        lock.wait();
                    }

                    System.out.println("P set value");
                    value = System.currentTimeMillis() + "";
                    Thread.sleep(1000);
                    lock.notify();
                } 
                catch (Exception e)
                {

                }
            }
        }
    }

    static class C
    {
        Object lock;

        public C(Object lock)
        {
            this.lock = lock;
        }

        public void get()
        {
            synchronized (lock)
            {
                try
                {
                    if ("".equals(value))
                    {
                        System.out.println("C wait");
                        lock.wait();
                    }

                    System.out.println("C get value: " + value);
                    value = "";

                    Thread.sleep(1000);
                    lock.notify();
                } 
                catch (Exception e)
                {
                }
            }
        }
    }
}

2) 多生产多消费者时请不要使用notify,可能会导致假死。请使用notifyAll。同时在wait的进入条件判断时不要使用if,否则可能导致5(2)的问题,请使用while,具体见下面例子
多生产多消费实现stack:

package com.amuro.studythread.chapter3_communication;

import java.util.ArrayList;
import java.util.List;

public class SingleMultiCStack
{
    public static void main(String[] args)
    {
        MyStack stack = new MyStack();
        P p = new P(stack);
        C c = new C(stack);

        for(int i = 0; i < 10; i++)
        {
            MyThreadP tp = new MyThreadP(p);
            tp.setName("P" + i);
            tp.start();
        }

        for(int i = 0; i < 10; i++)
        {
            MyThreadC tc = new MyThreadC(c);
            tc.setName("C" + i);
            tc.start();
        }
    }

    static class MyStack
    {
        List<String> list = new ArrayList<>();

        synchronized void push()
        {
            try
            {
                while(list.size() == 1)
                {
                    System.out.println("push when size == 1, wait...");
                    this.wait();
                }

                Thread.sleep(2000);

                list.add("str " + Math.random());
                this.notifyAll();
                System.out.println("After push list size is " + list.size());
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }

        synchronized String pop()
        {
            String result = "";

            try
            {
                while(list.size() == 0)
                {
                    System.out.println("pop when size == 0, wait...");
                    this.wait();
                }

                Thread.sleep(2000);

                result = list.remove(0);
                this.notifyAll();
                System.out.println("After pop list size is " + list.size());
            }
            catch (Exception e) 
            {
                e.printStackTrace();
            }

            return result;
        }
    }

    static class P
    {
        MyStack stack;

        public P(MyStack stack)
        {
            this.stack = stack;
        }

        void push()
        {
            stack.push();
        }
    }

    static class C
    {
        MyStack stack;

        public C(MyStack stack)
        {
            this.stack = stack;
        }

        void pop()
        {
            System.out.println("C get a str: " + stack.pop());
        }
    }

    static class MyThreadP extends Thread
    {
        P p;

        public MyThreadP(P p)
        {
            this.p = p;
        }

        @Override
        public void run()
        {
            while(true)
            {
                p.push();
            }
        }
    }

    static class MyThreadC extends Thread
    {
        C c;

        public MyThreadC(C c)
        {
            this.c = c;
        }

        @Override
        public void run()
        {
            while(true)
            {
                c.pop();
            }
        }
    }
}

7、使用管道进行线程通信
1)字节流
PipedInputStream和PipedOutputStream
2)字符流
PipedReader和PipedWriter
具体例子请百度,这里不再赘述。

二、join

1、概念:很多情况下,例如在主线程中创建并启动了子线程,如果子线程中需要进行大量的耗时运算,那主线程往往会在子线程之前就结束。所以,当需要主线程等待子线程结束后再结束自身的情况下,就需要用到join。看下面例子:

package com.amuro.studythread.chapter3_communication;

public class JoinExample
{
    public static void main(String[] args) throws Exception
    {
        Thread t = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                try
                {
                    Thread.sleep(3000);
                } 
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }

                System.out.println("t1 end");
            }
        });
        t.start();
        t.join();
        System.out.println("main thread end");
    }

}

程序输出:
t1 end
main thread end

2、join方法被interrupt时,被阻塞的线程会抛出异常,而原线程则继续运行不受影响。

package com.amuro.studythread.chapter3_communication;

public class JoinException
{
    public static void main(String[] args) throws Exception
    {

        Thread t1 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                try
                {
                    System.out.println("t1 begin");
                    Thread.sleep(3000);
                    System.out.println("t1 end");
                } 
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                try
                {
                    t1.join();
                    System.out.println("t2 end");
                } 
                catch (Exception e)
                {
                    System.out.println("t2 join interrupted");
                }           

            }
        });
        t2.start();

        Thread.sleep(1000);
        t2.interrupt();

    }
}

程序输出:
t1 begin
t2 join interrupted
t1 end
可见t2在等待时被中断了,t1本身不受任何影响。

3、join(time) 设定等待的时间,和线程执行的时间相比,较短的优先。

4、sleep和join的区别,不释放锁和释放锁

package com.amuro.studythread.chapter3_communication;

public class JoinAndSleep
{
    public static void main(String[] args) throws Exception
    {
        ThreadB tb = new ThreadB();
        ThreadA ta = new ThreadA(tb);
        ThreadC tc = new ThreadC(tb);

        ta.start();
        Thread.sleep(1000);
        tc.start();
    }

    static class ThreadA extends Thread
    {
        ThreadB tb;

        public ThreadA(ThreadB tb)
        {
            super();
            this.tb = tb;
        }

        @Override
        public void run()
        {
            try
            {
                synchronized (tb)
                {
                    System.out.println("ta begin");
                    tb.start();
//                  Thread.sleep(6000);
                    tb.join(6000);
                    System.out.println("ta end");
                }
            }
            catch (Exception e) 
            {
                e.printStackTrace();
            }
        }
    }

    static class ThreadB extends Thread
    {

        @Override
        public void run()
        {
            try
            {
                System.out.println("tb begin");
                Thread.sleep(5000);
                System.out.println("tb end");
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }

        synchronized void test()
        {
            System.out.println("tb test");
        }
    }

    static class ThreadC extends Thread
    {
        ThreadB tb;

        public ThreadC(ThreadB tb)
        {
            this.tb = tb;
        }

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

当运行sleep时,程序输出:
ta begin
tb begin
tb end
ta end
tb test
当运行join时,程序输出:
ta begin
tb begin
tb test
tb end
ta end
可见join被调用时,释放了锁,而sleep则不会。

三、ThreadLocal

1、ThreadLocal的作用是一个全局的盒子,盒子里可以存储每个线程的私有数据。

package com.amuro.studythread;

public class ThreadLocalExample
{
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args)
    {
        Thread t1 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                for(int i = 0; i < 10; i++)
                {
                    threadLocal.set("t1 : " + (i + 1));
                    System.out.println(threadLocal.get());
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                for(int i = 0; i < 10; i++)
                {
                    threadLocal.set("t2 : " + (i + 1));
                    System.out.println(threadLocal.get());
                }
            }
        });
        t2.start();
    }


}

程序输出:
1 : 1
t1 : 2
t1 : 3
t1 : 4
t1 : 5
t1 : 6
t1 : 7
t1 : 8
t1 : 9
t1 : 10
t2 : 1
t2 : 2
t2 : 3
t2 : 4
t2 : 5
t2 : 6
t2 : 7
t2 : 8
t2 : 9
t2 : 10
可以见t1和t2操作的都是同一个threadLocal,但ThreadLocal是线程安全的。

2、可以通过继承ThreadLocal来为其提供一个初始值:

static class MyThreadLocal extends ThreadLocal<String>
{
    @Override
    protected String initialValue()
    {
        return Thread.currentThread().getName();
    }

}

3、InheritableThreadLocal:子线程如果不调用set方法的话,会继承主线程中set的值。同时也可以通过复写childValue方法来提供子线程自己的value。

package com.amuro.studythread.chapter3_communication;

public class ThreadLocalExamplePro
{
    static class MyThreadLocal extends InheritableThreadLocal<String>
    {
        int count = 0;

        @Override
        protected String initialValue()
        {
            return "main";
        }

        @Override
        protected String childValue(String parentValue)
        {
            count++;
            return parentValue + " : " + count;
        }
    }

    static MyThreadLocal inThreadLocal = new MyThreadLocal();

    public static void main(String[] args) throws Exception
    {
        inThreadLocal.set("main");
        System.out.println(inThreadLocal.get());

        for(int i = 0; i < 10; i++)
        {
            new Thread(new Runnable()
            {

                @Override
                public void run()
                {
                    System.out.println(inThreadLocal.get());
                }

            }).start();
        }

    }
}

程序输出:
main
main : 1
main : 2
main : 3
main : 4
main : 5
main : 6
main : 7
main : 8
main : 9
main : 10

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值