您的位置:首页 > 职场人生

黑马程序员——Java语言基础——04.多线程(2)线程间通信

2014-12-18 15:50 721 查看


------- android培训java培训、期待与您交流!
----------

本节考点:

控制线程的任务结束
任务中都会有循环结构,只要控制住循环就可以结束任务。

控制循环通常就用定义标记来完成。

第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。强制动作会发生InterruptedException,一定要记得处理

2-1 线程间通信

2-1-1 线程间通信涉及的方法

多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。

等待/唤醒机制涉及的方法:

①wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。

②notify():唤醒线程池中的一个线程(任何一个都有可能)。

③notifyAll():唤醒线程池中的所有线程。
P.S.

1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。

2、必须要明确到底操作的是哪个锁上的线程!

3、wait和sleep区别?

①wait可以指定时间也可以不指定。sleep必须指定时间。

②在同步中时,对CPU的执行权和锁的处理不同。

wait:释放执行权,释放锁。

sleep:释放执行权,不释放锁。
为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法,监视器其实就是锁。

锁可以是任意的对象,任意的对象调用的方式一定在object类中。
两个线程的生产者消费者示例:
class Person
{
private String name;
private String sex;
private boolean flag = false;

public synchronized void setPerson(String name,String sex)
{
if(flag)
try{this.wait();}catch(Exception e){}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}

public synchronized void getPerson()
{
if(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(name+"...."+sex);
flag = false;
this.notify();
}

}

class Input implements Runnable
{
private Person p;
Input(Person p)
{
this.p = p;
}

boolean b = true;	//用来保证交替运行两个线程,也可以b=0,if(b == 0),else结束后,加上 b = (b+1)%2;
public void run()
{
while(true)
{
if (b)			//if、else是一个整体,不能只同步if或只同步else中的内容
{
p.setPerson("李雷","男");
b = false;
}
else
{
p.setPerson("韩梅梅","女");
b = true;
}
}
}
}

class Output implements Runnable
{
private Person p;

Output(Person p)
{
this.p = p;
}

public void run()
{
while(true)
{
p.getPerson();
}
}

}

class WaitDemo
{
public static void main(String[] args)
{
Person p = new Person();//只建立一个对象,两个线程共同操作其中的数据

Input in = new Input(p);//将对象传入
Output out = new Output(p);

Thread t1 = new Thread(in);
Thread t2 = new Thread(out);

t1.start();
t2.start();
}
}
多个线程的生产者消费者
class ProConDemo
{
public static void main(String[] args)
{
Factory f = new Factory();

Producer p = new Producer(f);
Consumer c = new Consumer(f);

Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
Thread t4 = new Thread(c);

t1.start();
t2.start();
t3.start();
t4.start();

}
}

class Factory
{
private String name;
private int count = 1;
private boolean flag = false;

public synchronized void set(String name)
{
while(flag)
try{this.wait();}catch(InterruptedException e){System.out.println("线程中断");}
this.name = name+"  "+count++;
System.out.println(Thread.currentThread().getName()+"。。生产者。。"+this.name);
flag = true;
this.notifyAll();
}

public synchronized void get()
{
while(!flag)
try{this.wait();}catch(InterruptedException e){System.out.println("线程中断");}
System.out.println(Thread.currentThread().getName()+"。。消费者。。"+this.name);
flag = false;
this.notifyAll();
}
}

class Producer implements Runnable
{
private Factory f;

Producer(Factory f)
{
this.f = f;
}

public void run()
{
while(true)
{
f.set("++++BMW++++");
}
}
}

class Consumer implements Runnable
{
private Factory f;

Consumer(Factory f)
{
this.f = f;
}

public void run()
{
while(true)
{
f.get();
}
}
}
多个线程和两个线程有区别:
因为有多个线程,不能只判断一次,会出现线程不安全,所以必须用while来判断
while判断标记+notify会导致死锁,所以必须用notifyAll();

2-1-2 JDK1.5新特性-Lock、Condition

同步代码块就是对于锁的操作是隐式的。

JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。

Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。

lock():获取锁。

unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。

Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。

Condition接口中的await方法对应于Object中的wait方法。

Condition接口中的signal方法对应于Object中的notify方法。

Condition接口中的signalAll方法对应于Object中的notifyAll方法。

示例,假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个condition实例来做到这一点。

class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull  = lock.newCondition();
final Condition notEmpty = lock.newCondition();

final Object[] items = new Object[100];
int putptr, takeptr, count;

public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}

