黑马程序员_Java基础——多线程(第2篇)

来源:互联网 发布:宣传图片制作软件 编辑:程序博客网 时间:2024/05/16 17:06

---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------

一、引言

    初学者刚接触多线程时往往觉得多线程技术多么的高深难懂,就如古代两军对垒不战而退,还没开始学呢,首先心理心理上就处于了弱势。其实完全没必要背着这种心理负担去学习。就如同忽悠人一样,许多高科技的东西就喜欢去起专业的名词唬人,接触之后才知道完全不是那么回事。多线程技术其实也是一回事,了解了也就觉得就那么回事儿。那么什么是多线程呢?了解多线程首先要了解什么是进程!关于进程,简单来说就是正在运行的程序。而一个线程就是进程中的一个独立的控制单元,其控制着进程的的执行,因而一个进程中至少有一个线程。而所谓多线程自然就是多个线程在一个进程中同时执行了,很明显这样做可以提高生产效率。

二、多线程实现技术分析

    那么在Java中如何让多个线程同时运行呢?Java提供了两种创建多线程的解决方案。其一是继承Thread类,其二是实现Runnable接口。下面我们具体的来看看这两种方式的具体实现以及区别。

--继承Thread类

步骤:1.继承Thread类;2.覆写Thread类中的run()方法;3.调用线程的start()方法执行run()方法

小例子:有自定义线程与主线程两个线程,观看执行结果

class DuoXianCheng extends Thread{public void run(){for(int i = 0; i < 60; i++)System.out.println("run..." + i );}}class ThreadDemo{public static void main(String[] args){DuoXianCheng d = new DuoXianCheng();d.start();for(int i = 0; i < 60; i++){System.out.println("hello word" + i);}}}
我们从执行结果中不难发现自定义线程与主线程在交替执行,而且每一次的执行结果都是不一样的。这是因为多个线程都在获取cpu的执行权,cpu执行到谁,谁就运行。明确一点,在某一个时刻只能有一个线程在运行(多核除外),cpu在做着快速的切换,以达到看上去同时执行的效果。我们可以形象的把多线程的运行行为看成是在互相的抢夺cpu的执行权。这也就是多线程的特点之一,随机性,谁抢到谁执行,至于执行的多长的时间,则由cpu决定。

    那么为什么要覆盖run()方法呢?我们可以认为Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就在run方法。简单来说就是,线程要运行的代码就放在run方法中,这就好比主线程的代码放在main方法中。

--实现Runable接口

步骤:1.定义类实现Runable接口;2.覆盖Runable接口中的run方法,将线程要运行的代码放在存放在改run方法中;3.通过Thread类建立线程对象;4.将Runnable接口的子类对象作为参数传递给Thrad类的构造函数;5.调用Thread类对象的start()方法开启线程并调用Runnable接口中的run()方法

   这里是不是有个疑问?为什么要把Runnable接口的子类对象作为参数传递给Thread类的构造函数?因为自定义的run()方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run()方法,就必须要明确你所要执行的代码所在的run()方法的所属对象。

    那么这两种实现方式有什么区别呢?

--避免的了单继承的局限性,因为java中只能单继承,不允许多继承,所以一个对象要是有父类,那么就不能通过这种继承的方式来实现。所以java提供了另一种方式来实现,虽然不允许多继承,但是可以实现接口,在平时的编程中建议使用实现接口的方式

--线程代码存放的位置不同

三、多线程的安全问题

当多个线程在操作同一个共享数据时,一个线程对多条语句子执行了一部分,还没有执行完,这时另一个线程参与进来,导致共享数据错乱。这就导致了多线程的安全问题。

如下例子,可能会出现打印0号票的情况,这就是典型的多线程安全问题。

/*  
需求:简单的买票程序  
多个窗口同事买票  
*/

