Java多线程的基本回顾

     接下来的系列,我将以JAVA线程及JDK Concurrent(jdk5引入)展开学习的笔记,坑爹的恒创服务器搞的我几周不能编写笔记,下年到时果断置换(说到底还是穷)。在开始之前我建议可以看看<<Java并发编程实战>>这本宝典,也可以多去Java并发编程网逛逛.

    本章主要简述的是多线程的一些使用方法,及多线程基础回顾。

多线程的基本回顾

  什么是线程和进程:

      进程:是一个正在执行的程序

      线程:是一个进程中的独立控制执行单元,线程控制进程的进行 一个进程至少一个线程

  线程的创建方式:

    1.如何自定义一个线程?

        *继承thread类,

        *重写run方法,

        *创建个对象,对象名.start();               

class Test extends Thread
{
    
     Test(String name)
     {
      
          super(name);
     }
     public void run()
     {
          for(int x=0; x<60; x++)
          {
               System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);
          }
     }
}
class ThreadTest
{
     public static void main(String[] args)
     {
          Test t1 = new Test("one---");
          Test t2 = new Test("two+++");
          t1.start();
          t2.start();
          for(int x=0; x<60; x++)
          {
               System.out.println("main....."+x);
          }
     }
}

2. 创建线程的第二种方式:

       1.定义类实现Runnable接口

       2.覆盖Runnable接口的run方法

       3.通过thread了建造线程对象

       4.将runnable接口的子类对象作为参数传递给thread类的构造函数

        5.thread对象调用start方法;

 例子:               

class Ticket implements Runnable
{
     private  int tick = 100;
     public void run()
     {
          while(true)
          {
               if(tick>0)
               {
                    System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
               }
          }
     }
}
class  TicketDemo
{
     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();
     }
}

    Thread类也是Runable接口的子类,但在Thread类中并没有完全的实现Runable接口的Run方法,

    实现方式和继承方式的区别:

     如果一个类继承了Thread类,则不适合多线程之间共享资源,而实现Runable接口则可以方便的实现线程资源共享。

     实现方式好处:避免了单继承的局限性

  *两种方式的区别:

      继承thread:代码存放在thread子类run方法中

      实现runnable:代码存放在runnable接口子类的run方法中;

  

  线程的start与run方法区别:

      用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

    run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void.。

Thread线程的状态:

    在 java thread的运行周期中, 有五种状态状态:

    如图:

      blob.png

      图片来自:http://www.cnblogs.com/dolphin0520/p/3920357.html

线程状态分析:

     NEW 状态是指线程刚创建, 尚未启动

    RUNNABLE 状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等

    BLOCKED  这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区

    WAITING  这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 该线程就可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在理解点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束

   TIMED_WAITING  这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态

    TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)

分析线程状态:

下面谈谈如何让线程进入以上几种状态:

1. NEW, 这个最简单了,  

 static void NEW() {
          Thread t = new Thread ();
         System. out.println(t.getState());
    }
  输出NEW

2. RUNNABLE, 也简单, 让一个thread start, 同时代码里面不要sleep或者wait等

 private static void RUNNABLE() {
         Thread t = new Thread(){
              public void run(){
                  for(int i=0; i<Integer.MAX_VALUE; i++){
                      System. out.println(i);
                 }
             } 
         };
         t.start();
    }

3. BLOCKED, 这个就必须至少两个线程以上, 然后互相等待synchronized 块

   两个线程同时竞争这个临界区。当t1获取到锁的资格,t2就会在临界区外BLOCKED。

private static void BLOCKED() {
          final Object lock = new Object();
         Runnable run = new Runnable() { 
              @Override
              public void run() {
                  for(int i=0; i<Integer.MAX_VALUE; i++){
                      
                       synchronized (lock) {
                          System. out.println(i);
                      }
                      
                 }
             }
         };
         
         Thread t1 = new Thread(run);
         t1.setName( “t1”);
         Thread t2 = new Thread(run);
         t2.setName( “t2”);
         
         t1.start();
         t2.start();
         
    }

4. WAITING, 这个需要用到生产者消费者模型, 当生产者生产过慢的时候, 消费者就会等待生产者的下一次notify

package com.viemall.thread;

public class Product implements Runnable {

	private Resource resource;
		public Product(Resource resource) {
		super();
		this.resource = resource;
	}

	@Override
	public void run() {
		while(true){
			resource.set("Iphone");
		}
	}
}
消费者:
public class Comsumer implements Runnable{
	private Resource resource;

