黑马程序员---------笔记整理(java基础八-----多线程)

来源:互联网 发布:深圳红酒网络销售 编辑:程序博客网 时间:2024/05/16 11:40

---------------------- android培训、java培训、期待与您交流! ----------------------  

    今天有点晕晕的,说不清的感觉,总觉得时间有点紧,不大够用。不知道究竟是怎么了。慢慢来吧,饭总得一口口吃。尽力!!!来吧,让暴风雨来得更猛烈些吧。
1.1 线程的优势 
1.2线程的弊端 
1.3 线程和进程的区别 
1.4多线程的目的 
1.5计算机执行任务的原理 
1.6 创建线程的两种方式 
1.7 线程的安全问题产生的原因 
1.9 线程状态图解 
2.0 锁 
2.1 死锁 
2.2 延迟加载同步代码块儿 
2.3 线程间通讯 
2.4 同步 
2.5 其他 

多线程
      进程:正在运行中的程序。      

      线程:就是进程中一个负责程序执行的控制单元(执行路径)

1.1 线程的优势 
      多线程好处:解决了多部分同时运行的问题 
1.2线程的弊端 
     多线程的弊端:线程太多回到效率的降低,因为线程的执行依靠的是CPU的来回切换。 1.3 线程和进程的区别 

    一个进程中可以多执行路径,称为多线程。
    一个进程中至少要有一个线程。
了解:
   进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。如果有兴趣深入的话,我建议你们看看《现代操作系统》或者《操作系统的设计与实现》。对就个问题说得比较清楚。

1.4多线程的目的 
      开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的   内容,这个内容称为线程要执行的任务
1.5计算机执行任务的原理 
     应用程序的执行都是cpu在做着快速的切换完成的,而且切换是随机的 1.6 创建线程的两种方式 
      1.继承Thread。
    继承Thread类;
    覆盖Thread类中的run方法;
    直接创建Threadde子类对象创建线程;
    调用start方法开启线程并调用线程的任务run方法。
    另外可以通过Thread的getName()获取线程的名称。
   主要代码:
        class ThreadDemo extends Thread  
          {
              public void run()
              {     
               for (int i=0;i<10;i++)
               {   
                      System.out.println("线程"+getName()+"正在运行:"+i);      
               }   
              }
          }
        class  Demo
        {
              public static void  main(String args[])
              {
                ThreadDemo a=new ThreadDemo();
                ThreadDemo b=new ThreadDemo();
                a.start();
                b.start();
              }
       }

     2.实现runnable接口
     定义类实现Runnable接口;
     覆盖接口的run的方法,将线程的任务代码封装到run方法中;
     通过Thread类创建线程对象,将Runnabl接口的子类对象作为Thread类的构造函数的参数进行传递
                       (原因:线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务)。
     调用线程对象的start方法开启线程。

      主要代码:
      class RunnableDemo implements Runnable   //实现Runnable
       {
              public void run()
              {
                show();
              }
              private void show()
              {
               for (int i=1;i<10 ;i++ )
               {
                      System.out.println(Thread.currentThread().getName()+"  "+i);
               }
              }
       }
       class Demo
       {
              public static void main(String[] args)
              {
                     RunnableDemo a=new RunnableDemo();
                     Thread b=new Thread(a);     //将任务封装成对象,将其传入线程中
                     Thread b1=new Thread(a);
                     b.start();
                     b1.start();
                     
              }
   }

      总结:两种方法的比较:实现Runnable接口,将线程的任务从线程的子类中分离出来的,进行了单独的封装,按照面向对象的思想将任务的封装成对象,避免了java中单继承的局限性。(创建线程第二种方式较好)

 1.7 线程的安全问题产生的原因 

       当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。     

    解决思路:就是将多条操作共享的线程代码封装起来,当有线程在执行这些代码的时候,其他线程就不可以参与运算,执行完以后, 

                        其他的线程才 可以执行。

      java中的体现:
           (同步代码块,解决线程的安全问题,但是降低了效率。因为同步外的线程 都会判断同步锁,同步的前提:同步中必须有有多
              个线程并使用用一个锁。)
       synchronized(对象)
       {
            需要被同步的代码;
       }

        主要代码:
       class Test implements Runnable
       {
              private int num=50;
              public void run()
              {
            while(num>0)//线程每循环一次,释放cpu的执行权,进来再次判断。
              {
               synchronized(this)//一次只能有一个线程进来售票
               {
                if(num>0)

                       System.out.println(Thread.currentThread().getName()+"正在出售:"+num--);
                  
               }
              }
              }
       }
       class TestDemo
       {
              public static void main(String[] args)
              {
                     Test a=new Test();
                     Thread t1=new Thread(a);
                     Thread t2=new Thread(a);
                     t1.start();
                     t2.start();
              }
       }

