Java进击(四)线程

一、线程、进程、多线程 概念   


        首先明白线程是什么?线程是一个程序内部的顺序控制流。线程经常和进程搞混,进程是一次程序的执行;线程可以看成是轻量级的进程。多进程:在操作系统中可能同时运行多个任务;多线程:在同一应用程序中有多个顺序流同时执行。


二、线程创建和启动


          Java的线程是通过java.lang.Thread类实现的。每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()成为线程体。通过调用Thread类的start()方法来启动一个线程。

          有两种方式创建新的线程:


           第一种:实现接口


                 1)定义线程类实现Runnable接口

                 2Thread myThread = new Threadtarget

                  3runnable中只有一个方法:publicvoid run()定义线程运行体

                  4)使用Runnable接口可以为多个线程提供共享的数据

                  5)在实现Runnable接口的类的run方法定义中可以使用Thread的静态方法:public static Thread currentThread()获取当前线程的引用

                   例子:

			public class RunnableDemo{
			public static void main(String[] args){
			//只创建一个实现Runnable接口类的对象
			   TestThread threadobj = new TestThread();
			   System.out.println("Starting threads");
			   //只用一个Runnable类型对象为参数创建三个新线程
			   new Thread(threadobj,"Thread1").start();
			   new Thread(threadobj,"Thread2").start();
			   new Thread(threadobj,"Thread3").start();
			   System.out.println("Threads started,main ends\n");
			}
			}
			
			class TestThread implements Runnable{
			   private int sleepTime;
			   //构造函数
			   public TestThread(){
			   //获取随机休息时间
			      sleepTime=(int)(Math.random()*6000);
			   }
			   public void run(){
			      try{
				     System.out.println(Thread.currentThread().getName()+"going to sleep for"+sleepTime);
					 Thread.sleep(sleepTime);
				  }catch(InterruptedException exception){
				  
				  }
				  System.out.println(Thread.currentThread().getName()+"finished");
			   }
			}

运行结果:

                     

  第二种:继承


                    1)可以定义一个Thread的子类并重写run方法:

                                      class MyThread extends Thread

                                                    public void run(){

                                        

                     2)然后生成该类的对象:

                                        MyThread myThread=newsMyThread

            例子:

			public class ExtendsDemo{
			   public static void main(String[] args){
			   //创建并命名每个线程
			                      TestThread thread1=new TestThread("thread1");
					TestThread thread2=new TestThread("thread2");
					TestThread thread3=new TestThread("thread3");
					
					System.out.println("Starting threads");
					//启动线程
					thread1.start();
					thread2.start();
					thread3.start();
					
					System.out.println("Threads started,main ends\n");	
			  }
			}
			class TestThread extends Thread{
			   private int sleepTime;
			   //构造函数
			   public TestThread(String name)
			   {
			      super(name);
				  sleepTime=(int)(Math.random()*6000);
			   }
			   public void run(){
			      try{
				     System.out.println(getName()+"going to sleep for"+sleepTime);
					 Thread.sleep(sleepTime);
				  }catch(InterruptedException exception){
				    
				  }
				   System.out.println(getName()+"finished");
			   }
			
			}
运行结果:

                                          

        通过以上两种方法都可以创建线程,那么这两种方法有什么特点,我们分析一下:通过以上两个例子,通过运行结果我们看到实现Runnable接口方法创建的线程可以共享一个资源sleepTime,而继承Thread方法创建的线程没有共享一个资源,虽然执行的是相同的代码,但彼此相互独立,且各自拥有自己的资源,互不干扰。(需要注意一个子类只能继承一个父类,但是可以实现多个接口)

   

    优缺点:

     采用继承Thread类方式:
         (1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
         (2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
    采用实现Runnable接口方式:
         (1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标(target)对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
         (2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。 

            

         在实际应用中根据情况选择合适的方法。

 

三、生产者--消费者

           现实中我们经常会遇到很多有趣的情况:同时运行的几个线程需要共享一些数据,并且要考虑到彼此的状态和动作。比如在一个线程对共享数据进行操作时,在没完成相关操作之前,不允许其他线程打断,否则会破坏数据的完整性。也就是说,被多个线程共享的数据在同一时刻只运行一个线程处于操作之中,这就是同步控制中的一个问题——线程间的互斥。

            我们模拟存票售票的例子感受一下:

		public class SaveSellTickets{
		  public static void main(String[] args){
		     Tickets t =new Tickets(10);
			 new Producer(t).start();
			 new Consumer(t).start();
		  }
		}
		
		class Tickets{
		  int number=0;
		  int size;
		  boolean available =false;
		  public Tickets(int size){
		    this.size=size;
		  }
		}
		
		class Producer extends Thread{
		  Tickets t = null;
		  public Producer(Tickets t){
		    this.t = t;
		  }
		  public void run(){
		    while(t.number<t.size){
			   System.out.println("Producer puts ticket"+(++t.number));
			   t.available=true;
			}
		  }
		}
		
		class Consumer extends Thread{
		Tickets t =null;
		 int i=0;
		 public Consumer(Tickets t){
		    this.t = t;
		 }
		 public void run(){
		  while(i<t.size){
		     if(t.available ==true && i <= t.number){
			    System.out.println("Consumer buys ticket"+(++i));
				if(i==t.number){
				try{
				  Thread.sleep(5000);}catch(InterruptedException exception){};
				  t.available =false;
				}
			 }
		  }
		 }
		}

运行结果:

                                       

              现在是卖10张票,我们多卖些看看会出现什么情况,多运行几次会发现有时候程序就陷入死机了,为什么?仔细想想,如果程序运行到t.available=false之前,cpu切换到执行存票程序,存票将available置为true,但再次切换到售票之后,售票线程执行t.available=false,则由于售票序号大于总票数,且存票结束不再重置为true,那么程序陷入死循环。那么如何避免这种意外呢?

                      java中,利用“锁旗标”可以实现线程间的互斥操作。看看如何修改代码:


最终优化,将两个需要互斥的方法放在Tickets类中实现:

		public class NewTickets{
		  public static void main (String[] args){
		      Tickets t = new Tickets(10);
			  new Producer(t).start();
			  new Consumer(t).start();
		 }
		}
		class Tickets{
		 int number=0;
		  int size;
		  int i = 0;
		  boolean available =false;
		  public Tickets(int size){
		    this.size=size;
		  }
		  public synchronized void put(){
		   System.out.println("Producer puts ticket"+(++number));
		   available=true;
		  }
		  public synchronized void sell(){
		    if(available == true && i <=number)	
			System.out.println("Consumer buys ticket"+(++i));
			if(i ==number) available=false;
		  }
		}
		class Producer extends Thread{
		Tickets t=null;
		public Producer(Tickets t){
		  this.t=t;
		}
		public void run(){
		while(t.number<t.size)
		t.put();
		}
		}
		
		class Consumer extends Thread{
		  Tickets t=null;
		  public Consumer(Tickets t){
		     this.t=t;
		  }
		  public void run(){
		    while(t.i<t.size)
			t.sell();
		  }
		}

四、线程间通信

            当线程在继续执行前需要等待一个条件时,仅有 synchronized 关键字是不够的。虽然 synchronized关键字阻止并发更新一个对象,但它没有实现线程间发信。Object 类为此提供了三个函数:wait()、notify() 和 notifyAll()

           还以售票为例子,要求存入一张票就售出一张票,售出后再存入,看代码:

		class Tickets{
		 int number=0;
		  int size;
		  int i = 0;
		  boolean available =false;
		  public Tickets(int size){
		    this.size=size;
		  }
		  public synchronized void put(){
		  if(available)//如果还有票剩余则存票等带
		  try{wait();}catch(Exception e){}
		   System.out.println("Producer puts ticket"+(++number));
		   available=true;
		   notify();//存入后唤醒售票
		  }
		  public synchronized void sell(){
		    if(! available)//如果没有存票则售票等待
			try{wait();}catch(Exception e){}
			System.out.println("Consumer buys ticket"+(number));
			available=false;
			notify();//售票后唤醒存票
			if(number == size) number=size+1;	  	
		  }
		}

只需修改Tickets类的putsell方法,结果如下:

                                                           

五、总结

                 线程还有很多需要学习的东西,有效利用多线程的关键是理解程序是并发执行而不是串行执行的。比如程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。通过对多线程的使用,可以编写出非常高效的程序。不过值得注意的是,如果创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。在真正应用中,线程还有很多需要注意和思考的地方。



        不足之处,敬请指正~~~


Java进击(四)线程,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。