黑马程序员——Java基础---多线程(2)

来源:互联网 发布:sqlserver 对等发布 编辑:程序博客网 时间:2024/06/07 23:43

   -----------android培训java培训、java学习型技术博客、期待与您交流!------------ 

多线程

一、单例设计模式
单例设计模式:一种有两种:懒汉式和饿汉式。
示例:
//单例设计模式
//恶汉式
classSingle{
    privatestatic final Single s=newSingle();
    privateSingle(){}
    publicstatic Single getInstance(){
         return s ;
    }
}
//懒汉式也被称为延迟加载
classSingle1{
    privatestatic Single1 s= null;
    privateSingle1(){}
    publicstatic Single1 getInstance(){
         if(s==null)
               //---->A----->B多线程访问
             s=new Single1();//延迟加载
         return s ;
    }
}
*****如果有多线程对共享数据S进行访问,就会出现安全隐患。
------------------------》加同步解决安全问题。----------------------------------

1)同步函数
//懒汉式
classSingle1{
    privatestatic Single1 s=null;
    privateSingle1(){}
    publicstatic synchronized Single1 getInstance(){//效率低-->同步代码块
         if(s==null)
             s=new Single1();
         return s ;
    }
}
如果是多线程,为了获得实例,进来的时候都要进行同步锁的判断,就比较低效。因此,懒汉式的方法比较低效。

2)同步代码块
//懒汉式
classSingle1{
    privatestatic Single1 s=null;
    privateSingle1(){}
    publicstatic synchronized Single1 getInstance(){//效率低-->同步代码块
         if(s==null){
             synchronized(Single1.class){//对象是:该类所属字节码文件的class
                 if(s==null)
                      s=new Single1();               
                 }
        }
         return s ;
    }
}
用双重判断解决低效,提高了懒汉式的效率
**************所以使用的时候,选择饿汉式比较方便,比好好。***************

面试的时候会问:懒汉式和饿汉式有什么不同?
不同在于:懒汉式实例的延迟加载;懒汉式的延迟加载有没有问题?
,如果多线程访问时,会出现安全问题。怎么解决?
可以加同步来解决。而使用同步函数和同步代码块都行,但是有些低效;用双重判断可以解决效率问题。加同步的时候使用的锁是哪一个?
该类所属的字节码文件对象:类名.class
一般会考:请给我写一个延迟加载的单例设计模式?

二、死锁
示例:
//死锁:同步中嵌套同步:
classTi implements Runnable{
    privateint tick =100;
    booleanflag=true;
    Objectobj=newObject();
    publicvoid run(){    
         if(flag){
             while(true){
                 synchronized(obj){
                      show();//嵌套同步方法、、this
                 }
             }
         }
         else{
             while(true){
                 show();
             }
         }
    }
    publicsynchronized void show(){//this
         synchronized(obj){
             if(tick>0){
                 try{Thread.sleep(10);}catch(Exception e){}
                 System.out.println(Thread.currentThread().getName()+":show:"+tick--);
             }
         }
    }
}
publicclass DeadLockDemo {
    publicstatic void main(String[] args) {
         Ti t=new Ti();
         Thread t1=new Thread(t);
         Thread t2=new Thread(t);
         t1.start();
         try{Thread.sleep(10);}catch(Exception e){}
         t.flag=false;         
         t2.start();
    }
}
面试的时候:给我 写一个死锁的程序
代码:
//
classTestJava implements Runnable{
    privateboolean flag ;
    TestJava(booleanflag){ //把标记传进来
         this.flag=flag;       
    }
    publicvoid run(){
         if(flag){
             synchronized(MyLock.locka){
                 System.out.println("if locka");
                 synchronized(MyLock.lockb){
                      System.out.println("if lockb");              
                 }
             }
         }else{
             synchronized(MyLock.lockb){
                 System.out.println("else lockb");
                 synchronized(MyLock.locka){
                      System.out.println("else locka");         
                 }
             }
         }        
    }
}
classMyLock{
    staticObject locka=newObject();//写静态只为了方便调用
    staticObject lockb=newObject();
}
publicclass DeadLockTest {

    publicstatic void main(String[] args) {
         Thread t1=new Thread(new TestJava(true));
         Threadt2=newThread( new TestJava(false));
         t1.start();
         t2.start();
    }

}
结果:
if locka
else lockb就锁住了 无法运行