2.4 同步 

 同步的好处:解决了线程的安全问题    

同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁

 同步的前提:同步中必须有多个线程并使用同一个锁。

同步的表现:多个线程运行到同步时,谁具备执行权,其他线程就不参与共享数据的操作。
同步代码块儿和同步函数区别:
              同步函数的锁是固定的this
        同步代码块的锁是任意的对象。
        建议使用同步代码块。
      静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前 类名.class表示。
1.9 线程状态图解 
多线程状态图.JPG 
分析:
状态一,创建,使用start()开启线程。
状态二,运行(具备着执行资格,具备着执行权)
状态三,冻结(释放执行权,同时释放执行资格)
从运行到冻结的方式:
sleep(time),sleep(time)时间到,进入临时阻塞状态(具备着执行资格,但是不具备执行权,正在等待执行权)
wait()线程等待,notify()线程唤醒,进入临时阻塞状态。
状态四,消亡
从运行到消亡的方式:
stop()中止线程;
run()方法结束,线程的任务结束。
2.0 锁 
    锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制

2.1 死锁 

      同步代码块之间的嵌套。就是有两把锁,你那我需要的那把,我持有你需要的那把,而我们谁也不释放该锁,这样 

   我们都不能进去,在那等待。

主要代码:
    class  DeadLock implements Runnable
       {
              public boolean flag=false;
              public void run()
              {
                     if (!flag)
                     {   while(true)
                            {
                            synchronized(this)
                     {
                                    synchronized(DeadLock.class)
                                   {
                                    System.out.println("true");
                                   }
                     }
                            }
                     }
                     else
                     {
                      while(true)
                     {
                       synchronized(DeadLock.class)
                     {
                                    synchronized(this)
                                   {
                                    System.out.println("false");
                                   }
                     }
                     }
                     }
               
              }
       }
       class  Test
       {
              public static void main(String[] args)
              {
                     DeadLock a=new DeadLock ();
                     Thread t1=new Thread(a);
                     Thread t2=new Thread(a);
                     t1.start();
                     try{ Thread.sleep(10);}catch(Exception e){}
              a.flag=true;
                     t2.start();      
              }
       }

2.2 延迟加载同步代码块儿 
/*单例设计模式:
//饿汉式。
/*
class Single
{
private static final Single s = new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}


//懒汉式,请给我写个延迟加载的单例设计模式。

/*
区别:
懒汉式特点是实例的延迟加载。多线程访问时回出现安全问题。
可以加同步来解决。可以用同步代码块或者同步函数。有些低效,
用双重判断可以效率问题。同步的时候,用的锁是,该类所属的字节码文件对象。
*/
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if (s==null)
{
s = new Single();
}
}
}
return s;
}
}
class SingleDmeo
{
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}

2.3 线程间通讯 
    多线程在处理同一资源,但是任务却不同。
       1.1 等待唤醒机制: 
           涉及的方法:
                 1,  wait(); 是为了让线程处于冻结状态,释放CPU执行权和执行资格,被wait的线程会被存储到线程池中。
                 2,  notify(); 是用于唤醒线程池中的一个线程(任意的线程)。
                 3,  notifyAll(); 是用于唤醒线程池中的所有线程。
         这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。

           为什么需要唤醒机制:
                  有时候我们多个线程在操作同一数据时,需要有一个先后顺序,当其中一个线程在操作该数据时,其他线程不能操作该数据,
                  这时候可能就会涉及到冻结和唤醒机制。
       1.2 为什么操作线程的方法wait notify notifyAll 定义在Object类中? 
             因为这些方法是监视器的方法,监视器其实就是锁,锁可以是任意的对象,任意的对象调用的方法一定定义在Object类当中。 
       1 .3 线程间通讯最重要的机制: 
            等待唤醒机制。
                   wait():让当前线程处于冻结状态,当前线程就被存储到了线程池中。
                  notify():唤醒线程池中的任意一个线程。让该线程恢复到运行状态,会具备cpu的执行资格。
                  notifyAll(): 唤醒线程池中的所有等待的线程,具备cpu的执行资格。

       1.4 等待唤醒机制中,最常见的体现就是生产者消费者问题: 
            copiedlocalimagefile1.png 