	public Comsumer(Resource resource) {
		super();
		this.resource = resource;
	}

	@Override
	public void run() {
		while(true){
			resource.out();
		}
	}
}
资源:
public class Resource {

	private String name;

	private int count = 1;

	private boolean config = false;

	public synchronized void set(String name) {
		while (config) {
			try {
				this.wait();
			} catch (Exception e) {
			}
		}
		this.name = name + "--" + count++;
		System.out.println(Thread.currentThread().getName() + "生产者."
				+ this.name);
		config = true;
		this.notifyAll();
	}

	public synchronized void out() {
		while (!config) {
			try {
				this.wait();
			} catch (Exception e) {
			}
		}
		System.out.println(Thread.currentThread().getName()+ "-------------消费者." + this.name);
		config = false;
		this.notifyAll();
	}

  public static void main(String[] args) {
	Resource resource = new Resource();
	Product product = new Product(resource);
	Comsumer consumer = new Comsumer(resource);
	Thread t1 = new Thread(product);
	Thread t2 = new Thread(consumer);
	t1.start();
	t2.start();
  }
}

5. TIMED_WAITING, 这个仅需要在4的基础上, 在wait方法加上一个时间参数进行限制就OK了.

    把4中的synchronized 块改成如下就可以了.

synchronized (lock) {
   try {
      lock.wait(60 * 1000L);
   } catch (InterruptedException e) {
   }
   System. out .println(i++);
 }
 synchronized (lock) {
    
    try {
          sleep(30*1000L);
    } catch (InterruptedException e) {
    }
    lock.notifyAll();
}

 对于Thread的Sleep和Wait区别:

      sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。 

      sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态,在调用sleep()方法的过程中,线程不会释放对象锁。

     而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

6. TERMINATED, 这个状态只要线程结束了run方法, 就会进入了…

  private static void TERMINATED() {
         Thread t1 = new Thread();
         t1.start();
         System. out.println(t1.getState());
          try {
             Thread. sleep(1000L);
         } catch (InterruptedException e) {
         }
         System. out.println(t1.getState());
    }
    输出: 
    RUNNABLE
    TERMINATED

  由于线程的start方法是异步启动的, 所以在其执行后立即获取状态有可能才刚进入RUN方法且还未执行完毕

Thread的其他方法:

  线程控制

     在多线程程序中,除了最重要的线程同步外,还有其它的线程控制,如线程的中断、合并、优先级等。

    线程等待(wait、notify、notifyAll)

    Wait:使当前的线程处于等待状态; 

    Notify:唤醒其中一个等待线程; 

    notifyAll:唤醒所有等待线程。

    用于线程的通信,生产者与消费者。

  线程中断(interrupt):

       详细的介绍可以查阅:http://book.51cto.com/art/201506/481045.htm

       在JDK1.0中,可以用stop方法来终止,但是现在这种方法已经被禁用了,改用interrupt方法。Thread.interrupt()方法不会中断一个正在运行的线程。它的作用是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。

在Java提供的线程支持类Thread中,有三个用于线程中断的方法: 

      public void interrupt(); 中断线程。其实是改变中断状态而已

      public static boolean interrupted(); 是一个静态方法,用于测试当前线程是否已经中断,并将线程的中断状态 清除。所以如果线程已经中断,调用两次interrupted,第二次时会返回false,因为第一次返回true后会清除中断状态。 

      public boolean isInterrupted(); 测试线程是否已经中断,并未消除中断状态

  来看一个例子:

1.	class ATask implements Runnable{   
2.	   
3.	    private double d = 0.0;   
4.	       
5.	    public void run() {   
6.	        //死循环执行打印"I am running!" 和做消耗时间的浮点计算   
7.	        while (true) {   
8.	            System.out.println("I am running!");   
9.	               
10.	            for (int i = 0; i < 900000; i++){   
11.	                d =  d + (Math.PI + Math.E) / d;   
12.	            }   
13.	            
14.	        }   
15.	    }   
16.	}   
17.	   
18.	public class InterruptTaskTest {   
19.	       
20.	    public static void main(String[] args)throws Exception{   
21.	        //将任务交给一个线程执行   
22.	        Thread t = new Thread(new ATask());   
23.	        t.start();   
24.	            
25.	        //运行一断时间中断线程   
26.	        Thread.sleep(100);   
27.	       System.out.println("****************************");   
28.	        System.out.println("InterruptedThread!");   
29.	       System.out.println("****************************");   
30.	        t.interrupt();   
31.	    }   
32.	}

    运行这个程序,我们发现调用interrupt()后,程序仍在运行,如果不强制结束,程序将一直运行下去,如下所示:

I am running! 
I am running! 
I am running! 
I am running! 
**************************** 
InterruptedThread! 
**************************** 
I am running! 
I am running! 
I am running! 
I am running! 
I am running! 
....

    interrupt()只是改变中断状态而已。interrupt()不会中断一个正在运行的线程。

    如果线程没有被阻塞,这时调用interrupt()将不起作用;否则,线程就将得到InterruptedException异常(该线程必须事先预备好处理此状况),接着逃离阻塞状态。

离开线程有两种常用的方法:

   抛出InterruptedException和用Thread.interrupted()检查是否发生中断,下面分别看一下这两种方法:

       1、在阻塞操作时如Thread.sleep()时被中断会抛出InterruptedException(注意,进行不能中断的IO操作而阻塞和要获得对象的锁调用对象的synchronized方法而阻塞时不会抛出InterruptedException)

public class TestLock {
	public static void main(String[] args) throws Exception {
		// 将任务交给一个线程执行
		Thread t = new Thread(new ATask());
		t.start();

		// 运行一断时间中断线程
		Thread.sleep(100);
		System.out.println("****************************");
		System.out.println("InterruptedThread!");
		System.out.println("****************************");
		t.interrupt();
	}
}

class ATask implements Runnable {

	private double d = 0.0;

	public void run() {
		// 死循环执行打印"I am running!" 和做消耗时间的浮点计算
		try {
			while (true) {
				System.out.println("I am running!");

				for (int i = 0; i < 900000; i++) {
					d = d + (Math.PI + Math.E) / d;
				}
				// 休眠一断时间,在休眠的过程中接收到中断信号,会抛出InterruptedException
				Thread.sleep(50);
			}
		} catch (InterruptedException e) {
			System.out.println("ATask.run() interrupted!");
		}
	}
}

打印结果:
I am running!
I am running!
****************************
InterruptedThread!
****************************
ATask.run() interrupted!

    2、Thread.interrupted()检查是否发生中断。Thread.interrupted()能告诉你线程是否发生中断,并将清除中断状态标记,所以程序不会两次通知你线程发生了中断

public class TestLock {
	public static void main(String[] args) throws Exception {
		// 将任务交给一个线程执行
		Thread t = new Thread(new ATask());
		t.start();
		// 运行一断时间中断线程
		Thread.sleep(100);
		System.out.println("****************************");
		System.out.println("InterruptedThread!");
		System.out.println("****************************");
		t.interrupt();
	}
}

class ATask implements Runnable {
	private double d = 0.0;

	public void run() {
		// 检查程序是否发生中断
		while (!Thread.interrupted()) {
			System.out.println("I am running!");
			for (int i = 0; i < 20; i++) {
				d = d + (Math.PI + Math.E) / d;
			}
		}
		System.out.println("ATask.run()interrupted!");
	}
}

I am running!
I am running!
I am running!
I am running!
****************************
InterruptedThread!
****************************
I am running!
ATask.run()interrupted!

  所以在sleep,wait,join这些方法内部会不断检查中断状态的值,而自己抛出的InterruptedException。当线程A终于执行wait(),sleep(),join()时,才马上会抛出InterruptedException。从而中断线程。

     若没有调用sleep(),wait(),join()这些方法,并且没有在线程中自己检查中断状态抛出自己抛出InterruptedException的话,InterruptedException是不会被抛出来的。

1.	class ATask implements Runnable{   
2.	   
3.	    private double d = 0.0;   
4.	       
5.	    public void run() {   
6.	           
7.	        try {   
8.	        //检查程序是否发生中断   
9.	        while (!Thread.interrupted()) {   
10.	            System.out.println("I am running!");   
11.	             
12.	            Thread.sleep(20);   
13.	               
14.	           System.out.println("Calculating");   
15.	            for (int i = 0; i < 900000; i++){   
16.	                d = d + (Math.PI + Math.E) / d;   
17.	            }   
18.	        }   
19.	           
20.	        } catch (InterruptedException e) {   
21.	            System.out.println("Exiting byException");   
22.	        }   
23.	           
24.	        System.out.println("ATask.run()interrupted!");   
25.	    }   
26.	}

    Thread.stop()也是让线程中断的静态方法,与Thread .interrupt() 最大的区别在于:interrupt()方法是设置线程的中断状态,让用户自己选择时间地点去结束线程;而stop()方法会在代码的运行处直接抛出一个ThreadDeath错误,这是一个java.lang.Error的子类。所以直接使用stop()方法就有可能造成对象的不一致性。