public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}



修改生产者消费者多线程代码如下:

package com.itheima.base;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* 需求:生产者消费者多线程例子
* @author user
*
*/
public class ProConDemo {

public static void main(String[] args) {
Resource r = new Resource();
new Thread(new Producer(r)).start();
new Thread(new Consumer(r)).start();
new Thread(new Producer(r)).start();
new Thread(new Consumer(r)).start();

}

}

class Resource {

private int count = 1;
private String name;
private boolean flag = false;
Lock lock = new ReentrantLock();
Condition notFull  = lock.newCondition();
Condition notEmpty = lock.newCondition();

public void setGoods(String name){
lock.lock();
try{
while (flag)
try{notFull.await();}catch(Exception e){}
this.name = name;
System.out.println(Thread.currentThread()+" "+name + "  " + count++);
notEmpty.signalAll();
flag = true;
}
finally {
lock.unlock();
}

}

public void putGoods() {
lock.lock();
try{
while (!flag)
try{notEmpty.await();} catch(Exception e){}
System.out.println(Thread.currentThread()+" "+name + "  " + count);
notFull.signalAll();
flag = false;
}
finally {
lock.unlock();
}

}

}

class Producer implements Runnable {

private Resource r;

Producer(Resource r) {
this.r = r;
}

public void run() {
while(true){
r.setGoods("nishishabi");
}
}
}

class Consumer implements Runnable {

private Resource r;

Consumer(Resource r) {
this.r = r;
}

public void run() {
while (true){
r.putGoods();
}
}
}


2-1-3 停止线程

怎么控制线程的任务结束呢?

任务中都会有循环结构,只要控制住循环就可以结束任务。

控制循环通常就用定义标记来完成。

第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。强制动作会发生InterruptedException,一定要记得处理
class StopThread implements Runnable{
private boolean flag = true;
public void run(){
while(flag){
try{
wait();
} catch(InterruptedException e){
System.out.println(Thread.currentThread().getName() + "..." + e);
flag = false;
}
System.out.println(Thread.currentThread().getName() + "......");
}
}
public void setFlag(){
flag = false;
}
}

class StopThreadDemo{
public static void main(String[] args){
StopThread st = new StopThread();

Thread t1 = new Thread(st);
Thread t2 = new Thread(st);

t1.start();
t2.start();

int num = 1;
for(;;){
if(++num == 50){
t1.interrupt();
t2.interrupt();
break;
}
System.out.println( "main..." + num);
}
System.out.println( "over");
}
}

2-1-4 守护线程(后台线程)
setDaemon(boolean on):将该线程标记为守护线程或者用户线程。

当主线程结束,守护线程自动结束,比如圣斗士星矢里面的守护雅典娜,在多线程里面主线程就是雅典娜,守护线程就是圣斗士,主线程结束了,守护线程则自动结束。

当正在运行的线程都是守护线程时,java虚拟机jvm退出;所以该方法必须在启动线程前调用;

守护线程的特点:

守护线程开启后和前台线程共同抢夺cpu的执行权,开启、运行两者都没区别,

