您的位置:首页 > 编程语言 > Java开发

黑马程序员_Java基础_线程间通信,生产者消费者案例,jdk1.5锁机制,守护线程

2014-05-20 19:22 661 查看
一,线程间的通信

1,在线程中我们使用多线程进行操作的时候,都是对同一个资源进行相同的操作。如果我们想要通过多线程操作某一个资源但是操作的动作都不相同的时候,这时候就会有两个run方法。那么这两个run方法就代表不同的动作,也就是有不同的线程,那么怎样让着两个线程之间的同步是安全的呢?方法当然是加锁。

我们先来看这个程序的需求:一个人在往系统中输入一个人的姓名和性别,另一个人在输出该系统中的人的姓名和性别;

分析:他们同时在对该系统进行不同的操作,每个人的动作算是一个线程,两个人在操作就是有两个线程,而这两个人的输入和输出又有两个动作,输入的人要输入姓名,还要输入性别,而输出的人同样要输出姓名和性别;

我们加锁后只是保证了在输入的那一个人没有输入完之前,输出的那个人不能输出,当只输入了一个人的姓名和性别,输出的人开始输出了,这时候他可能获取的CPU时间较长,就会不断输出一大块,出入的人同样也是这样。我们的要求还有一个就是,当系统没有输入人的信息的时候,输出的人不能输出,当输出的人输出数据后系统里的信息就没有在输出的必要了,这时候需要告诉输入的人重新输入。这就要用到线程通信的等待唤醒机制。

2,等待唤醒机制:当两个不同的线程进行对同一资源进行操作的时候,他们之间是线程同步的,一个线程对资源操作完之后,唤醒另一个线程,让另一个线程进行资源操作,这时候这个线程就需要让自己处于冻结状态,让另一个线程操作资源,另一个线程操作完之后,唤醒前一个线程,让自己处于冻结状态,同时锁也释放了。

等待唤醒机制中的等待是通过wait()方法完成的,唤醒是通过notify或notifyAll方法完成的。

wait;

notify();唤醒的是单个等待的线程。

notifyall();唤醒的是多个等待的线程;

这几个都是用在同步中,到时必须对持有监视器(锁)的线程进行操作,

所以要使用在同步中,只有线程中财存在锁;

为什么在这些对线程的操作要定义在Object中呢?

因为这些方法操作同步中的线程时,必须都要标示他们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对持有不同锁的进程唤醒;

也就是说等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法是存在于Object中;

class Res
{
String name;
String sex;
boolean flag = false;
}
class Input implements Runnable
{
private Res r;
//Object obj = new Object();
Input(Res r) {
this.r = r;
}
public void run() {
int x = 0;
while(true) {
//synchronized(obj){    如果按照这种方式加锁,我们会发现很明显这是两个不同的锁
synchronized(r) {     //这里不能用超类的子类是因为这样做事两个不同的对象,所以
//该对象必须是同一个对象,这两个锁才能实现同步,所以可以用字
//节码文件代替,如Input.class,Output.class,但是必须一致;也可以是
//公共类r,i,o;
if(r.flag)
try{r.wait();}catch(Exception e){}
if(x==0) {
r.name = "Mike";
r.sex = "man";
}
else {
r.name = "李红";
r.sex = "女";
}
x = (x+1)%2;
r.flag = true;
r.notify();             }
//}
}
}
}
class Output implements Runnable
{
private Res r;
//Object obj = new Object();
Output(Res r) {
this.r = r;
}
public void run() {
while(true) {
//synchronized(obj) {
synchronized(r) {
if(!r.flag)
try{r.wait();}catch(Exception e){}
System.out.println(r.name + "++++++++++++" + r.sex);
r.flag = false;
r.notify();
}
//}
}
}
}
class InputAndOutput
{
public static void main(String[] args) {
Res r = new Res();
Input i = new Input(r);
Output o = new Output(r);
Thread t1 = new Thread(i);
Thread t2 = new Thread(o);
t1.start();
t2.start();
}
}


纵观上述程序发现,程序的封装性并不是太好,类Res里面的属性应该私有化,性别和姓名也应该通过Res类来设置,所以该代码还应该优化,优化代码如下:

