一、懒汉式单列设计模式中的安全问题
解决一个类在内存只存在一个对象的设计模式中想要保证唯一性。单列设计模式很恰当的解决了这一问题。单例设计模式有饿汉式和懒汉式两种方式。懒汉式只有对象被调用时才被初始化,也称对像的延迟加载。与饿汉方式区别也在于此,饿汉创建对象就初始化操作。
懒汉式在多线程控制中有安全隐患。如何避免这一安全隐患,显得很重要。
为了解决多线程中的安全隐患,在懒汉式代码中加入了同步方法synchronized()。使用同步函数,在函数方法上加入synchronized。这样,可以解决多线程中的安全隐患。但是,从程序执行效率考虑,每个进程会多次都会判断对你调用是否初已经初始化,从而运行效率大大降低。通过以snchronized(){}同步代码块的,进一步提高了程序的执行效率。详细代码如下:
classSingle
{
private static s = null;
private Sigle(){}
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
retruns ;
}
}
return s ;
}
}
扩展程序:
用饿汉式实现四个线程卖票小程序实例
//懒汉式(延迟加载)
classSingal implements Runnable
{
private Singal(){}
private static Singal s =null;
private int ticket = 100;
public static Singal getInstance()//用同步函数也可以实现同步,但效率比较低
{
if(s==null)
{
synchronized(Singal.class)//双重判断 提高了效率
{
if(s==null)
s= new Singal();
return s;
}
}
return s;
}
public void run()
{
while(true)
{
synchronized(Singal.class)
{
if(ticket>0)
{
//for(int x=0;x<100 ;x++ )//逻辑错误这样写 只会运行一个线程
//{
try{Thread.sleep(100);}catch(Exceptione){} //延时操作,以遍4个进程都有运行到(由于本笔记本多核处理哭,数据太少而输出又快,有可以用不完四个进程,所以延时以达到效果)
System.out.println(Thread.currentThread().getName()+":"+ticket--);
//}
}
}
}
}
}
class SingalTicket
{
public static void main(String[] args)
{
Singal s =Singal.getInstance();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
Thread t4 = new Thread(s);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
二、生产者与消费者
用四个线程实现,两个生产者生产产品后,另两个消费者消费。要求生产一个产品必须先消费才能再生产下一个商品。
分析:生产者与消费者之前有共享资源(产品);且有多个线程(两个生产者与两个消费者)参与其中;生产者生产过程和消费过程都会使用到产品。以上三点,可以判断整个过程会引发安全隐患。我只需将双方使用产品的过程进行同步实现即可解决这个问题,这个安全隐患。程序代码如下
class ProduceConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Thread t1 = new Thread(newProduce(r));
Thread t2 = new Thread(newConsumer(r));
Thread t3 = new Thread(newProduce(r));
Thread t4 = new Thread(newConsumer(r));
t1.start();
t2.start();
t3.start();
t4.start();
/*
new Thread(newProduce(r)).start();
new Thread(newConsumer(r)).start();
new Thread(newProduce(r)).start();
new Thread(newConsumer(r)).start();
*/
}
}
classResource
{
private String name;
private int count = 1;
private boolean flag;
//t1和t2拥有不同的锁(Produce和Consumer对像),因此能够进入set函数内
public synchronized void set(Stringname)//同步函数的锁是this,在这里this代表它所在类Resource的引用(即调用都是对象)
{
//if(flag) //换while,那么问题就来了,程序会停住。这时,唤醒方式改成notifyAll就可以解决。
//考虑如何处理?想到反复判断flag不就解决了,因此用while(flag)替换if(flag)
// t1放弃资格 t2获取资格//当t2获取资格后就不再判断flag,这样会出现还没消费掉t1生产的商品他t2就紧接着生产。
while(flag)try{this.wait();}catch(Exception e){}
this.name =name+"----"+count++;
System.out.println(Thread.currentThread().getName()+"--生产者--"+this.name);
flag = true;
//this.notify();//notify唤醒的是池中的第一个等待中的线程 notifyAll唤醒池中所有等待中的进程
this.notifyAll();
}
//t1和t2拥有不同的锁(Produce和Consumer对像),因此能够进入out函数内
public synchronized void out()//同步函数的锁是this,在这里this代表它所在类Resource的引用(即调用都是对象)
{
/*if(!flag)*/
t1放弃资格 t2获取资格
while(!flag) try{this.wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"--消费者--------"+name);
flag = false;
//this.notify();
this.notifyAll();
}
}
classProduce implements Runnable
{
Resource r = new Resource();
Produce(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.set("商品");
}
}
}
classConsumer implements Runnable
{
Resource r = new Resource();
Consumer(Resource r)
{
this.r = r;
}
public void run()
{
while(true)
{
r.out();
}
}
}
为实现同步,加入同步函数后发现。程序并达到没有预期的要求(产品必须先消费才能再生产下一个商品)。为达到这个目的,首先分析问题所在。经验证发现在使用产品过程中,同步函数监视器(锁)只能监视共同使用一个监视器的进程才有效,即无法监视具有不同监视器的对象。所以会导致程序中t1和t2对象判断语句判断后停留在那里,这样还会带来安全隐患。因此,为了达到多次判断,我们选择使用while循环语句。当然,引入while循环语句会带来程序停留的问题,不过使用Objec中的唤醒机制中的notifyAll完美解决了这个问。其中,一定要注意使用notify唤醒为什么会程序停留,因为其唤醒的只是池中的第一个进程。这样不难分析,其中生产者与消费者会多次使用产品的问题,而不是目标所要求的。