1.线程间的通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
为了操作同一个资源对象,我们可以将同一个资源对象传给两个线程
代码如下:
package thread;
public class InputOutputDemo {
public static void main(String[] args) {
Resource1 r = new Resource1();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
//资源类
class Resource1{
String name;
String sex;
}
//输入线程
class Input implements Runnable{
private Resource1 r;
public Input(Resource1 r){
this.r = r;
}
@Override
public void run() {
int x = 1;
while(true){
if(x==1){
r.name = "丽丽";
r.sex = "女";
}else{
r.name = "jack";
r.sex = "man";
}
x = 1-x;
}
}
}
//输出线程
class Output implements Runnable{
private Resource1 r;
public Output(Resource1 r){
this.r = r;
}
@Override
public void run() {
while(true)
System.out.println(r.name+":"+r.sex);
}
}
运行结果截图:
结果很诡异,设置完name,还没来得及设置sex的值,就去打印了。这时我们首先想到的是同步。
赋值和打印都是操作的同一个资源,所以都应该加入同步,且应该使用同一把锁。
看看同步后的代码:
//输入线程
class Input implements Runnable{
private Resource1 r;
public Input(Resource1 r){
this.r = r;
}
@Override
public void run() {
int x = 1;
while(true){
synchronized(r){
if(x==1){
r.name = "丽丽";
r.sex = "女";
}else{
r.name = "jack";
r.sex = "man";
}
}
x = 1-x;
}
}
}
//输出线程
class Output implements Runnable{
private Resource1 r;
public Output(Resource1 r){
this.r = r;
}
@Override
public void run() {
while(true){
synchronized(r){
System.out.println(r.name+":"+r.sex);
}
}
}
}
运行结果截图:
结果和我们预期的还是不太一样,我们希望的是存一次数据打印一次,而不是存一次打印了很多次或者存很多次打印一次。
这时我们可以定义一个标志,为true时代表里面存入了值,还没有被打印,为false代表里面之前存入的值已经打印完需要重新存入新的值。那么标志为true时,如果输入线程在运行,我们希望它等待,直到数据被打印完,标记变为false,然后它再运行,存入数据后将标志置为true,并唤醒输出线程。而输出线程则和输入线程刚好相反。
这时我们需要用到wait和notify方法。
代码实现如下:
//资源类
class Resource1{
String name;
String sex;
boolean flag;
}
//输入线程
class Input implements Runnable{
private Resource1 r;
public Input(Resource1 r){
this.r = r;
}
@Override
public void run() {
int x = 1;
while(true){
synchronized(r){
if(r.flag)
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(x==1){
r.name = "丽丽";
r.sex = "女";
}else{
r.name = "jack";
r.sex = "man";
}
r.flag = true;
r.notify();
}
x = 1-x;
}
}
}
//输出线程
class Output implements Runnable{
private Resource1 r;
public Output(Resource1 r){
this.r = r;
}
@Override
public void run() {
while(true){
synchronized(r){
if(!r.flag)
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(r.name+":"+r.sex);
r.flag = false;
r.notify();
}
}
}
}
运行截图:
这里我们用到了等待唤醒机制
等待唤醒机制中主要用到的方法:
wait();
notify();
notifyAll();
都使用在同步中,因为要对持有锁的线程操作。
所以要使用在同步中,因为只有同步才具有锁
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步中的线程时,都必须要标识它们所操作的线程,而这个只有锁。
只有同一个锁上被等待的线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程唤醒。不可以对不同锁上的线程进行唤醒。
也就是说等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
对代码进行优化:
//资源类
class Resource1{
private String name;
private String sex;
private boolean flag;
public synchronized void set(String name, String sex){
if(flag)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out(){
if(!flag)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name+":"+this.sex);
flag = false;
this.notify();
}
}
//输入线程
class Input implements Runnable{
private Resource1 r;
public Input(Resource1 r){
this.r = r;
}
@Override
public void run() {
int x = 1;
while(true){
if(x==1){
r.set("丽丽", "女");
}else{
r.set("jack", "man");
}
x = 1-x;
}
}
}
//输出线程
class Output implements Runnable{
private Resource1 r;
public Output(Resource1 r){
this.r = r;
}
@Override
public void run() {
while(true){
r.out();
}
}
}
</pre><pre name="code" class="java">public class InputOutputDemo {
public static void main(String[] args) {
Resource1 r = new Resource1();
Thread t1 = new Thread(new Input(r));
Thread t2 = new Thread(new Input(r));
Thread t3 = new Thread(new Output(r));
Thread t4 = new Thread(new Output(r));
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行截图:
由截图可以看出,有时候生产了两次才去消费,有时候消费了两次才去生产。
因为当线程1等待时唤醒的可能是线程2,唤醒的是本方线程,这里没有再去判断标记,所以就又去生产了一次。
这时我们可以将if判断语句改为while循环。但是这样还是有可能唤醒本方线程,这样所有的线程都将处于等待状态。
这时我们应该将notify换成notifyAll唤醒所有标记在同一把锁上的线程。
代码如下:
class Resource1{
private String name;
private String sex;
private int count = 1;
private boolean flag;
public synchronized void set(String name, String sex){
while(flag)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name = name;
this.sex = sex;
System.out.println(Thread.currentThread().getName()+"---生产了---"+this.name+"---"+count);
flag = true;
this.notifyAll();
}
public synchronized void out(){
while(!flag)
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---消费了---"+this.name+"---"+count++);
flag = false;
this.notifyAll();
}
}
JDK1.5中提供了多线程升级解决方案
将同步sychronized替换成显示的Lock操作
将Object中的wait、notify、notifyAll替换成了Condition对象
该对象可以Lock锁进行获取
可以实现生产者消费者问题中只唤醒对方的操作
//资源类
class Resource1{
private String name;
private String sex;
private int count = 1;
private boolean flag;
private Lock lock = new ReentrantLock();
private Condition inputCon = lock.newCondition();
private Condition outputCon = lock.newCondition();
public void set(String name, String sex){
lock.lock();
try{
while(flag)
try {
inputCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name = name;
this.sex = sex;
System.out.println(Thread.currentThread().getName()+"---生产了---"+this.name+"---"+count);
flag = true;
outputCon.signal();
}finally{
lock.unlock();
}
}
public void out(){
lock.lock();
try{
while(!flag)
try {
outputCon.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---消费了---"+this.name+"---"+count++);
flag = false;
inputCon.signal();
}finally{
lock.unlock();
}
}
}
如何停止线程?
stop方法已经过时
只有一种,run方法结束
开启多线程运行,运行代码通常是循环结构。
只要控制循环条件,就可以结束循环,结束run方法,停止线程
特殊情况:
当线程处于了冻结状态时,就不会读取到标记,那么线程就不会结束
当没有指定的方式让冻结的线程回复到运行状态时,这时需要对冻结进行清除,
强制让线程恢复到运行状态,这样就可以操作标记让线程结束。
Thread类提供该方法interrupt();将处于冻结状态的线程,强制清除其冻结状态,使其进入运行状态
线程中其它方法
void setDaemon(boolean flag); 将线程标志为后台线程,需在线程启动前调用,前台线程结束后,Java虚拟机退出。
void join();
当A线程执行到了B线程的.join()方法时,A就会等待,进行冻结状态。等B线程都执行完,A才会执行。
join方法可以用来临时加入线程执行
void setPriority(int newPriority);设置优先级,默认值为5,取值范围为1~10
void yeild();暂停当前正在执行的线程,并执行其他线程