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

关于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()方法

Java多线程(1) 创建的更多相关文章

  1. java多线程-概念&创建启动&中断&守护线程&优先级&线程状态(多线程编程之一)

    今天开始就来总结一下Java多线程的基础知识点,下面是本篇的主要内容(大部分知识点参考java核心技术卷1): 1.什么是线程以及多线程与进程的区别 2.多线程的创建与启动 3.中断线程和守护线程以及 ...

  2. Java多线程的创建(一)

    方法一:继承Thread类实现 1.创建一个类A,并继承Thread类 2.重写A的run()方法 3.创建A的实例对象b,即创建了线程对象 4.使用b调用start()方法:启动线程(会自动调用ru ...

  3. Java多线程的创建与简单使用

    一.线程的基本概念 什么是线程:Thread 进程内部的一个执行单元,它是程序中一个单一的顺序控制流程. 线程又被称为轻量级进程(lightweight process) 如果在一个进程中同时运行了多 ...

  4. java多线程之创建线程的4种方式及Future

    Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例.Java可以用四种方式来创建线程: 继承Thread创建线程 实现Runnable接口创建线程 实现callab ...

  5. Java多线程的创建(二)

    前言: 虽然java的API中说创建多线程的方式只有两种(There are two ways to create a new thread of execution),分别是继承Thread类创建和 ...

  6. java多线程-线程创建

    Java 线程类也是一个 object 类,它的实例都继承自 java.lang.Thread 或其子类. 可以用如下方式用 java 中创建一个线程,执行该线程可以调用该线程的 start()方法: ...

  7. Java多线程-----匿名内部类创建线程

       1.继承Thread类创建线程 package com.practise.createthread; public class AnonymousThread { public static v ...

  8. Java多线程——之一创建线程的四种方法

    1.实现Runnable接口,重载run(),无返回值 package thread; public class ThreadRunnable implements Runnable { public ...

  9. Java多线程之创建线程的三种方式比较

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6560057.html  一:继承Thread类创建线程 1:继承Thread类定义线程子类: 2:重写run( ...

随机推荐

  1. javascript中的lambda表达式

    <!DOCTYPE html> <html> <head> </head> <body> <script> var nubLis ...

  2. 设置Linux SSH登录后的欢迎信息

    在这几个文件,各自都设置一下: /etc/motd /etc/issue 在/etc/ssh/sshd_config添加“Banner /etc/ssh/ssh_login_banner” 内容: \ ...

  3. make and make bzImage

    2.6内核 make = make bzImage + make modules 无非是改下Makefile而已 2.4 内核 01.make menuconfig 02.make dep 03.ma ...

  4. Windows+VS2012环境下编译调试MySQL源码 转

    http://m.blog.csdn.net/blog/SnowyWolf/18952643

  5. Tomcat – java.lang.OutOfMemoryError: PermGen space Cause and Solution

    Read more: http://javarevisited.blogspot.com/2012/01/tomcat-javalangoutofmemoryerror-permgen.html#ix ...

  6. [Apache] Apache 從 2.2 換至 2.4 httpd.conf 的調整筆記 (windows 環境)

    原文地址: http://www.dotblogs.com.tw/maplenote/archive/2012/07/20/apache24_httpd_conf.aspx 整理一下 Windows ...

  7. json 数据类型,后台在组数据时,错一个标点符号,前端都解析不出来。

    json 数据类型,后台在组数据时,错一个标点符号,前端都解析不出来.

  8. Ubuntu 将应用程序固定到快速启动栏

    Ubuntu上没有快捷方式的说法,而通过软件中心安装的软件就有图标,并能加入到启动器上,这是因为它们有一个desktop配置文件的缘故.这些配置文件在/usr/share/applications这个 ...

  9. 使用supervisor过程的坑

    1.安装:由于使用的是公司的虚拟机,所以使用pip install supervisor的过程遇到很多权限问题. 中间尝试使用sudo pip install supervisor的方式安装,但是使用 ...

  10. Android几种Service常驻内存的小思路

    老话说的好:躲得了初一,躲只是高三 ! 大多数的Android开发人员遇到的一个问题-怎样保证Service常驻内存. 近期我最终也在项目中务必幸运的遇到了 先来了解一下什么是Service常驻内存. ...