三、线程间通讯
1、线程间通讯:其实就是多个线程在操作同一资源。但是操作的动作不同。
示例:
packageoo;
//进程间通讯
classRes{
    Stringname;
    Stringsex;
}
classInput implements Runnable{
    privateRes r;
    Input(Res r){
         this.r=r;
    }
    publicvoid run(){
         int i=0;
         while(true){
             if(i==0){//间隔存入数据
                 r.name="Mike";
                 r.sex="male";
             }else{
                 r.name="张三";
                 r.sex="男";
             }
             i=(i+1)%2;
         }
         
    }
}
classOutput implements Runnable{
    privateRes r;
    Output(Res r){
         this.r=r;
    }
    publicvoid run(){
         int i=0;
         while(true){
             System.out.println(r.name+"::"+r.sex);
         }
    }
}
publicclass OutputInputDemo {

    publicstatic void main(String[] args) {
         Res r=new Res();
         Input in=new Input(r);
         Output out=new Output(r);
         Thread t1=new Thread(in);
         Thread t2=new  Thread(out);
         t1.start();
         t2.start();
    }
}
发现:会打印出错误的信息:比如mike::男等错误那信息,怎么实现线程间信息的同步?
--->同步,加锁。
加了同步后,还不安全--想前提
1)同步里面有两个线程;
2)是同一个锁。
四个类是唯一的,可以使用类名.class;但是比较牵强;res对象也是唯一的。
示例:
packageoo;
//进程间通讯
classRes{
    Stringname;
    Stringsex;
}
classInput implements Runnable{
    privateRes r;
    Input(Res r){
         this.r=r;
    }
    publicvoid run(){
         int i=0;
         while(true){
             synchronized(r){//
                 if(i==0){//间隔存入数据
                      r.name="Mike";
                      r.sex="male";
                 }else{
                      r.name="张三";
                      r.sex="男";
                 }
                 i=(i+1)%2;
             }
         }
         
    }
}
classOutput implements Runnable{
    privateRes r;
    Output(Res r){
         this.r=r;
    }
    publicvoid run(){
         while(true){
             synchronized(r){
                 System.out.println(r.name+"::"+r.sex);
                 }
             }
    }
}
publicclass OutputInputDemo {

    publicstatic void main(String[] args) {
         Res r=new Res();
         Input in=new Input(r);
         Output out=new Output(r);
         Thread t1=new Thread(in);
         Thread t2=new  Thread(out);
         t1.start();
         t2.start();

    }

}

三、等待唤醒机制
如果input获得CPU执行权,不断往里面录入,前面的信息会被覆盖掉。而等到Output获得Cpu执行权,就会把一个相同的信息打印多次。
1、线程运行的时候,内存中会有一个线程池。等待线程都在线程池中,而notify()唤醒的都是线程池中的线程。如果有多个等待线程,通常唤醒第一个等待的线程。(即按顺序唤醒线程)
示例:
packageoo;
//进程间通讯
classRes{
    Stringname;
    Stringsex;
    booleanflag=false;
}
classInput implements Runnable{
    privateRes r;
    Input(Res r){
         this.r=r;
    }
    publicvoid run(){
         int i=0;
         while(true){
             synchronized(r){
                 if(r.flag)
                      try{r.wait();}catch(Exception e){}//只能try
                 if(i==0){//间隔存入数据
                      r.name="Mike";
                      r.sex="male";
                 }else{
                      r.name="张三";
                      r.sex="男";
                 }
                 i=(i+1)%2;
                 r.flag=true;
                 r.notify();
             }
         }
         
    }
}
classOutput implements Runnable{
    privateRes r;
    Output(Res r){
         this.r=r;
    }
    publicvoid run(){
         while(true){
             synchronized(r){
                 if(!r.flag)
                      try{r.wait();}catch(Exception e){}
                 System.out.println(r.name+"::"+r.sex);
                 r.flag=false;
                 r.notify();//notifyAll():唤醒全部的线程
                 }
             }
    }
}
publicclass OutputInputDemo {

    publicstatic void main(String[] args) {
         Res r=new Res();
         Input in=new Input(r);
         Output out=new Output(r);
         Thread t1=new Thread(in);
         Thread t2=new  Thread(out);
         t1.start();
         t2.start();

    }
}
//当前对象必须拥有此对象的监视器(锁),只有同步才有锁。--->因此wait(),notify();notifyAll(),去那用在同步中。
//因此同步会出现嵌套,必须标识wait()所属于的锁。
2、思考:
1)、wait()、notify(), notifyAll():
这些方法存在于同步中,因为要对持有监视器(锁)的线程进行操作。所以,要使用在同步中,因为只有同步才具有锁。

