——- android培训、java培训、期待与您交流! ———-
对于操作同一资源的不同行为,该如何解决?
思路:因为两个线程操作的是同一资源,为避免出现数据安全问题,必须实现同步,且设置的同步锁对象一致,由于是两个线程的两种行为,可以将资源类名.class作为同步锁传入。
例子一:一边输入姓名和性别,一边输出。
定义资源类,包括姓名,性别,两个属性。
class Resource
{
String name;
String sex;
}
输入类,实现Runnable接口,作用,轮流输入两个人的姓名和性别。
class Input implements Runnable
{ private Resource resource;
public Input(Resource resource)
{
this.resource = resource;
}
public void run()
{ int i=1;
while(true)
{
if(i%2==0)
{
resource.name = "mack";
resource.sex = "male";
}else
{
resource.name = "莉莉";
resource.sex = "女女女女女女";
}
i++;
}
}
}
输出类:实现Runnable接口,作用,输出资源里的姓名和性别。
class Output implements Runnable
{ private Resource resource;
public Output(Resource resource)
{
this.resource = resource;
}
public void run()
{ while(true)
{
System.out.println(resource.name+"....."+resource.sex);
}
}
}
在主函数中实例化并调用两个线程
public class ThreadDemo
{
public static void main(String args[])
{
Resource resource = new Resource();
Input input = new Input(resource);
Output out = new Output(resource);
Thread t1 = new Thread(input);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
在未执行同步操作之前,输出结果为:
发现出现了数据异常,并没有同步输出对应的名字和性别。因此需要使用同步。
同步之后代码块如下:
class Input implements Runnable
{ private Resource resource;
public Input(Resource resource)
{
this.resource = resource;
}
public void run()
{ int i=1;
while(true)
{
synchronized(Resource.class)
{
if(i%2==0)
{
resource.name = "mack";
resource.sex = "male";
}else
{
resource.name = "莉莉";
resource.sex = "女女女女女女";
}
i++;
}
}
}
}
class Output implements Runnable
{ private Resource resource;
public Output(Resource resource)
{
this.resource = resource;
}
public void run()
{ while(true)
{
synchronized(Resource.class)
{
System.out.println(resource.name+"....."+resource.sex);
}
}
}
}
运行结果如下:
应用同步解决了数据混乱的问题。
但是输出方式却是连着输出,而不符合轮流输出的效果。此时需要在输入之后,让输入线程等待,让输出线程取出之后,唤醒输入线程,同时输出线程等待,如此循环,能到达轮流输出的效果。
这就是重点:
线程间的通信,等待唤醒机制
(一)利用Synchronized同步来实现
修改上述代码:
共享资源项:
class Resource
{
String name;
String sex;
boolean flag = true;
}
输入:
class Input implements Runnable
{ private Resource resource;
public Input(Resource resource)
{
this.resource = resource;
}
public void run()
{ int i=1;
while(true)
{
synchronized(Resource.class)
{ if(resource.flag)
{
try{Resource.class.wait();}catch(Exception e){}
}
if(i%2==0)
{
resource.name = "mack";
resource.sex = "male";
}else
{
resource.name = "莉莉";
resource.sex = "女女女女女女";
}
i++;
resource.flag = true;
Resource.class.notify();
}
}
}
}
输出:
class Output implements Runnable
{ private Resource resource;
public Output(Resource resource)
{
this.resource = resource;
}
public void run()
{ while(true)
{
synchronized(Resource.class)
{
if(!resource.flag)
{
try{Resource.class.wait();}catch(Exception e){}
}
System.out.println(resource.name+"....."+resource.sex);
resource.flag = false;
Resource.class.notify();
}
}
}
}
运行结果如下:
现在实现了轮流输出的效果。
上面讲述的是一个输入一个输出,假如两个生成者,两个消费者,看看结果如何?
例子二:两个生成者,两个消费者,开启四个线程。
class Resource
{
private String name;
private int count;
boolean flag = true;
public synchronized void set(String name)
{
if(flag)
{
try{this.wait();}catch(Exception e){}
}
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(Exception e){}
}
System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
flag = false ;
this.notify();
}
}
class Producer implements Runnable
{
private Resource resource;
public Producer(Resource resource)
{
this.resource = resource;
}
public void run()
{
while(true)
{
resource.set("张三");
}
}
}
class Consumer implements Runnable
{
private Resource resource;
public Consumer(Resource resource)
{
this.resource = resource;
}
public void run()
{
while(true)
{
resource.out();
}
}
}
public class ProducerConsume
{
public static void main(String args[]) throws Exception
{
Resource resource = new Resource();
Producer pro = new Producer(resource);
Consumer con = new Consumer(resource);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果如下:
可以看出:出现了问题,当生产者生产出好几个,但是消费者都未消费掉,是因为当第一个生产者生产完,首先被唤醒的是第二个生产者,而它未经过判断标志位就开始生产,从而产生了这种情况。
修改办法:
1)把Resource类中同步函数部分中,把if换成while(避免唤醒后不用判断);
2)而用whlie可以实现循环判断,四个线程会同时出现等待状态,为避免这种情况,修改notify为notifyAll来实现。
class Resource
{
private String name;
private int count;
boolean flag = true;
public synchronized void set(String name)
{
while(flag)
{
try{this.wait();}catch(Exception e){}
}
this.name = name+"......."+count++;
System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
flag = true ;
this.notifyAll();
}
public synchronized void out()
{
while(!flag)
{
try{this.wait();}catch(Exception e){}
}
System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
flag = false ;
this.notifyAll();
}
}
运行结果如下:
(二)JDK1.5之后Lock类
JDK1.5之后同步的新优点:
第一点:把隐性的synchronized同步锁变成了可见的Lock代替,上锁lock和解锁unlock。
第二点:把Object(可操作wait,notify,notifyAll)同步锁换成了Condition接口(await,signal,signalAll),此接口可以通过Lock类获得newCondition。
第三点:最大的优点,一个锁同时可以拥有许多个condition对象,可以有选择性的唤醒个别线程。
备注:必须注意同步最后需要解锁,此类最大的好处是只定义一个锁,就可以有好几个condition,避免了synchronized定义多个锁引起的同步嵌套,避免了死锁的问题。实现了只唤醒对方线程的优点。
例子:改写上述两个生产和两个消费过程的实例,
import java.util.concurrent.locks.*;
class Resource
{
private String name;
private int count;
Lock lock = new ReentrantLock();
Condition conditionPro = lock.newCondition();
Condition conditionCon = lock.newCondition();
boolean flag = true;
public void set(String name) throws Exception
{
lock.lock();
try
{
while(flag)
conditionPro.await();
this.name = name+"......."+count++;
System.out.println(Thread.currentThread().getName()+".....生产者...."+this.name);
flag = true ;
conditionCon.signal();
}finally
{
lock.unlock();
}
}
public void out() throws Exception
{
lock.lock();
try
{
while(!flag)
conditionCon.await();
System.out.println(Thread.currentThread().getName()+".....消费者...."+this.name);
flag = false ;
conditionPro.signal();
}finally
{
lock.unlock();
}
}
}
运行结果如下:
完美的解决了问题。比起synchronized更直观,更清楚的让人理解。