class Res
{
private String name;
private String sex;
boolean flag = false;
public synchronized void set(String name,String sex) {
if(flag)
try{this.wait();}catch(Exception e){}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
//两个Synchronized的锁都是Res对象的锁,也就是this的锁
public synchronized void print() {
if(!flag)
try{this.wait();}catch(Exception e){}
System.out.println(name + "++++++++++++" + sex);
flag = false;
this.notify();
}
}
class Input implements Runnable
{
private Res r;
Input(Res r) {
this.r = r;
}
public void run() {
int x = 0;
while(true) {
if(x==0) {
r.set("Mike","man");
}
else {
r.set("李红","女");
}
x = (x+1)%2;
}
}
}
class Output implements Runnable
{
private Res r;
Output(Res r) {
this.r = r;
}
public void run() {
while(true) {
r.print();
}
}
}
class InputAndOutput
{
public static void main(String[] args) {
Res r = new Res();
//创建匿名对象
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}


二,生产者消费者问题是一个线程应用的经典例子,需要严格掌握。

下面是使用jdk1.4之前的线程同步与通信的方法,以及等待唤醒机制。
具体代码步骤分析见代码注释部分。

//创建一个资源,也就是生产者和消费者操作的资源
class Resource
{
//定义产品的名称,记录操作第几个产品,以及一个判断标记字段。
//判断标记用于判断是进行生产操作还是消费操作
private String name;
private int count = 1;
private boolean flag = false;
//定义产品的被生产属性
public synchronized void set(String name) {
while(flag) //判断是否生产了商品,如果存在商品没有被消费则生产者等待。还让让被唤醒的线程再一次判断标记。            try{this.wait();}catch (Exception e){}
//否则生产商品,并打印出结果,将flag置为真,表示生产了商品,没被消费,并且唤醒等待的线程让消费者消费
this.name = name + "---" + count++ ;
System.out.println(Thread.currentThread().getName() + "生产者" + this.name);
flag = true;
this.notifyAll();
}
//定义善品的被消费属性
public  synchronized void get() {
while(!flag)  //如果不存在商品,则消费者sleep等待生产者生产
try{this.wait();}catch (Exception e){}
//否则,如果有生产好的商品需要消费,则消费者消费掉该商品。并将标志位置为false,并唤醒生产者生产
System.out.println(Thread.currentThread().getName() + "消费者" + this.name);
flag = false;
this.notifyAll();
}
}
//定义生产者线程,覆盖run方法,生产一个商品
class Producer implements Runnable
{
private Resource res;
Producer(Resource res) {
this.res = res;
}
public void run() {
while(true) {
res.set("商品");
}
}
}
//定义一个消费者线程,覆盖run方法,消费生产好的商品
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res) {
this.res = res;
}
public void run() {
while(true) {
res.get();
}
}
}
public class ProducerAndConsumer
{
public static void main(String[] args) {
Resource res = new Resource();
//创建生产者消费者线程,生产,消费产品
new Thread(new Producer(res)).start();
new Thread(new Consumer(res)).start();
}
}


三,JDK1.5 中提供了多线程升级解决方案。

将同步synchronized替换成实现Lock操作;将Object中的wait,notify,notifyAll替换成Condition,该对象可以Lock锁进行获取。这样做仅仅是把synchronized换成了Lock,把wait和notify换成了condition,但是会唤醒自己这一方的问题还是没有解决,和之前用synchronized,wait,notify没什么区别。

而Lock存在的意义就是避免notifyAll的时候唤醒自己这一方等待的线程,

只唤醒对方等待的线程;

该例中实现了本方只唤醒对方的操作;