Thread.stop()不推荐使用。

线程合并(join)

   所谓合并,就是等待其它线程执行完,再执行当前线程,执行起来的效果就好像把其它线程合并到当前线程执行一样。其执行关系如下: 

public final void join() 

    等待该线程终止

public final void join(long millis); 

    等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。

public final void join(long millis, int nanos) 

    等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒

    这个常见的一个应用就是安装程序,很多大的软件都会包含多个插件,如果选择完整安装,则要等所有的插件都安装完成才能结束,且插件与插件之间还可能会有依赖关系。

/**
 * 插件1
 */
class Plugin1 implements Runnable {

   @Override
   public void run() {
      System.out.println("插件1开始安装.");
      System.out.println("安装中...");
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      System.out.println("插件1完成安装.");
   }
}

/**
 * 插件2
 */
class Plugin2 implements Runnable {

   @Override
   public void run() {
      System.out.println("插件2开始安装.");
      System.out.println("安装中...");
      try {
         Thread.sleep(2000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      System.out.println("插件2完成安装.");
   }
}
合并线程的调用:
System.out.println("主线程开启...");
Thread thread1 = new Thread(new Plugin1());
Thread thread2 = new Thread(new Plugin2());
try {
   thread1.start();   //开始插件1的安装
   thread1.join();       //等插件1的安装线程结束
   thread2.start();   //再开始插件2的安装
   thread2.join();       //等插件2的安装线程结束,才能回到主线程
} catch (InterruptedException e) {
   e.printStackTrace();
}
System.out.println("主线程结束,程序安装完成!");
结果如下:
主线程开启… 
插件1开始安装. 
安装中… 
插件1完成安装. 
插件2开始安装. 
安装中… 
插件2完成安装. 
主线程结束,程序安装完成!

优先级(Priority)

      线程优先级是指获得CPU资源的优先程序。优先级高的容易获得CPU资源,优先级底的较难获得CPU资源,表现出来的情况就是优先级越高执行的时间越多。

     Java中通过getPriority和setPriority方法获取和设置线程的优先级。Thread类提供了三个表示优先级的常量:MIN_PRIORITY优先级最低,为         1;NORM_PRIORITY是正常的优先级;为5,MAX_PRIORITY优先级最高,为10。我们创建线程对象后,如果不显示的设置优先级的话,默认为5。

/**
 * 优先级
 */
class PriorityThread implements Runnable{
   @Override
   public void run() {
      for (int i = 0; i < 1000; i ++) {
         System.out.println(Thread.currentThread().getName() + ": " + i);
      }
   }
}
调用代码:
//创建三个线程
Thread thread1 = new Thread(new PriorityThread(), "Thread1");
Thread thread2 = new Thread(new PriorityThread(), "Thread2");
Thread thread3 = new Thread(new PriorityThread(), "Thread3");
//设置优先级
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(8);
//开始执行线程
thread3.start();
thread2.start();
thread1.start();
从结果中我们可以看到线程thread1明显比线程thread3执行的快。

setDaemon方法的解析:

   守护线程[当前台线程(主线程)都结束,后台线程(守护线程)将会自行结束]

    xx.setDaemon(true);  该方法在启动前调用。

线程合并(yield): 

    Java线程中有一个Thread.yield( )方法,很多人翻译成线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行。—->可以达到平均效果,稍微节省该线程的运行频率.

public class YieldTest extends Thread {  
  
    public YieldTest(String name) {  
        super(name);  
    }  
  
    @Override  
    public void run() {  
        for (int i = 1; i <= 50; i++) {  
            System.out.println("" + this.getName() + "-----" + i);  
            // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)  
            if (i == 30) {  
                this.yield();  
            }  
        }  
    }  
  
    public static void main(String[] args) {  
        YieldTest yt1 = new YieldTest("张三");  
        YieldTest yt2 = new YieldTest("李四");  
        yt1.start();  
        yt2.start();  
    }  
}

相关学习资料:

   Java并发编程:进程和线程之由来

   Java并发编程:如何创建线程?

   http://ifeve.com/

发表评论