zoukankan      html  css  js  c++  java
  • Java多线程(1) 创建

    一.线程的生命周期及五种基本状态

    关于Java中线程的生命周期,首先看一下以下这张较为经典的图:

    Java线程具有五中基本状态

    新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

    就绪状态(Runnable):当调用线程对象的start()方法(t.start();)。线程即进入就绪状态。处于就绪状态的线程,仅仅是说明此线程已经做好了准备。随时等待CPU调度运行。并非说运行了t.start()此线程马上就会运行;

    执行状态(Running):当CPU開始调度处于就绪状态的线程时。此时线程才得以真正执行,即进入到执行状态。注:就绪状态是进入到执行状态的唯一入口,也就是说,线程要想进入执行状态执行。首先必须处于就绪状态中;

    堵塞状态(Blocked):处于执行状态中的线程因为某种原因,临时放弃对CPU的使用权。停止执行。此时进入堵塞状态。直到其进入到就绪状态,才 有机会再次被CPU调用以进入到执行状态。

    依据堵塞产生的原因不同。堵塞状态又能够分为三种:

    1.等待堵塞:执行状态中的线程执行wait()方法。使本线程进入到等待堵塞状态。

    2.同步堵塞 -- 线程在获取synchronized同步锁失败(由于锁被其他线程所占用),它会进入同步堵塞状态;

    3.其它堵塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到堵塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完成时。线程又一次转入就绪状态。

    【疑问】:锁的持有问题

    死亡状态(Dead):线程运行完了或者因异常退出了run()方法,该线程结束生命周期。


    二. Java多线程的创建及启动

    Java中线程的创建常见有如三种基本形式

    1.继承Thread类。重写该类的run()方法。

    复制代码
     1 class MyThread extends Thread {
     2     
     3     private int i = 0;
     4 
     5     @Override
     6     public void run() {
     7         for (i = 0; i < 100; i++) {
     8             System.out.println(Thread.currentThread().getName() + " " + i);
     9         }
    10     }
    11 }
    复制代码

     1 public class ThreadTest {
     2 
     3     public static void main(String[] args) {
     4         for (int i = 0; i < 100; i++) {
     5             System.out.println(Thread.currentThread().getName() + " " + i);
     6             if (i == 30) {
     7                 Thread myThread1 = new MyThread();     // 创建一个新的线程  myThread1  此线程进入新建状态
     8                 Thread myThread2 = new MyThread();     // 创建一个新的线程 myThread2 此线程进入新建状态
     9                 myThread1.start();                     // 调用start()方法使得线程进入就绪状态
    10                 myThread2.start();                     // 调用start()方法使得线程进入就绪状态
    11             }
    12         }
    13     }
    14 }
    复制代码

    如上所看到的,继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,当中run()方法的方法体代表了线程须要完毕的任务。称之为线程运行体。当创建此线程类对象时一个新的线程得以创建。并进入到线程新建状态。

    通过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不一定会立即得以运行,这取决于CPU调度时机。

    2.实现Runnable接口,并重写该接口的run()方法。该run()方法相同是线程运行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

    复制代码
     1 class MyRunnable implements Runnable {
     2     private int i = 0;
     3 
     4     @Override
     5     public void run() {
     6         for (i = 0; i < 100; i++) {
     7             System.out.println(Thread.currentThread().getName() + " " + i);
     8         }
     9     }
    10 }
    复制代码
    复制代码
     1 public class ThreadTest {
     2 
     3     public static void main(String[] args) {
     4         for (int i = 0; i < 100; i++) {
     5             System.out.println(Thread.currentThread().getName() + " " + i);
     6             if (i == 30) {
     7                 Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
     8                 Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
     9                 Thread thread2 = new Thread(myRunnable);
    10                 thread1.start(); // 调用start()方法使得线程进入就绪状态
    11                 thread2.start();
    12             }
    13         }
    14     }
    15 }
    复制代码

    相信以上两种创建新线程的方式大家都非常熟悉了。那么Thread和Runnable之间究竟是什么关系呢?我们首先来看一下以下这个样例。

    1 public interface Runnable {
    2    
    3     public abstract void run();
    4     
    5 }

    我们看一下Thread类中对Runnable接口中run()方法的实现:

    复制代码
      @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    复制代码

    也就是说,当运行到Thread类中的run()方法时,会首先推断target是否存在。存在则运行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。可是上述给到的列子中,因为多态的存在。根本就没有运行到Thread类中的run()方法,而是直接先运行了运行时类型即MyThread类中的run()方法。


    3.使用Callable和Future接口创建线程。详细是创建Callable接口的实现类,并实现clall()方法。

    并使用FutureTask类来包装Callable实现类的对象。且以此FutureTask对象作为Thread对象的target来创建线程。


     看着好像有点复杂,直接来看一个样例就清晰了。

    复制代码
     1 public class ThreadTest {
     2 
     3     public static void main(String[] args) {
     4 
     5         Callable<Integer> myCallable = new MyCallable();    // 创建MyCallable对象
     6         FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
     7 
     8         for (int i = 0; i < 100; i++) {
     9             System.out.println(Thread.currentThread().getName() + " " + i);
    10             if (i == 30) {
    11                 Thread thread = new Thread(ft);   //FutureTask对象作为Thread对象的target创建新的线程
    12                 thread.start();                      //线程进入到就绪状态
    13             }
    14         }
    15 
    16         System.out.println("主线程for循环运行完成..");
    17         
    18         try {
    19             int sum = ft.get();            //取得新创建的新线程中的call()方法返回的结果
    20             System.out.println("sum = " + sum);
    21         } catch (InterruptedException e) {
    22             e.printStackTrace();
    23         } catch (ExecutionException e) {
    24             e.printStackTrace();
    25         }
    26 
    27     }
    28 }
    29 
    30 
    31 class MyCallable implements Callable<Integer> {
    32     private int i = 0;
    33 
    34     // 与run()方法不同的是。call()方法具有返回值
    35     @Override
    36     public Integer call() {
    37         int sum = 0;
    38         for (; i < 100; i++) {
    39             System.out.println(Thread.currentThread().getName() + " " + i);
    40             sum += i;
    41         }
    42         return sum;
    43     }
    44 
    45 }
    复制代码

    首先,我们发现。在实现Callable接口中。此时不再是run()方法了,而是call()方法,此call()方法作为线程运行体,同一时候还具有返回值。在创建新的线程时,是通过FutureTask来包装MyCallable对象,同一时候作为了Thread对象的target。

    那么看下FutureTask类的定义:

    1 public class FutureTask<V> implements RunnableFuture<V> {
    2     
    3     //....
    4     
    5 }
    1 public interface RunnableFuture<V> extends Runnable, Future<V> {
    2     
    3     void run();
    4     
    5 }

    于是。我们发现FutureTask类实际上是同一时候实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,能够作为Thread对象的target。而Future特性,使得其能够取得新创建线程中的call()方法的返回值。

    运行下此程序。我们发现sum = 4950永远都是最后输出的。

    而“主线程for循环运行完成..”则非常可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环运行完成..”的输出时机是没有不论什么问题的,那么为什么sum =4950会永远最后输出呢?

    原因在于通过ft.get()方法获取子线程call()方法的返回值时。当子线程此方法还未运行完成,ft.get()方法会一直堵塞,直到call()方法运行完成才干取到返回值。

    上述主要解说了三种常见的线程创建方式,对于线程的启动而言。都是调用线程对象的start()方法。须要特别注意的是:不能对同一线程对象两次调用start()方法



  • 相关阅读:
    Codeforces 813F Bipartite Checking 线段树 + 并查集
    Codeforces 263E Rhombus (看题解)
    Codeforces 173E Camping Groups hash
    Codeforces 311C Fetch the Treasure 取模意义下的最短路 (看题解)
    R 培训之 Table
    Docker命令详解
    Celery的实践指南
    Using Celery with Djang
    PostgreSQL
    改时区参考
  • 原文地址:https://www.cnblogs.com/brucemengbm/p/7352399.html
Copyright © 2011-2022 走看看