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

java笔记15 多线程2(线程通信、Lock)

2015-06-16 00:30 666 查看
1.      线程间通信

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

此时输入输出都要上锁,而且要保证是同一个锁。

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

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

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

3、notifyAll():唤醒线程池中的所有线程。

1.3注意:

1、这些方法都需要定义在同步中。

2、这些方法必须要标示所属的锁。

    A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。

3、这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?

    因为这三个方法都需要定义同步内,并标示所属的同步锁,锁可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。

1.4wait和sleep区别:

wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。

sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

wait:线程会释放执行权,而且线程会释放锁。

sleep:线程会释放执行权,但不是不释放锁。

wait是定义在Object中的方法,sleep是线程中的方法。

2.  生产者消费者示例

public class H_04ThreadWaitNotify
{
public static void main(String[] args)
{
Reso r=new Reso();//创建资源对象
new Thread(new Input(r)).start();//匿名创建线程
new Thread(new Output(r)).start();//匿名创建线程
}
}
class Reso
{
private Stringname;
private Stringsex;
private boolean flag=false;
public synchronized void set(String name,String sex)//锁是this
{
if(this.flag)
try
{
this.wait();//使用时要指定锁
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
this.name=name;
this.sex=sex;
this.flag=true;
this.notify();
}
public synchronized void out()//锁是this
{
if(!this.flag)
try
{
this.wait();
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
System.out.println(name+"......."+sex);
this.flag=false;
this.notify();
}
}

class Input implements Runnable
{
private Reso r;
Input(Reso r)
{
this.r=r;
}
public void run()
{
int x=0;
while(true)
{
if (x==0)
r.set("mike","man");
else
r.set("lili","女");
x=(x+1)%2;
}
}
}

class Output implements Runnable
{
private Reso r;
Output(Reso r)
{
this.r=r;
}
public void run()
{
while(true)
{
r.out();
}
}
}

结果(不停打印)

mike.......man

lili.......女

mike.......man

lili.......女

 

3.  多生产者—消费者问题

代码

public class H_05ProductDemo
{
public static void main(String [] args)
{
Resouce r=new Resouce();
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 Resouce
{
private String name;
private int count=1;
private boolean flag=false;
public synchronized void set (String name)
{
if(flag)
{
try{this.wait();}
catch(Exceptione){}
}
this.name=name+"---"+count++;//名字+编号
System.out.println(Thread.currentThread().getName()+"_______....生产者..._______"+this.name);
flag=true;
this.notify();
}
public synchronized void out ()
{
if(!flag)
{
try{this.wait();}
catch(Exceptione){}
}
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag=false;
this.notify();
}

}
class Producer implements Runnable//生产者调用set方法
{
private Resouce res;
Producer(Resouce res)
{
this.res=res;
}
public void run()
{
while(true)
{
res.set("+商品+");
}
}
}

class Consumer implements Runnable//消费者调用out方法
{
private Resouce res;
Consumer(Resouce res)
{
this.res=res;
}
public void run()
{
while(true)
{
res.out();
}
}
}

 
部分结果

Thread-2_______....生产者..._______+商品+---66093
Thread-0_______....生产者..._______+商品+---66094
Thread-3...消费者...+商品+---66094
Thread-2_______....生产者..._______+商品+---66095
Thread-0_______....生产者..._______+商品+---66096
Thread-3...消费者...+商品+---66096
Thread-2_______....生产者..._______+商品+---66097
Thread-0_______....生产者..._______+商品+---66098
Thread-3...消费者...+商品+---66098

    线程2生产了66093,flag设为true,2继续执行,2等待。分析:(0、2生产,1、3消费)

    线程0(之前等待)生产了66094,flag设为true,线程0等待。

    线程3获得了执行权,消费了66094,flag设为false,3等待,唤醒线程2,

    线程2无需判断,生产了66095,flag为true,线程2等待,并唤醒了线程0.

    线程0也无需判断,生产了66096,flag为true,线程0等待,唤醒了线程3.

 

原因:

    由于if判断,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。故修改成while判断,线程获取CPU执行权及锁后,将重新判断是否具备运行条件。

   notify方法只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。notifyAll解决了本方线程一定会唤醒对方线程的问题。

   

    while+notify的死锁分析

    线程0生产了。001,flag为true,0继续执行,0等待

    线程2判断,flag为true,2等待。

    线程1获取了执行权和锁,消费了001,flag设为false,线程1等待,唤醒了线程0,但是线程0无锁。

    线程3执行,flag为false,线程3等待。此时1、2、3均等待。

    线程0获得了锁,生产了002,flag设为true,唤醒了2,但flag为true,所以2等待。0继续执行,0等待。

    此时所有线程都在等待,形成死锁。

   

故应该使用while+notifyAll,每次判断并唤醒其他所有线程,避免数据出错和死锁。

 

4.  Lock和Condition

jdk1.5 关于多线程升级解决方案:

4.1 将同步synchronized替换成了显式Lock操作。

4.2 将Object中的wait,notify,notifyAll,替换成了condition对象。

4.3 Condition对象可以通过Lock锁获取。

4.4 同一个锁创建不同的Condition对象,同一个Condition对象分别在a方法中等待,在b方法中唤醒。

这样可以实现在本方法中唤醒对方操作,解决了notify可能唤醒本方操作无意义的问题。

注意:释放锁动作一定要执行,要放在finally中。

替换后的主要代码:

class Resouce2
{
private Stringname;
private int count=1;
private boolean flag=false;

private Locklock=new ReentrantLock();//创建一个锁
//Lock是接口,所以用子类创建
private Conditioncondition_pro=lock.newCondition();//创建不同的对象
private Conditioncondition_con=lock.newCondition();

public void set (String name)
{
lock.lock();//显式加锁
try
{
while(flag)
condition_pro.await();//通过Condition对象调用
this.name=name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"_______....生产者..._______"+this.name);
flag=true;
condition_con.signal();//唤醒对方线程(与con对应)
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
finally
{
lock.unlock();//一定要释放锁
}
}
public void out ()
{
lock.lock();
try
{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);
flag=false;
condition_pro.signal();//唤醒对方
} catch (InterruptedExceptione)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
}

替换方式:
     建立锁对象

     建立不同的Condition对象

     去掉原有的synchronized

     使用Condition对象的wait方法

     使用另一个Condition对象的signal方法。

     释放锁。

 

5.  线程的停止

5.1 stop方法已经过时。

5.2 如何停止线程?

只有一种方法:run方法结束,

开启多线程运行,运行代码通常是循环结构,

只要控制住循环,就可以让run方法结束,也就是线程结束。

特殊情况:

当线程处于冻结状态,就不会读取标记,那么线程就不会结束。

5.3 清除冻结状态

当没有指定的方式让冻结的线程恢复到运行状态时,这是需要对冻结状态进行清除。

强制让线程恢复到运行状态汇总来,这样就可以操作标记让线程结束。

Thread提供了interrupt方法。

public class H_07StopThread
{
public static void main(String[] args)
{
StopTest st=new StopTest();
Thread t1=new Thread(st);
Thread t2=new Thread(st);
t1.start();//wait
t2.start();//wait

intnum=0;
while(true)
{
if(num++==60)//主线程运行到num=60 main...60
{
t1.interrupt();//打断wait运行Thread-0...run
t2.interrupt();//打断wait运行Thread-1...run
break;
}
System.out.println(Thread.currentThread().getName()+"......"+num);
}
}
}

class StopTest implements Runnable
{
private boolean flag=true;
public synchronized void run()
{
while(flag)
{
try
{
wait();
}
catch (InterruptedExceptione)
{
flag=false;//在catch中处理
}
System.out.println(Thread.currentThread().getName()+".....run");
}
}
public void changeflag()
{
flag=false;
}
}

 
6.  多线程的其他方法

6.1  setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。

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

t1.setDaemon(true);//运行前执行,当只剩下守护线程时,jvm退出
t2.setDaemon(true);
t1.start();
t2.start();

intnum=0;
while(true)//当主线程运行结束,只剩下守护线程,jvm退出
{
if(num++==60)
{
st.changeflag();
break;
}
System.out.println(Thread.currentThread().getName()+"......"+num);
}

6.2  join:临时加入一个线程的时候可以使用join方法。
当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。

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

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

getPriority():返回线程的优先级。(默认是5)

public class H_09JoinThreadDemo
{
public static void main(String[] args) throws Exception
{
Demo d=new Demo();
Thread t1=new Thread(d);
Thread t2=new Thread(d);
t1.start();
t1.join();//t1强制获得执行权
//t1.setPriority(Thread.MAX_PRIORITY);//静态常量
t2.start();
t2.setPriority(Thread.MIN_PRIORITY);
//t1.join();
for(inti=0;i<80;i++)//主线程在执行
{
System.out.println(Thread.currentThread().toString()+"...."+i);
}
}
}
class Demo implements Runnable
{
public void run()
{
for(inti=0;i<70;i++)
{
System.out.println(Thread.currentThread().toString()+"...."+i);
Thread.yield();//每次运行就切换到另外一个线程执行
}
}
}

结果:

           线程0先执行完(强制插入)

           线程1和main线程穿插执行(线程1的优先级显示为1)

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