1.轮询机制
通过不断访问共享对象的状态,来判断是否满足了线程执行要求
定义操作类MyObject,内部有一个volatile关键字修饰的list成员变量,使用volatile主要是为了让操作类对象的改变可以让每个线程都能感应到。代码如下:
package com.feng.example;
import java.util.ArrayList;
import java.util.List;
public class MyObject {
//此处必须是volatile,否则线程B中的object感应不到线程A中的object的变化
volatile private List<String> list = new ArrayList<String>();
public void add() {
list.add("hahaha");
}
public int size(){
//System.out.println("hahah");
return list.size();
}
}
定义两个线程类MyThreadA 用于向MyObject对象中添加数据,MyThreadB用于在list中存在大于等于五个元素时退出
代码如下:
package com.feng.example;
public class MyThreadA extends Thread{
private MyObject object;
public MyThreadA(MyObject object)
{
this.object = object;
}
@Override
public void run() {
try {
for(int i=0; i<10; i++)
{
System.out.println("添加了第"+i+"个元素");
object.add();
Thread.sleep(1000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package com.feng.example;
public class MyThreadB extends Thread {
private MyObject object;
public MyThreadB(MyObject object) {
this.object = object;
}
@Override
public void run() {
// object.print();
try {
while (true) {
if (object.size() >= 5) {
System.out.println("有五个元素,线程B应该退出了");
// 终止线程
throw new InterruptedException();
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
测试类代码如下:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
MyObject object = new MyObject();
Thread a = new MyThreadA(object);
Thread b = new MyThreadB(object);
a.start();
b.start();
}
}
分析:线程b在while(true)无限循环中不断的判断object对象的个数,如果超过5就会终止线程。线程b需要不断的去访问object对象的size方法才能知道object对象的状态。这就是轮询的方式,非常的浪费cpu资源。
执行结果如下:
添加了第0个元素
添加了第1个元素
添加了第2个元素
添加了第3个元素
添加了第4个元素java.lang.InterruptedException
有五个元素,线程B应该退出了
at com.feng.example.MyThreadB.run(MyThreadB.java:20)
添加了第5个元素
添加了第6个元素
添加了第7个元素
添加了第8个元素
添加了第9个元素
2.等待/通知机制
2.1等待/通知的知识点
使用wait和notify来实现多个线程间的通信。下面介绍一下这两个方法的注意事项:
(1)wait使线程进入阻塞队列,notify唤醒保持同一锁对象的wait阻塞线程
(2)wait和notify都只能在synchronized语句块或者synchronized方法中使用,保证使用时是持有锁的,否则会抛出异常
(3)调用wait方法,直接释放锁,调用notify方法,执行完同步代码段后才释放锁
(4)线程被notify唤醒后进入就绪队列,重新竞争锁,并不是直接获取锁对象
(5)notify方法只唤醒一个线程,如果有多个wait线程,随机唤醒一个,notifyAll是唤醒所有(前提还是要有同一个锁对象)
(6)notify方法执行之后,如果没有wait阻塞线程,则直接忽略
(7)终止wait中的线程,抛出异常
(8)wait(long) 等待特定的时间,在特定时间内没有被唤醒将自动唤醒
wait和notify,notifyAll方法是object中的方法,所有的类都是继承于object,因此所有的类都有wait和notify,notifyAll方法。
使用等待/通知机制实现上述功能:
修改两个线程类MyThreadA与MyThreadB,代码如下:
package com.feng.example;
public class MyThreadA extends Thread{
private MyObject object;
public MyThreadA(MyObject object)
{
this.object = object;
}
@Override
public void run() {
try {
synchronized(object)
{
for(int i=0; i<10; i++)
{
System.out.println("添加了第"+i+"个元素");
object.add();
if(object.size()==5)
{
object.notify();
System.out.println("通知已发出");
}
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package com.feng.example;
public class MyThreadB extends Thread {
private MyObject object;
public MyThreadB(MyObject object) {
this.object = object;
}
@Override
public void run() {
// object.print();
try {
synchronized(object)
{
if(object.size() < 5)
{
System.out.println("线程b等待");
object.wait();
System.out.println("线程b被唤醒 ");
}
throw new InterruptedException();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
测试类代码修改如下:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
MyObject object = new MyObject();
Thread a = new MyThreadA(object);
Thread b = new MyThreadB(object);
b.start();
try {
Thread.sleep(50); //确保线程b先执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a.start();
}
}
分析:线程a,b启动,线程a先获取到object的锁,往object对象中添加元素,添加到五个时,发出notify通知,但是线程a并不释放锁,而是继续执行,执行完同步代码段之后释放锁,然后线程b获取到锁,终止线程b
运行结果如下:
线程b等待
添加了第0个元素
添加了第1个元素
添加了第2个元素
添加了第3个元素
添加了第4个元素
通知已发出
添加了第5个元素
添加了第6个元素
添加了第7个元素
添加了第8个元素
添加了第9个元素
线程b被唤醒
java.lang.InterruptedException
at com.feng.example.MyThreadB.run(MyThreadB.java:25)
上述程序需要保证是notify发生在wait之后,如果notify先发生,那么wait将永远等待
上述实验也证明了执行notify后是不释放锁的,执行完同步代码块之后才能释放锁。
2.2wait/notify使用前必须获取锁对象
观察下面代码,下面代码中的MyObject类为上例中的MyObject类
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
MyObject object = new MyObject();
try {
object.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
分析:object.wait()必须要有锁对象才能够调用,因此,这里的调用是不对的,运行会抛出非法的监视器状态违例
notify同理,这里就不演示了。执行结果如下:
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:485)
at com.feng.example.ThreadTest.main(ThreadTest.java:13)
修改就是添加synchronized语句块,修改如下:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
MyObject object = new MyObject();
try {
synchronized(object)
{
object.wait();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.3wait直接释放锁,notify执行完同步代码块之后才会释放锁
notify执行完同步代码块之后才能释放锁已经在2.1中证实了。下面验证wait直接释放锁。
修改测试类代码:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
MyObject object = new MyObject();
Thread a = new MyThreadB(object);
Thread b = new MyThreadB(object);
b.start();
a.start();
}
}
运行结果如下:
线程b等待
线程b等待
可以证实wait执行后直接释放锁
2.4 Interrupt wait中的线程,抛出异常
修改测试类:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
MyObject object = new MyObject();
Thread a = new MyThreadB(object);
a.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
a.interrupt();
}
}
运行结果如下:
线程b等待
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:485)
at com.feng.example.MyThreadB.run(MyThreadB.java:22)
2.5 wait(long)
修改MyThreadB类:
package com.feng.example;
public class MyThreadB extends Thread {
private MyObject object;
public MyThreadB(MyObject object) {
this.object = object;
}
@Override
public void run() {
// object.print();
try {
synchronized(object)
{
if(object.size() < 5)
{
System.out.println("线程b等待"+System.currentTimeMillis());
object.wait(1000);
System.out.println("线程b被唤醒 "+System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
修改测试类代码:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
MyObject object = new MyObject();
Thread a = new MyThreadB(object);
a.start();
}
}
运行结果如下:
线程b等待1449803493388
线程b被唤醒 1449803494402
此示例中并没有notify方法唤醒线程a,是等待1s之后自动唤醒。
2.6 wait条件发生变化
定义操作类:
package com.feng.example;
import java.util.ArrayList;
import java.util.List;
public class MyObject {
//此处必须是volatile,否则线程B中的object感应不到线程A中的object的变化
volatile private List<String> list = new ArrayList<String>();
public void add() {
list.add("hahaha");
}
public int size()
{
return list.size();
}
public void sub(){
//System.out.println("hahah");
list.remove(0);
}
}
定义两个线程类,线程B添加元素,线程A删除元素
package com.feng.example;
public class MyThreadA extends Thread {
private MyObject object;
public MyThreadA(MyObject object) {
this.object = object;
}
@Override
public void run() {
try {
synchronized (object) {
if (object.size() <= 0) {
System.out.println("等待===="+Thread.currentThread().getName());
object.wait();
System.out.println("被唤醒==="+Thread.currentThread().getName());
}
object.sub();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package com.feng.example;
public class MyThreadB extends Thread {
private MyObject object;
public MyThreadB(MyObject object) {
this.object = object;
}
@Override
public void run() {
synchronized (object) {
object.add();
System.out.println("添加一个元素");
object.notifyAll();
System.out.println("唤醒所有");
}
}
}
测试类代码:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
MyObject object = new MyObject();
Thread b = new MyThreadB(object);
Thread a = new MyThreadA(object);
Thread c = new MyThreadA(object);
a.start();
c.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
b.start();
}
}
分析:a,c两个线程都是执行的减操作,object中没有数据,两个线程都阻塞,线程b执行添加操作,唤醒a,c线程,只添加了一个元素,却唤醒两个元素去执行减操作,后减的线程会出现越界。
执行结果如下:
等待====Thread-1
等待====Thread-2
添加一个元素
Exception in thread "Thread-1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.RangeCheck(ArrayList.java:547)
at java.util.ArrayList.remove(ArrayList.java:387)
at com.feng.example.MyObject.sub(MyObject.java:23)
at com.feng.example.MyThreadA.run(MyThreadA.java:24)
唤醒所有
被唤醒===Thread-2
被唤醒===Thread-1
修改类MyThreadA的代码,在唤醒后都要重新检查一遍是否满足减的条件,将if修改为while
package com.feng.example;
public class MyThreadA extends Thread {
private MyObject object;
public MyThreadA(MyObject object) {
this.object = object;
}
@Override
public void run() {
try {
synchronized (object) {
while (object.size() <= 0) {
System.out.println("等待===="+Thread.currentThread().getName());
object.wait();
System.out.println("被唤醒==="+Thread.currentThread().getName());
}
object.sub();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
执行结果如下:
等待====Thread-1
等待====Thread-2
添加一个元素
唤醒所有
被唤醒===Thread-2
被唤醒===Thread-1
等待====Thread-1
3.管道通信
3.1字节流
主要用的输入输出流为PipedInputStream PipedOutputStream
定义两个操作类WriteData用于写数据, ReaderData用于读数据,代码如下:
package com.feng.example;
import java.io.IOException;
import java.io.PipedOutputStream;
public class WriteData {
public void WriteMethod(PipedOutputStream out)
{
try {
System.out.println("write:");
for(int i=0; i< 10; i++)
{
String data = ""+i;
out.write(data.getBytes());
System.out.print(data);
}
System.out.println();
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package com.feng.example;
import java.io.IOException;
import java.io.PipedInputStream;
public class ReaderData {
public void ReaderMethod(PipedInputStream in) {
try {
System.out.println("read:");
byte[] data = new byte[11];
int length = in.read(data);
while (length != -1) {
String newData = new String(data, 0, length);
System.out.print(newData);
length = in.read(data);
}
System.out.println();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
定义两个线程类MyThreadA调用写操作,MyThreadB调用读操作,代码如下:
package com.feng.example;
import java.io.PipedOutputStream;
public class MyThreadA extends Thread {
private WriteData writeData;
private PipedOutputStream out;
public MyThreadA(WriteData writeData, PipedOutputStream out) {
this.writeData = writeData;
this.out = out;
}
@Override
public void run() {
writeData.WriteMethod(out);
}
}
package com.feng.example;
import java.io.PipedInputStream;
public class MyThreadB extends Thread {
private ReaderData readerData;
private PipedInputStream in;
public MyThreadB(ReaderData readerData, PipedInputStream in) {
this.readerData = readerData;
this.in = in;
}
@Override
public void run() {
readerData.ReaderMethod(in);
}
}
测试类代码如下:
package com.feng.example;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
try {
WriteData wd = new WriteData();
ReaderData rd = new ReaderData();
PipedOutputStream out = new PipedOutputStream();
PipedInputStream in = new PipedInputStream();
out.connect(in);
Thread a = new MyThreadA(wd, out);
Thread b = new MyThreadB(rd, in);
b.start();
Thread.sleep(2000);
a.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
分析:在测试类中定义管道输入流in管道输出流out,使用in.connect(out)或者out.connect(in)将输入输出流绑定。也就是输入流读取的是输出流写入的数据。程序首先执行线程b,线程b执行读操作,此时输出流并没有写任何数据,因此线程b在in.read(data)处阻塞。线程a执行写操作后,线程b才获取到数据继续执行。
结果如下:
read:
write:
0123456789
0123456789
3.2字符流
主要就是将处理字节改为处理字符,将PipedInputStream改为PipedReader,将PipedOutputStream改为PipedWriter
修改后的操作类如下:
package com.feng.example;
import java.io.IOException;
import java.io.PipedWriter;
public class WriteData {
public void WriteMethod(PipedWriter out)
{
try {
System.out.println("write:");
for(int i=0; i< 10; i++)
{
String data = ""+i;
out.write(data);
System.out.print(data);
}
System.out.println();
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package com.feng.example;
import java.io.IOException;
import java.io.PipedReader;
public class ReaderData {
public void ReaderMethod(PipedReader in) {
try {
System.out.println("read:");
char[] data = new char[11];
int length = in.read(data);
while (length != -1) {
String newData = new String(data, 0, length);
System.out.print(newData);
length = in.read(data);
}
System.out.println();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
线程类如下:
package com.feng.example;
import java.io.PipedWriter;
public class MyThreadA extends Thread {
private WriteData writeData;
private PipedWriter out;
public MyThreadA(WriteData writeData, PipedWriter out) {
this.writeData = writeData;
this.out = out;
}
@Override
public void run() {
writeData.WriteMethod(out);
}
}
package com.feng.example;
import java.io.PipedReader;
public class MyThreadB extends Thread {
private ReaderData readerData;
private PipedReader in;
public MyThreadB(ReaderData readerData, PipedReader in) {
this.readerData = readerData;
this.in = in;
}
@Override
public void run() {
readerData.ReaderMethod(in);
}
}
测试类如下:
package com.feng.example;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
try {
WriteData wd = new WriteData();
ReaderData rd = new ReaderData();
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
out.connect(in);
Thread a = new MyThreadA(wd, out);
Thread b = new MyThreadB(rd, in);
b.start();
Thread.sleep(2000);
a.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果:
read:
write:
0123456789
0123456789
4.join
join方法用于等待线程对象销毁,执行a.join()的线程阻塞,等待线程对象a执行完成后继续执行。
定义线程类:
package com.feng.example;
public class MyThread extends Thread {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"===="+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"===="+System.currentTimeMillis());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
定义测试类:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
try {
Thread a = new MyThread();
a.start();
a.join();
Thread.sleep(100);
System.out.println("线程a执行完了"+System.currentTimeMillis());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果:
Thread-0====1449821481676
Thread-0====1449821483689
线程a执行完了1449821483799
从结果可以看出,main函数中最后的输出语句是在线程a执行完成后输出的。
在join过程中,如果线程(执行join的线程比如上述的主线程)被中断,抛出异常
join(long) 等待有限时间,注意与sleep(long)的区别
5.ThreadLocal的使用
ThreadLocal主要解决线程间变量的隔离性,它会为每一个线程保存一份自己的值。在Struts中使用的就是ThreadLocal
查看下面代码:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
private static ThreadLocal tl = new ThreadLocal();
public static void main(String[] args) {
if(tl.get() == null)
{
System.out.println("没有放过值");
tl.set("newData");
}
System.out.println(tl.get());
System.out.println(tl.get());
}
}
运行结果如下:
没有放过值
newData
newData
说明,没有值时取出的为null
下面来验证ThreadLocal的隔离性,新建两个线程,两个线程向ThreadLocal中添加数据,观察数据会不会出现混乱。
定义工具类:
package com.feng.example;
public class Tools {
public static ThreadLocal t = new ThreadLocal();
}
定义两个线程类:
package com.feng.example;
public class MyThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
Tools.t.set(Thread.currentThread().getName() +" "+ i);
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"==="+Tools.t.get());
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package com.feng.example;
public class MyThreadB extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
Tools.t.set(Thread.currentThread().getName() +" "+ i);
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"==="+Tools.t.get());
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
测试类:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
Thread a = new MyThreadA();
Thread b = new MyThreadB();
a.start();
b.start();
}
}
运行结果:
Thread-1===Thread-1 0
Thread-0===Thread-0 0
Thread-1===Thread-1 1
Thread-0===Thread-0 1
Thread-1===Thread-1 2
Thread-0===Thread-0 2
Thread-1===Thread-1 3
Thread-0===Thread-0 3
Thread-1===Thread-1 4
Thread-0===Thread-0 4
Thread-1===Thread-1 5
Thread-0===Thread-0 5
Thread-1===Thread-1 6
Thread-0===Thread-0 6
Thread-1===Thread-1 7
Thread-0===Thread-0 7
Thread-1===Thread-1 8
Thread-0===Thread-0 8
Thread-1===Thread-1 9
Thread-0===Thread-0 9
从结果中可以看出,数据并没有混乱,可以得知t.get(),是根据执行这条语句的currentThread来提取的值,t.set()也是根绝currentThread来存的值,也就是说每一个线程都会有一个数据的备份,都有自己的值,相互不会影响。
如果想修改初始值,可以重写ThreadLocal值的initialValue方法;
定义自己的类继承ThreadLocal
package com.feng.example;
public class MyThreadLocal extends ThreadLocal{
@Override
protected Object initialValue() {
// TODO Auto-generated method stub
return "自己设置的初始值";
}
}
测试:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
private static MyThreadLocal t = new MyThreadLocal();
public static void main(String[] args) {
System.out.println(t.get());
}
}
执行结果:
自己设置的初始值
可见自己设置的值生效了
6.InheritableThradLocal的使用
此类主要可以获取到父线程的值,(父线程并不是继承关系的,是谁创建的谁就是父线程)
定义自己的ThreadLocal类继承InheritableThreadLocal类,重写initialValue方法,这里返回时间,主要看什么时候初始化的。从而判断是子线程初始化还是父线程初始化的。
代码如下:
package com.feng.example;
import java.util.Date;
public class MyThreadLocal extends InheritableThreadLocal{
@Override
protected Object initialValue() {
// TODO Auto-generated method stub
return new Date();
}
}
定义工具类:
package com.feng.example;
public class Tools {
public static MyThreadLocal t = new MyThreadLocal();
}
定义线程类:
package com.feng.example;
public class MyThreadA extends Thread {
@Override
public void run() {
System.out.println(Tools.t.get());
}
}
定义测试类:
package com.feng.example;
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(Tools.t.get());
Thread a = new MyThreadA();
a.start();
}
}
运行结果:
Fri Dec 11 16:49:48 CST 2015
Fri Dec 11 16:49:48 CST 2015
可见main线程中和a线程中的值是一样的,时间一样说明子线程继承了main线程中的值。
如果想在继承的时候修改,可以重写childValue方法
修改自定义ThreadLocal类,代码如下:
package com.feng.example;
import java.util.Date;
public class MyThreadLocal extends InheritableThreadLocal{
@Override
protected Object initialValue() {
// TODO Auto-generated method stub
return new Date();
}
@Override
protected Object childValue(Object parentValue) {
// TODO Auto-generated method stub
return parentValue +"==="+new Date();
}
}
运行代码如下:
Fri Dec 11 16:51:46 CST 2015
Fri Dec 11 16:51:46 CST 2015===Fri Dec 11 16:51:46 CST 2015
如果父线程中的值修改了,那么子线程获取的是在取值时候的值。
来源:oschina
链接:https://my.oschina.net/u/2309504/blog/542916