2)、wait(),sleep()有什么区别?
wait():释放资源,释放锁。
sleep():释放资源,不释放锁。
3)wait()、notify(), sleep() 用来操作线程为什么定义在Object中?
因为这些方法在操作同步中的线程时,都必须要标识它们所操作线程 只有的锁;只有同一个锁上被等待的线程,可以被同一锁上的notify()唤醒;不可以对不同锁中的线程进行唤醒。
也就是说:等待和唤醒必须是同一个锁。

而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object中。
3、代码优化:
示例:
packageoo;
//进程间通讯
classRes{
    Stringname;
    Stringsex;
    booleanflag=false; 
    publicsynchronized void set(String name,String sex){
         if(flag)
             try{this.wait();}catch(Exception e){}
         this.name=name;
         this.sex=sex;
         flag=true;
         this.notify();
    }
    publicsynchronized void out(){
         if(!flag)
             try{this.wait();}catch(Exception e){}
         System.out.println(name+"..."+sex);
         flag=false;
         this.notify();
    }
}
classInput implements Runnable{
    privateRes r;
    Input(Res r){
         this.r=r;
    }
    publicvoid run(){
         int i=0;
         while(true){
             if(i==0)
                 r.set("mike","male" );
             else
                 r.set("张三","男");
             i=(i+1)%2;                
         }        
    }
}
classOutput implements Runnable{
    privateRes r;
    Output(Res r){
         this.r=r;
    }
    publicvoid run(){
         while(true){
             r.out();
         }            
    }
}
publicclass OutputInputDemo {

    publicstatic void main(String[] args) {
         Res r=new Res();
         new Thread(new Input(r)).start();
         new Thread(new Output(r)).start();
    }
}
四、生成消费问题:
notify()唤醒线程池中的第一个,有可能是唤醒本方,可能导致数据错乱,是因为没有判断标记;加了while(flag),循环判断以后,有可能全部等待。---》使用notifyAll()唤醒全部,
因此:当出现了多个生产者、消费者的时候,要使用While()循环和notifyAll().
示例:
packageoo;
//生成消费问题:生成一个,消费一个
publicclass ProducerConsumerDemo {

    publicstatic void main(String[] args) {
         Resource r=new Resource();
         Producer pro=new Producer(r);
         Consumer con=new Consumer(r);
         Thread t1=new Thread(pro);
         Thread t2=new Thread(con);
         
         Thread t3=new Thread(pro);
         Thread t4=new Thread(con);
         
         t1.start();
         t2.start();
         t3.start();
         t4.start();
    }

}
classResource{
    privateString name;
    privateint count =1;
    privateboolean flag =false;
    
    publicsynchronized void set(String name){
         while(flag)
             //--t1 --t3
             try{wait();}catch(Exception e){}
         this.name=name+"--"+count++;
        System.out.println(Thread.currentThread().getName()+".生产者."+this.name);
         flag=true;
         this.notifyAll();
    }
    publicsynchronized void out(){
         while(!flag)
             //--t2 --t4
             try{wait();}catch(Exception e){}//this可以省略
        System.out.println(Thread.currentThread().getName()+".消费者...." +this.name);
         flag=false;
         this.notifyAll();     
    }
}
classProducer implements Runnable{
    privateResource res;
    Producer(Resource res){
         this.res=res;
    }
    publicvoid run(){
         while(true){
             res.set("商品+");
         }        
    }
}
classConsumer implements Runnable{
    privateResource res;
    Consumer(Resource res){
         this.res=res;
    }
    publicvoid run(){
         while(true){
             res.out();
         }        
    }
}
原理图:

分析:
我们使用notifyAll(),是想唤醒对方线程,但是把所有线程都唤醒了。如果之唤醒对方,不唤醒本方线程,应该怎么做呢?
五、生产消费JDK
java.util.concurrent.locks
1、Lock实现提供了比使用同步函数和同步代码块可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的condition对象。
----lock在替代同步函数。。的操作----
2、方法:
lock()获取锁;unlock()释放锁。
3、wait();notify()等方法都应该定义在同步块中,每一个wait(),notify(),,都要标示自己所使用的锁;
现在同步变成了lock,wait()/notify()变成 了condition
4、condition将Object监视器方法(wait,notify,notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多哥等待set(wait-set)。其中,Lock替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用。
注意:JDK1.5提供了多线程升级解决方案;将同步替换成显式的Lock操作,将Object中的wait(),notify(),notifyAll(),替换成了condition对象,该对象可以通过Lock锁进行获取。
该示例中,实现了只唤醒对方的操作。


0 0
原创粉丝点击