发现两个问题:
1,出现了错误的数据。是因为多生产和多消费的时候,被唤醒的线程没有再次判断标记就执行了。
解决是将if判断变成while判断。
2,发现有了while判断后,死锁了。
因为本方线程唤醒的有可能还是本方线程。所以导致了死锁。
解决:本方必须唤醒对方才有效。notify只能唤醒一个,还不确定。所以干脆唤醒全部,肯定包含对方,
至于被唤醒的本方,会判断标记是否继续等待。


示例代码:
class ThreadDemo4
{
         public static void main(String [] args)
         {
                   Resource r = new Resource();
                   Shengchan s = new Shengchan(r);
                   Xiaofei x = new Xiaofei(r);
                   Thread t1 = new Thread(s);
                   Thread t2 = new Thread(s);
                   Thread t3 = new Thread(x);
                   Thread t4 = new Thread(x);
                   t1.start();
                   t2.start();
                   t3.start();
                   t4.start();
         }
}
class Resource
{
         private String name;
         private int count = 1;
         private boolean flag = false;
         public  synchronized void set(String name)
         {
                   if(flag)
                            try{this.wait();}catch(InterruptedException e){}
                   this.name = name+count;
                   count++;
                   System.out.println(Thread.currentThread().getName()+"...生产者...."+this.name);
                   flag = true;
                   notify();
         }
         public synchronized void out()
         {
                   if(!flag)
                            try{this.wait();}catch(InterruptedException e){}
                   System.out.println(Thread.currentThread().getName()+".........消费者........."+this.name);
                   flag = false;
                   notify();
         }
}
class Shengchan implements Runnable
{
         Resource r;
         Shengchan(Resource r)
         {
                   this.r = r;
         }
         public void run()
         {
                   while(true)
                            r.set("烤鸭");
         }
}
class Xiaofei implements Runnable
{
         Resource r;
         Xiaofei(Resource r)
         {
                   this.r = r;
         }
         public void run()
         {
                   while(true)
                            r.out();
         }
}
编译后:

copiedlocalimagefile0.png 
造成了线程的不安全的问题。
notify唤醒的是不确定的一其中的一个线程,造成了生产出的烤鸭没有被消费。