但结束时有区别,当所有前台线程都结束后,守护线程会自动结束。
示例:
class StopThread implements Runnable
{
private boolean flag =true;
public  void run()
{
while(flag)
{

System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag()
{
flag = false;
}
}

class  StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();

Thread t1 = new Thread(st);
Thread t2 = new Thread(st);

t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();

int num = 0;

while(true)
{
if(num++ == 60)
{
//st.changeFlag();
//t1.interrupt();
//t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}<span style="font-family: 微软雅黑; text-align: -webkit-auto; background-color: rgb(255, 255, 255);">		</span>

2-1-5 线程其他方法

多线程join方法:

void join() 等待该线程终止。

void join(long millis)  等待该线程终止的时间最长为 millis 毫秒。throws InterruptedException         

特点:当A线程执行到B线程的join方法时,A就会等待B线程都执行完,A才会执行

作用: join可以用来临时加入线程执行;

多线程优先级:yield()方法

yield():暂停当前正在执行的线程对象,并执行其他线程

setPriority(int newPriority):更改线程优先级

int getPriority() 返回线程的优先级。

String toString() 返回该线程的字符串表示形式,包括线程名称、优先级和线程组

(1)MAX_PRIORITY:最高优先级(10级)

(2)Min_PRIORITY:最低优先级(1级)

(3)Morm_PRIORITY:默认优先级(5级)
2-2 其他相关知识

(1)什么是ThreadLocal类,怎么使用它?
ThreadLocal类提供了线程局部 (thread-local) 变量。是一个线程级别的局部变量,并非“本地线程”。
ThreadLocal 为每个使用该变量的线程,提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本
下面是线程局部变量(ThreadLocal variables)的关键点:
一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。
ThreadLocal 实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。 
当多个线程访问 ThreadLocal 实例时,每个线程维护 ThreadLocal 提供的独立的变量副本。
常用的使用可在 DAO 模式中见到,当 DAO 类作为一个单例类时,
数据库链接(connection)被每一个线程独立的维护,互不影响。(基于线程的单例)

(2)什么时候抛出InvalidMonitorStateException异常?为什么?
调用 wait ()/notify ()/notifyAll ()中的任何一个方法时,如果当前线程没有获得该对象的锁,
那么就会抛出 IllegalMonitorStateException 的异常
也就是说程序在没有执行对象的任何同步块或者同步方法时,
仍然尝试调用 wait ()/notify ()/notifyAll ()时。由于该异常是 RuntimeExcpetion 的子类,
所以该异常不一定要捕获(尽管你可以捕获只要你愿意
作为 RuntimeException,此类异常不会在 wait (),notify (),notifyAll ()的方法签名提及。 

(3)在静态方法上使用同步时会发生什么事?
同步静态方法时会获取该类的“Class”对象,所以当一个线程进入同步的静态方法中时,
线程监视器获取类本身的对象锁,其它线程不能进入这个类的任何静态同步方法。
它不像实例方法,因为多个线程可以同时访问不同实例同步实例方法。

(4)当一个同步方法已经执行,线程能够调用对象上的非同步实例方法吗?
可以,一个非同步方法总是可以被调用而不会有任何问题。
实际上,Java 没有为非同步方法做任何检查,锁对象仅仅在同步方法或者同步代码块中检查。
如果一个方法没有声明为同步,即使你在使用共享数据Java照样会调用,而不会做检查是否安全,
所以在这种情况下要特别小心。一个方法是否声明为同步取决于临界区访问(critial section access),
如果方法不访问临界区(共享资源或者数据结构)就没必要声明为同步的。

(5)在一个对象上两个线程可以调用两个不同的同步实例方法吗?
不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。
所以只有执行完该方法释放对象锁后才能执行其它同步方法。

(6)什么是线程饿死,什么是活锁?
线程饿死和活锁虽然不像死锁一样是常见的问题,但是对于并发编程的设计者来说就像一次邂逅一样。
当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。
JavaAPI 中线程活锁可能发生在以下情形:
当所有线程在程序中执行 Object.wait (0),参数为 0 的 wait 方法。
程序将发生活锁直到在相应的对象上有线程调用 Object.notify ()或者 Object.notifyAll ()。
当所有线程卡在无限循环中。


------- android培训java培训、期待与您交流!
----------

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: