一、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