码迷,mamicode.com
首页 > 编程语言 > 详细

Java 传统线程技术

时间:2015-08-19 16:51:23      阅读:141      评论:0      收藏:0      [点我收藏+]

标签:多线程   线程同步   线程通信   线程封闭   

Java 多线程

在Java中,线程类Thread创建方式有两种:一是继承Thread类,重写run方法;二是,实现Runnable接口。大多数情况下,推荐使用第二种方式,实现runnable接口,这样可以很好的将任务与执行单元分离,更加突出面向对象的思想。

在JDK1.5之前,线程间互斥主依靠内置锁(监视器),而线程间通信则采用Object实例的wait,notify等方法。在JDK1.5之后,增加了很多线程新技术,如线程池、锁、信号量、条件、栅栏、阻塞队列、同步容器等。

1、       传统线程技术

技术分享

1.1、  线程互斥

在Java中,提供了内置锁(监视器)的概念。每一个对象可以看作是与一把锁关联,在进入临界区前需要获取与对象所关联的锁,在离开临界区释放对象关联的锁。

Java提供了synchronized关键字来方便的获取和释放对象所关联的锁。在Java中synchronized关键字可以修饰方法或代码块,被synchronized关键字修饰的方法或代码块会在进入作用域前获取相应的锁,在离开作用域后释放相应的锁,如下

public class Ticket

{

   private int tickets=10;

    public void sale()

    {

        synchronized (this)

      {

         if (tickets>0)

         {

            tickets--;

         }

      } 

    }

 }

通过上面的代码,我们知道使用synchronized代码块需要显示指定那个对象的关联锁。synchronized关键字既可以修饰实例方法,也可以修饰类方法,那么synchronized修饰实例方法获取的是那个对象的关联锁,而修饰类方法又获取的是那个对象的关联锁,如下

public class Factory

{

   private static int count=10;

    public synchronized void consume()

    {

       if (count>0)

      {

         count--;

      }

    }

   

    public synchronized static void show()

    {

       System.out.println("count="+count);

    }

   

}

synchronized修饰实例方法时,获取的是该实例所关联的锁(对象锁);而修饰类方法其实获得是该类对应的Class对象的锁(类锁)。由于对象锁和类锁并不是一把锁,所以默认采用synchronized来修饰实例方法和类方法,这些方法是达不到互斥作用的,上面的互斥代码是有问题的。若想实例方法和类方法达到互斥效果,需要采用synchronized代码块,显示指定获取那个对象的关联锁(如同一把类锁,或同一个对象的关联锁),如下

public class Factory

{

   private static int count=10;

   private static final Object lock=new Object();

    public void consume()

    {

       synchronized (lock)

      {

          if (count>0)

          {

             count--;

          } 

      }

    }

   

    public static void show()

    {

       synchronized (lock)

      {

          System.out.println("count="+count);

      }

    }

   

}

1.2、  线程通信

synchronized关键字可以使线程间互斥,而线程间的通信需要使用Object实例的wait、notify等方法,但前提是不同的线程必须获取同一个实例的监视器,也就是说waitnotify方法必须在互斥块内使用,而且不同的线程所要获取的监视器是同一个监视器

wait方法可以使一个线程等待,并释放该线程获得的监视器,直到被另一个持有同一个监视器的线程所唤醒;当条件满足时,需要在其他线程中使用notify或notifyAll方法来唤醒等待的线程,被唤醒的线程可以重新获取监视器对象。

在上面我们强调了不同线程间通信的前提是,这些线程所持有或申请持有的是同一个监视器。为了便于不同线程间可以获取同一个监视器,也为了任务和执行单元分离,在涉及多线程的编程时往往将要执行的一组任务封装到一个类中,如下(子线程先打印10句话,主线程接着打印100句;子线程再打印10句,主线程也接着打印100句;如此顺序子线程、主线程个执行50次):

public class Service

{

   private  boolean isSubRunable=true;

    public void sub() throwsInterruptedException

    {

       synchronized (this)

      {

          while (!isSubRunable)

         {

            this.wait();

         }

         

         for (int i = 0; i < 10; i++)

         {

            System.out.println("sub loopof "+i);

         }

         isSubRunable=false;

         this.notifyAll();

      }

    }

   

    public void main() throwsInterruptedException

    {

       synchronized (this)

      {

          while(isSubRunable)

          {

             this.wait();

          }

          for (int i = 0; i < 100; i++)

         {

            System.out.println("main loopof "+i);

         }

          isSubRunable=true;

         this.notifyAll();

      }

    }

   

    public static void main(String[] args) throws InterruptedException

   {

      final Service service=new Service();

      new Thread(new Runnable()

      {

        

         @Override

         public void run()

         {

            for (int i = 0; i <50; i++)

            {

               try

               {

                  service.sub();

               }

               catch (InterruptedException e)

               {

                 

               }

            }

         }

      }).start();

      

      for (int i = 0; i < 50; i++)

      {

         service.main();

      }

   }

}

 

注意:

l   wait方法,使线程等待时可以释放获得的监视器;

l   sleep方法,也可以使线程等待一段时间后再执行,但是在等待期间并不会释放锁获得的监视器;

