Java多线程通信

上一篇介绍Java提供synchronized关键字来实现多线程同步。如下例所示:
代码:
class ThreadA implements Runnable {
private Parcel7 p;

public ThreadA(Parcel7 p) {

this.p = p;

}

public void run() {

while (true) {

p.input();

}

}

}

class ThreadB implements Runnable {

private Parcel7 p;

public ThreadB(Parcel7 p) {

this.p = p;

}

public void run() {

while (true) {

p.output();

}

}

}

public class Parcel7 {

private int count = 0;

synchronized void input() {

count ++;

System.out.println(Thread.currentThread().getName() + "+++  " + count);

}

synchronized void output() {

count --;

System.out.println(Thread.currentThread().getName() + "---  " + count);

}

public static void main(String[] args) {

Parcel7 test = new Parcel7();

ThreadA a = new ThreadA(test);

ThreadB b = new ThreadB(test);

new Thread(a).start();

new Thread(b).start();

}

}
使用了两个synchronized 方法来使的对count的操作对于线程a和b是一个互斥的过程。但是如果我们我们想要使得a,b线程交替执行的话,我们需要使用到多线程之间的通信方式:wait(),notify(),notifyAll()。这三个方法是属于java.lang.Object类中的,即所有对象都包含这三个方法。wait()是使当前线程放弃CPU执行权限,并等待(会抛出一个 InterruptedException的异常)。void notify()和void notifyAll()用来唤醒持有该对象锁线程,前者唤醒单个,后者唤醒所有。当然,这三者所操作的都是持有对象锁的线程,所以需要跟synchronized一起使用。
代码:

class ThreadA implements Runnable {

private Parcel7 p;

public ThreadA(Parcel7 p) {

this.p = p;

}

public void run() {

while (true) {

p.input();

}

}

}

class ThreadB implements Runnable {

private Parcel7 p;

public ThreadB(Parcel7 p) {

this.p = p;

}

public void run() {

while (true) {

p.output();

}

}

}

public class Parcel7 {

private int count = 0;

private boolean flag = false;

synchronized void input() {

while (flag) {

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

count ++;

System.out.println(Thread.currentThread().getName() + "+++  " + count);

flag = true;

this.notify();

}

synchronized void output() {

while (!flag) {

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

count --;

System.out.println(Thread.currentThread().getName() + "---  " + count);

flag = false;

this.notify();

}

public static void main(String[] args) {

Parcel7 test = new Parcel7();

ThreadA a = new ThreadA(test);

ThreadB b = new ThreadB(test);

new Thread(a).start();

new Thread(b).start();
                // new Thread(a).start();
                // new Thread(b).start();

}

}
可以正确使线程a,b交替执行,因为每次a执行完一次之后都会,把b唤醒并自己进入wait状态,b被唤醒后做a同样的操作。但当每个任务都不止一个线程操作时,(把上例的注释部分去掉)会出现如下情况:
【运行结果】

Thread-3---  0

Thread-0+++  1

Thread-1---  0

Thread-0+++  1
Thread-3---  0
没有无限执行而是停住了。因为四个线程都陷入wait状态了。
解决办法:
将notify改成notifyAll,每次都把所有wait中的线程唤醒,主要是为了确保把执行不同操作的线程唤醒。而不是唤醒执行相同操作的线程。问题:使用notifyAll不仅把对方线程唤醒,还把本方线程也都唤醒了。如何只唤醒本方线程。
java5.0之前只能通过synchronized提供锁机制,而java5.0之后提供了一个接口:Lock里面包含了lock()获取锁的方法,和unlock()主动解锁的方法。并且将对象锁封装成一个接口Condition,通过Condition的实例对象来取代原本的对象锁。
代码:

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

class ThreadA implements Runnable {

private Parcel7 p;

public ThreadA(Parcel7 p) {

this.p = p;

}

public void run() {

while (true) {

p.input();

}

}

}

class ThreadB implements Runnable {

private Parcel7 p;

public ThreadB(Parcel7 p) {

this.p = p;

}

public void run() {

while (true) {

p.output();

}

}

}

public class Parcel7 {

private int count = 0;

private boolean flag = false;

Lock lock = new ReentrantLock();

Condition condition = lock.newCondition();

Condition condition2 = lock.newCondition();

void input() {

lock.lock();

try{

while (flag) {

try {

condition.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

count ++;

System.out.println(Thread.currentThread().getName() + "+++  " + count);

flag = true;

condition2.signal();

}finally{

lock.unlock();

}

}

void output() {

lock.lock();

try{

while (!flag) {

try {

condition2.await();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

count --;

System.out.println(Thread.currentThread().getName() + "---  " + count);

flag = false;

condition.signal();

}finally{

lock.unlock();

}

}

public static void main(String[] args) {

Parcel7 test = new Parcel7();

ThreadA a = new ThreadA(test);

ThreadB b = new ThreadB(test);

new Thread(a).start();

new Thread(a).start();

new Thread(b).start();

new Thread(b).start();

}

}
对于synchronized每次只能持有一把锁,而对于Lock可以通过newCondition方法来获取好几个不同的对象锁。
总结:
synchronized与lock的区别:
1,ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
     线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,

     如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断

     如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事
2,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动  释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
3,在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
停止线程:interrupt(),java.lang.Thread中的一个方法,用于将wait()、join()、sleep的线程中断状态清除并返回一个InterruptedException。
代码:

class ThreadA implements Runnable {

public synchronized void run() {

try {

this.wait();

} catch (InterruptedException e) {

System.out.println("从中断中恢复!");

}

}

}

public class Parcel7 {

public static void main(String[] args) {

ThreadA a = new ThreadA();

Thread b= new Thread(a);

b.start();

b.interrupt();

}

}
【运行结果】
从中断中恢复!
守护线程:是为其他线程的运行提供便利的线程。守护线程不会阻止程序的终止。Java的垃圾收集机制的某些实现就使用了守护线程。
非守护线程:包括常规的用户线程或诸如用于处理GUI事件的事件调度线程。
java.lang.Thread中的一个方法setDaemon(),用于将某线程设置为守护线程,必须在调用它的start方法之前进行设置,否则,抛出IllegalThreadStateException异常。程序只有守护线程时,该程序便可以结束运行。
代码:
class ThreadA implements Runnable {

public  void run() {

while (true) {

System.out.println(Thread.currentThread().getName()+"守护线程!");

}

}

}

public class Parcel7 {

public static void main(String[] args) {

ThreadA a = new ThreadA();

Thread b= new Thread(a);

Thread c= new Thread(a);

b.setDaemon(true);

c.setDaemon(true);

b.start();

c.start();

}

}

当主线程结束时,两个守护线程也同时结束,虽然执行时while(true)的无限循环;
等待线程结束:java.lang.Thread中的方法join();如果被interrupt()中断时抛出InterruptedException。也可以用来实现线程之间的同步。

代码:

class ThreadA implements Runnable {

private Parcel7 p;

public ThreadA(Parcel7 p) {

this.p = p;

}

public void run() {

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

p.inc();

}

}

}

public class Parcel7 {

private static int a =0;

void inc(){

a++;

}

public static void main(String[] args) {

ThreadA A = new ThreadA(new Parcel7());

Thread b= new Thread(A);

b.start();

System.out.println(a);

try {

b.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(a);

}

}
【输出结果】
0

10
释放cpu执行权:yield()。该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。而sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。sleep和yield都不会释放对象锁。

        实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”,它把运行机会让给了同等级的其他线程。
        sleep 方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程此时获取CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用sleep方法,也没有受到I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,方可有机会运行。

你可能感兴趣的:(java,多线程,thread)