Java基础之多线程框架

一.进程与线程的区别

1.定义:

    进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

    线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

2.关系:

    一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。

相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

3.区别:

   进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

     3.1线程的划分尺度小于进程,使得多线程程序的并发性高。

     3.2简而言之,一个程序至少有一个进程,一个进程至少有一个线程

     3.3另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

    3.4线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

   3.5从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

4.优缺点:

      线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。

二.多线程两个基本实现框架

       Java编写程序都运行在在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的。每用Java命令启动一个Java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行。

      在Java中,多线程的实现有两种基本方式:继承java.lang.Thread类;实现java.lang.Runnable接口。(优先选择)。

     第三种方式(少):使用ExecutorService、Callable、Future实现有返回结果的多线程。

1.继承Thread类来实现多线程

当一个类继承Thread类时,在类中必须重载run()方法,同时这个run()方法也是线程的入口,在调用的过程中,通过调用start()方法来启动新线程,其基本框架为:

class 类名 extends Thread{
方法1;
方法2;
…
  public void run(){
   // other code…
  }
 属性1;
 属性2;
  …  
}

一般生活场窗口售票情况:

 1 class TestThread extends Thread
 2  {
 3     private String name;
 4     public TestThread(String name)
 5      {
 6         this.name=name;
 7     }
 8      public void run()
 9     {
10 
11          for (int i = 0; i < 7; i++)
12         {
13             if (num > 0)
14             {
15                 System.out.println(name+"正在卖票  "+"num= " + num--);
16              }
17         }
18      }
19   
20     public static void main(String[] args)
21     {
22  
23         TestThread h1 = new TestThread("窗口1");
24         TestThread h2 = new TestThread("窗口2");
25         TestThread h3 = new TestThread("窗口3");
26        h1.start();
27        h2.start();
28        h3.start();
29      }
30     private int num = 4;
31  }

这样出现的问题是:1.不清楚线程之间执行的具体顺序,2.相当于每个窗口都在售卖4张票。

优化方案:实现Runnable接口

2.实现Runnable接口来实现多线程

      和继承Thread类似,当一个类实现Runnable接口时,在类中也必须重载run()方法,同时这个run()方法也是线程的入口,在调用的过程中,通过调用start()方法来启动新线程,其基本框架为:

class 类名 implements Runnable{
 方法1;
 方法2;
 …
 public void run(){
  // other code…
  }
属性1;
属性2;
… 
}

代码改进:

class MyThread implements Runnable
{ 
    private int ticket = 5;  //5张票
 
    public void run() 
    {
         for (int i=0; i<=20; i++) 
         {
            if (this.ticket > 0) 
             {
                 System.out.println(Thread.currentThread().getName()+ "正在卖票"+this.ticket--);
            }
         }
     }
 }
 public class TestThread {     
     public static void main(String [] args) 
     {
        MyThread my = new MyThread();
        new Thread(my, "1号窗口").start();
        new Thread(my, "2号窗口").start();
        new Thread(my, "3号窗口").start();
     }
 }

    程序执行的结果为:

1 1号窗口正在卖票5
2 1号窗口正在卖票4
3 1号窗口正在卖票3
4 2号窗口正在卖票2
5 1号窗口正在卖票1

3.使用ExecutorService、Callable、Future实现有返回结果的多线程

     ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类。返回结果的线程是在JDK1.5中引入的新特征,确实很实用,有了这种特征我就不需要再为了得到返回值而大费周折了,而且即便实现了也可能漏洞百出。可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。

 1 import java.util.concurrent.*;  
 2 import java.util.Date;  
 3 import java.util.List;  
 4 import java.util.ArrayList;  
 5   
 6 /** 
 7 * 有返回值的线程 
 8 */  
 9 @SuppressWarnings("unchecked")  
10 public class Test {  
11 public static void main(String[] args) throws ExecutionException,  
12     InterruptedException {  
13    System.out.println("----程序开始运行----");  
14    Date date1 = new Date();  
15   
16    int taskSize = 5;  
17    // 创建一个线程池  
18    ExecutorService pool = Executors.newFixedThreadPool(taskSize);  
19    // 创建多个有返回值的任务  
20    List<Future> list = new ArrayList<Future>();  
21    for (int i = 0; i < taskSize; i++) {  
22     Callable c = new MyCallable(i + " ");  
23     // 执行任务并获取Future对象  
24     Future f = pool.submit(c);  
25     // System.out.println(">>>" + f.get().toString());  
26     list.add(f);  
27    }  
28    // 关闭线程池  
29    pool.shutdown();  
30   
31    // 获取所有并发任务的运行结果  
32    for (Future f : list) {  
33     // 从Future对象上获取任务的返回值,并输出到控制台  
34     System.out.println(">>>" + f.get().toString());  
35    }  
36   
37    Date date2 = new Date();  
38    System.out.println("----程序结束运行----,程序运行时间【"  
39      + (date2.getTime() - date1.getTime()) + "毫秒】");  
40 }  
41 }  
42   
43 class MyCallable implements Callable<Object> {  
44 private String taskNum;  
45   
46 MyCallable(String taskNum) {  
47    this.taskNum = taskNum;  
48 }  
49   
50 public Object call() throws Exception {  
51    System.out.println(">>>" + taskNum + "任务启动");  
52    Date dateTmp1 = new Date();  
53    Thread.sleep(1000);  
54    Date dateTmp2 = new Date();  
55    long time = dateTmp2.getTime() - dateTmp1.getTime();  
56    System.out.println(">>>" + taskNum + "任务终止");  
57    return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";  
58 }  
59 }  

代码说明:
上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads) 
创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool() 
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor() 
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。

二:补充说明

       在继承Thread类实现多线程时,我们创建了三个不同的对象,所以创建的三个线程实际上是完成的三个不同的任务,所以才会相互独立的完成;而通过实现Runable接口来实现多线程时,我们只创建了一个对象,然后实例化三个不同的线程去完成这个任务,所以相当于是共同完成任务。

Thread类也是实现Runnable接口的:

class Thread implements Runnable {
    //…
 public void run() {
        if (target != null) {
              target.run();
        }
        }
 }

Thread中的run方法其实就是调用的是Runnable接口的run方法。

Reference:http://www.cnblogs.com/rollenholt/archive/2011/08/28/2156357.html

原文地址:https://www.cnblogs.com/wangzhengyu/p/8527308.html