解决多生产多消费的线程安全问题:
示例代码:
class ThreadDemo4
{
         public static void main(String [] args)
         {
                   Resource r = new Resource();
                   Shengchan s = new Shengchan(r);
                   Xiaofei x = new Xiaofei(r);
                   Thread t1 = new Thread(s);
                   Thread t2 = new Thread(s);
                   Thread t3 = new Thread(x);
                   Thread t4 = new Thread(x);
                   t1.start();
                   t2.start();
                   t3.start();
                   t4.start();
         }
}
class Resource
{
         private String name;
         private int count = 1;
         private boolean flag = false;
         public  synchronized void set(String name)
         {
                   while(flag) //这里写while循环是为了当线程每次醒后再判断一次标记。
                            try{this.wait();}catch(InterruptedException e){}
                   this.name = name+count;
                   count++;
                   System.out.println(Thread.currentThread().getName()+"...生产者...."+this.name);
                   flag = true;
                   notifyAll();
         }
         public synchronized void out()
         {
                   while(!flag)
                            try{this.wait();}catch(InterruptedException e){}
                   System.out.println(Thread.currentThread().getName()+".........消费者........."+this.name);
                   flag = false;
                   notifyAll();
         }
}
class Shengchan implements Runnable
{
         Resource r;
         Shengchan(Resource r)
         {
                   this.r = r;
         }
         public void run()
         {
                   while(true)
                            r.set("烤鸭");
         }
}
class Xiaofei implements Runnable
{
         Resource r;
         Xiaofei(Resource r)
         {
                   this.r = r;
         }
         public void run()
         {
                   while(true)
                            r.out();
         }
}

       1.5 同步代码块儿 
            同步代码块,对于锁的操作是隐式的
           JDK1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了对象中,将隐式动作编程了显示动作。
           copiedlocalimagefile0.jpg 
      1.6 Lock接口 
            出现替代了同步代码块或者同步函数。将同步的隐式锁操作编程了显示锁操作。 同事更为灵活。可以一个锁上加上多组监视器。
            lock(); 获取锁。
           unlock();释放锁,通常需要定义finally代码块中。
           JDK1.5版本后,对多线程中的内部细节进行了升级改良。
      在java.util.concurrent.locks包中提供了一个Lock接口。
           Lock接口中提供了 lock()获取锁  unlock释放锁的操作。
           Lock接口更符合面向对象的思想,将锁这种事物封装成了对象。
          public void run()
         {
              synchronized(obj)
             {//获取锁。
                   code...
                //释放锁。
            }
        }
    但是对于释放和获取锁的操作,都是隐式的。
      JDK1.5后,就有了新的方法,将锁封装成了对象。因为释放锁和获取锁动作,锁自己最清楚。
    锁对象的类型就是Lock接口。
    并提供了,显示的对锁的获取和释放的操作方法。
       Lock lock;
       public void run()
      {
         try
         {
         lock.lock();//获取锁。
         
                   code...throw ...
         }
         finally
         {
                   lock.unlock();//释放锁.
         }
      }

     Lock接口替代了synchronized .

     Condition替代了Object类中监视器方法 wait notify  notifyAll。、
   将监视器方法单独封装成了Condition对象。而且一个锁上可以组合多组监视器对象。
   实现了多生产者多消费者时,本方只唤醒对方中一个的操作,提高效率。


      1.7Condition接口 
           出现替代了Object中的wait notify notifyAll方法。
                               await()signal();  signalAll();
                              将这些监视器方法单独进行封装,变成了Condition监视器对象。
                              可以任意锁进行组合。
          await();  睡眠
          signal();signalAll();  唤醒
      使用为一般是生产者是被消费者唤醒,消费者是被生产者唤醒。

      1.8 停止线程: 
            1,stop方法
            2,run方法结束。
                 stop方法已经过时。 所以只剩下一种办法:线程执行的代码结束,线程会自动终止。
         1run方法中通常都有循环语句,所以只要让循环结束即可,所以只要控制住循环的条件,最简单的方式就是定义标记。
     怎么控制线程的任务结束呢?
               任务中都会有循环结构,只要控制住循环就可以结束任务。
                控制循环通常就用定义标记来完成。
         但是如果线程处于了冻结状态,无法读取标记,如何结束呢?
         可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。但是强制动作会发
                terruptedException,记得要处理。

        示例代码:        
class StopThread implements Runnable       
 {                 
private boolean flag = true;                 
public synchronized void run(){                          
 while(flag){                                   
 try {                                            
 wait();//t0 t1                                    

}catch (InterruptedException e){                                             

System.out.println(Thread.currentThread().getName()+"....."+e);                                            

flag = false;                                   

 } 

 System.out.println(Thread.currentThread().getName()+"......++++");                           

}                 

}                 

public void setFlag(){                           

flag = false;                 

}        

}                                   
class StopThreadDemo        
{                 
public static void main(String[] args) {                           
StopThread st = new StopThread();                                   
 Thread t1 = new Thread(st);                           
Thread t2 = new Thread(st);                                   
t1.start();                           
t2.setDaemon(true);                           
t2.start();                                            
 int num = 1;                          
 for(;;){                                   
 if(++num==50){       
 //st.setFlag();                                             
t1.interrupt();        
//t2.interrupt();                                             
break;                                    

}                                    

System.out.println("main...."+num);                           

}                                    
System.out.println("over");                 
}        

}         

   临时加入一个线程运算时可以使用join方法。

            
       面试题 
        class Test implements Runnable
        {
                 public void run(Thread t)
                 {}
        }
        //如果错误 错误发生在哪一行?错误在第一行,应该被abstract修饰
         

         

        class ThreadTest
        {
                 public static void main(String[] args)
                 {
                           new Thread(new Runnable(){
                                    public void run(){
                                             System.out.println("runnable run");
                                    }}){
                                    public void run(){

                                             System.out.println("subThread run");
                                    }

                           }.start();

        打印的是subThread run 运行以子类的为主,如果子类里面没有这个run()方法的话,就以任务线程的run为主,如果都没有的话,以父类自己的为主。

       总结: 

             waitsleep的区别:

wait可以指定时间也可以不指定.

sleep必须指定时间。       

在同步中时,对于CPU的执行权和锁的处理不同。        

wait:释放执行权,释放锁。        

sleep:释放执行权,不释放锁。        

wait方法,必须定义在同步中,sleep不一定。


-

--------------------- android培训、java培训、期待与您交流! ----------------------  

原创粉丝点击