class Ticket implements Runnable  {      private int ticket = 100;      public void run()      {          while(true)          {                            if(ticket > 0)              {                  //如果线程0,1,2,3都在这儿趴下了,那么就会出现线程的安全问题              //可以用try{Thread.sleep(10);}catch(Exception e){}来模拟  //sleep()抛出InterruptedException异常              //这里为什么不能抛出这个异常呢?因为run方法时从Runnable接口中覆写来的,而Runnable中的run方法              //没有抛出异常,所以这里绝对不能抛,只能处理                  System.out.println(Thread.currentThread().getName() + "..." + ticket--);              }          }        }  }    class ThreadTicketDemo  {      public static void main(String[] args)      {          Ticket t = new Ticket();          Thread t1 = new Thread(t);          Thread t2 = new Thread(t);          Thread t3 = new Thread(t);          Thread t4 = new Thread(t);          t1.start();          t2.start();          t3.start();          t4.start();      }  }  
那么如何解决这样的多线程的安全问题呢。Java中对于多线程的安全问题提供了专业的解决方案,那就是synchronized关键字——同步。同步有两种形式,一是同步代码块,而是同步函数。

synchronized(对象)  {      //需要被同步的代码  }
这里的对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获得了cpu的执行权也没有办法进去执行代码,因为没有获取锁。
//下面存钱的例子,同步函数解决多线程的安全问题

/* 
需求:银行有个金库 
有两个用户分别往里面存300块,每次存100,分三次

*/

class Bank  {      private int sum;      Object obj = new Object();      public synchronized void add(int n)      {          //synchronized(obj)          //{              sum = sum + n;              try{Thread.sleep(20);} catch(Exception e){}              //如果线程1和线程0都在这边趴了那么就出问题了              //同步一下就可以了              //也可以在函数上加同步,变成同步函数              System.out.println("sum = " + sum);          //}      }    }    class Cus implements Runnable  {      private Bank b = new Bank();      public void run()      {          for(int i = 0; i < 3; i++)          {              b.add(100);          }      }  }    class BankDemo  {      public static void main(String[] args)      {          Cus cus = new Cus();          Thread t1 = new Thread(cus);          Thread t2 = new Thread(cus);          t1.start();          t2.start();      }  }  

但是同步也是要有条件的,对于同步的前提:1.必须有两个或两个以上的线程;2.必须多个线程使用同一把锁,必须保证同步中只能有一个线程在执行。

至此,同步的好处和弊端也显而易见了,好处便是解决了多线程的安全问题,弊端嘛,自然是需要判断锁,需要消耗资源。

注:同步函数所使用的锁是this,而静态同步函数呢?因为静态同步函数没有本类对象,所以其所使用的锁是该类的字节码文件对象 类名.class

四、单例设计模式(懒汉式-延迟加载)的多线程实现

class Single  {      private static Single s = null;      private Single()      {      }      public static Single getInstance()//这里如果用同步函数的话效率会很低,      {//素以一般使用同步代码块儿          if(s == null)//提高效率,使用双层判断          {              synchronized(Single.class)//因为是静态的没有this,锁可以使用Single.class              {                  if(s == null)                      //A-->趴下,B-->趴下                      s = new Single();                  return s;              }          }      }  }  

五、死锁

    死锁简单来说就是锁相互之间相互等待对方的情况,就用个小例子来说明了!

class Test implements Runnable  {      private boolean flag;      Test(boolean flag)      {          this.flag = flag;      }      public void run()      {          if(flag)          {              synchronized(DeadLock.obj1)              {                  System.out.println("if obj1");                  synchronized(DeadLock.obj2)//互相等待对方                  {                      System.out.println("if obj2");                  }              }          }          else          {              synchronized(DeadLock.obj2)              {                  System.out.println("else obj2");                  synchronized(DeadLock.obj1)//互相等待对方                {                      System.out.println("else obj1");                  }              }          }      }  }    class DeadLock  {      static Object obj1 = new Object();      static Object obj2 = new Object();  }    class DeadLockTest  {      public static void main(String[] args)      {          Thread t1 = new Thread(new Test(true));          Thread t2 = new Thread(new Test(false));          t1.start();          t2.start();      }  }  


---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
0 0