import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();//一个锁上可以有多个condition
private Condition condition_cons = lock.newCondition();//一个锁上可以有多个condition
public void set(String name) throws InterruptedException {
lock.lock();//显示的给代码块上锁,让程序员知道具体在哪里上的锁
try
{
while(flag)
condition_pro.await();//
this.name = name + "---" + count++ ;
System.out.println(Thread.currentThread().getName() + "生产者" + this.name);
flag = true;
condition_cons.signalAll();
}
finally {
lock.unlock();
}
}
public  void get() throws InterruptedException {
lock.lock();
try
{
while(!flag)
condition_cons.await();
System.out.println(Thread.currentThread().getName() + "消费者" + this.name);
flag = false;
condition_pro.signalAll();
}
finally {
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res) {
this.res = res;
}
public void run() {
while(true) {
try
{
res.set("商品");
}catch(InterruptedException e) {}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res) {
this.res = res;
}
public void run() {
while(true) {
try{
res.get();
}catch (InterruptedException e){}
}
}
}
public class ProducerAndConsumer
{
public static void main(String[] args) {
Resource res = new Resource();
new Thread(new Producer(res)).start();
new Thread(new Consumer(res)).start();
}
}


面试:生产者消费者有什么替代方案?

1.5版本以后提供了显式的锁机制,以及显式的所对象上的等待唤醒操作机制,同时将等待唤醒进行了封装,一个锁可以对应多个condition,没有之前一个锁对应一个wait和notify;如果想再有一个wait和notify,必须要在建一个锁同步,而锁的增加可能会引起死锁,而1.5以后想当于 一个锁可以有多个wait和notify。

四,线程的停止方法。

线程的停止方法不能直接使用stop方法,因为stop方法已经过时了。

停止线程的原理是终止run方法,run方法中运行的是线程的运行代码,只要让run方法终止就能停止线程了。

开启多线程的运行通常代码是循环结构的,只要控制循环就可以让run方法结束。

特殊情况:当线程处于冻结状态的时候,就不会读取到标记,那么run方法不会结束,线程也不会结束当没有指定的方法让冻结的线程恢复到运行状态时,这时候需要对冻结进行清除。就是使用interrupt()方法。当线程调用该方法的时候,会抛出InterruptedException异常,对该异常进行try。。。catch处理,处理代码块中将标志置为false就可以结束while循环,从而也就结束了run方法。
示例代码:

public class ThreadStopDemo {
public static void main(String[] args) {
ThreadDem td = new ThreadDem();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
t1.start();
t2.start();

int num = 0;
//主线程中的运行代码
while(true) {
if(num++ == 60) {
//td.changFlag();
t1.interrupt();//强制中断处于冻结状态的线程
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName() + "...main" + num);
}
}
}
class ThreadDem implements Runnable {
private boolean flag = true;
public synchronized void run() {
while(flag) {
try {
wait();
} catch (InterruptedException e) {
//System.out.println("InterruptedException异常");
flag = false;
}
System.out.println(Thread.currentThread().getName() + ".....run");
}
}
/*public void changFlag() {
flag = false;
}*/
}


五,守护线程

守护线程(用户线程)可以理解为后台线程,我们所看到的线程都是前台线程。当把某些线程标记为后台线程后,启动这些线程后和前台线程一样启动,执行。但是后台线程的不同之处在于,当所有的前台线程都结束后,后台线程会自动结束。也就是当一个程序中所有的线程都结束,但是只剩下守护进程的时候,虚拟机会自动停止。

标记为守护线程的方法是Thread类中的setDaemon()方法,该方法要在启动线程之前调用,也就是在调用start方法之前,调用setDaemon();

六,Thread类的join方法:
这个方法不是特别重要,但是有点技术含量。它的作用是,当A线程执行到了B线程的join方法,此时A线程会冻结,B线程获取执行权,当B线程运行结束后,A线程才能获取执行权。Join方法可以临时加入线程执行。

class Demon implements Runnable
{
public void run() {
for(int i=0;i<60;i++) {
System.out.println("Thread......." + i);
}
}
}
class JoinDemon
{
public static void main(String[] args) throws Exception {
Demon d = new Demon();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
//t1.join(); //join加在这里,是说t1要cup的执行权,此时执行权在主线程上,主线程将执行权交给t1,
//主线程会停止,等待t1线程运行结束后才获取执行权,然后主线程和t2争夺资源,交替执行。
t2.start();
t1.join();//join在这个位置时,主线程停止,没有执行权,主线程会等到t1执行完后
//才能拥有执行权,但是t1在主线程等待之前还是有执行权的会和t2交替执行
//但是如果一旦t1运行完,不管t2是否结束,主线程都会抢到执行权。
for (int i=0;i<100 ;i++ )
{
System.out.println("main...." + i);
}
}
}


七,ThreadGroup类中,关于线程有限制的设置方法,setMaxPriority()方法设置优先级,如果想通过控制态输出线程的优先级,可以通过Thread.currentThread().toString()方法打印出线程的完整信息,线程的优先级只有1到10。

Thread中的yield方法:暂停正在执行的线程,执行其他线程。其实作用就是让线程之间循环交替,均匀的循环交替执行。

八,企业中的线程使用:
/*
例如这样一个程序:
class Demon {
public static void main(String[] ars) {
for(int i=0;i<100;i++) {
System.out.println("result" + i);
}
for(int i=0;i<100;i++) {
System.out.println("result" + i);
}
for(int i=0;i<100;i++) {
System.out.println("result" + i);
}
}
}
类中的这三个计算是按顺序计算的,只要第一个不运算结束,后两个基本上没有
计算机会,如果第一个计算足够大,后两个有可能永远不能计算。
*/
class Demon {
public static void main(String[] ars) {
new Thread() {
public void run() {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}.start();
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName() + i);
}
Runnable r = new Runnable() {
public void run() {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
};
new Thread(r).start();
}
}
//这样以后就相当于三个计算在同时进行计算,程序里面有三个线程,他们在交替计算
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