l   join:一直等待该线程执行完毕才去执行其他线程,也即调用join方法的线程获得CPU控制权,直到该线程执行完毕;在实际应用中,可以使一个线程等待另一个线程执行完毕,才开始执行;

l   yield:该方法指明该线程主要的任务已执行完毕,可以让出当次CPU控制权(让出CPU控制权后立即变为就绪态,参与下次CPU控制权的竞争),使其他同优先级的线程获得执行机会;注意yield并不会释放锁。

1.3、  假唤醒

当某些条件不满足时,我们需要使线程等待,直到这些条件得到满足。在进行条件判断时,为什么要使用循环语句,而不是if语句呢?

由于一些中断或唤醒会提前发生,所以在判断条件满足时需要采用循环语句,以防止提前唤醒。

1.4、  终止线程

在大多数情况下,我们不用显示终止线程,而是等待线程执行完毕。但是在与用户交互的应用中或出现一些其他突发状况,需要终止线程的执行,如用户点击了取消按钮,JVM马上要关闭等。

出现上述情况,该如何安全的终止线程,是立即终止,还是缓慢的终止(等待已提交的任务执行完毕,而不接受新的任务)?

一个线程是一条独立的执行路径,要终止线程的执行,正确的做法是线程自己内部终止本线程的运行。

1.4.1、interrupt

interrupt方法并不是中断一个线程的执行,而是将线程的中断标志设置为true,也即isInterrupted=true;相当于只是打了一个标记,告诉线程要中断了。但是,具体的中断操作是需要线程内部来实现的。

注意:调用interrupt方法将中断标志设置为true后,若调用该线程的sleep、wait、join等方法,将会清除中断标志位,并抛出InterruptedException。由于interrupt方法意味着将要中断线程,而sleep、wait、join等方法与interrupt方法是冲突的,并不能一起执行,所以会将中断标志清除,并抛出InterruptedException异常。

采用中断标志为来终止线程的执行,如下

public static void main(String[] args)

    {

        Thread thread= new Thread(new Runnable()

      {

        

         @Override

         public void run()

         {

            Thread currentThread=Thread.currentThread();

             while (!currentThread.isInterrupted())

            {

                System.out.println(currentThread.getName()+" is running....");

                try

               {

                  Thread.sleep(1000);

               }

               catch (InterruptedException e)

               {

                  //需要再次调用interrupt

                  currentThread.interrupt();

                   e.printStackTrace();

               }

            }

         }

      });

      //开启线程

      thread.start();

      for (int i = 0; i < 20; i++)

      {

          System.out.println("main thread is run loop of "+i);

          try

         {

             if (i==10)

            {

               thread.interrupt();

            }

            Thread.sleep(1000);

         }

         catch (InterruptedException e)

         {

            e.printStackTrace();

         }

      }

}

1.4.2、标志位

在实际应用中,往往采用自定义标志位来终止线程的执行,如下

public class Test

{

     public static void main(String[] args)

    {

        Task task=new Task();

      //开启线程

      new Thread(task).start();

      for (int i = 0; i < 20; i++)

      {

          System.out.println("main thread is run loop of "+i);

          try

         {

             if (i==10)

            {

               task.setStoped(true);

            }

            Thread.sleep(1000);

         }

         catch (InterruptedException e)

         {

            e.printStackTrace();

         }

      }

    }

}

 

class Task implements Runnable

{

 

   privatevolatilebooleanisStoped=false;

   @Override

   public void run()

   {

      while (!isStoped)

      {

         System.out.println("task isrunning.......");

         try

         {

            Thread.sleep(1000);

         }

         catch (InterruptedException e)

         {

            e.printStackTrace();

         }

      }

   }

  

   public boolean isStoped()

   {

      return isStoped;

   }

   public void setStoped(boolean isStoped)

   {

      this.isStoped = isStoped;

   }

}

1.5、  线程封闭

若一些数据只在一个线程内共享,并不会被其他线程访问,可以采用线程封闭技术,将这些数据封闭在具体的执行线程中。在Java中,线程封闭技术采用ThreadLocal技术来实现,我们可以将只在一个线程内共享的数据放入ThreadLocal中,这样在获取数据时只会获取与本线程关联的数据,而不会获取其他线程的数据,这样可以避免线程同步的性能开销,如下

public class UserService

    private ThreadLocal<Connection> threadLocal=new ThreadLocal<UserService.Connection>(){

       protected Connection initialValue()

       {

          return new Connection();

       };

      };

   

   

    public Connection getCurrentConnection()

    {

       return threadLocal.get();

    }

   

    static class Connection

    {

      

    }

}

1.6、  守护线程

在Java中线程分为用户线程和守护线程。只要有用户线程正在运行,JVM就不会退出;而守护线程是依附用户线程存在的,如负责垃圾收集的线程就是典型的守护线程,但所有用户线程执行完毕,负责垃圾收集的线程也没有存在的必要。

在Java中,默认开启的线程都为用户线程,若要使某个线程称为守护线程,可以在线程启动前,调用setDaemon方法。


版权声明:本文为博主原创文章,未经博主允许不得转载。

Java 传统线程技术

标签:多线程   线程同步   线程通信   线程封闭   

原文地址:http://blog.csdn.net/sunshuolei/article/details/47781613

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!