Java 多线程

多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的。

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

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

上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,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 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();
 8                 Thread thread = new MyThread(myRunnable);
 9                 thread.start();
10             }
11         }
12     }
13 }
14
15 class MyRunnable implements Runnable {
16     private int i = 0;
17
18     @Override
19     public void run() {
20         System.out.println("in MyRunnable run");
21         for (i = 0; i < 100; i++) {
22             System.out.println(Thread.currentThread().getName() + " " + i);
23         }
24     }
25 }
26
27 class MyThread extends Thread {
28
29     private int i = 0;
30
31     public MyThread(Runnable runnable){
32         super(runnable);
33     }
34
35     @Override
36     public void run() {
37         System.out.println("in MyThread run");
38         for (i = 0; i < 100; i++) {
39             System.out.println(Thread.currentThread().getName() + " " + i);
40         }
41     }
42 }

同样的,与实现Runnable接口创建线程方式相似,不同的地方在于

1 Thread thread = new MyThread(myRunnable);

那么这种方式可以顺利创建出一个新的线程么?答案是肯定的。至于此时的线程执行体到底是MyRunnable接口中的run()方法还是MyThread类中的run()方法呢?通过输出我们知道线程执行体是MyThread类中的run()方法。其实原因很简单,因为Thread类本身也是实现了Runnable接口,而run()方法最先是在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多线程的就绪、运行和死亡状态

就绪状态转换为运行状态:当此线程得到处理器资源;

运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。

运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。

此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。

由于实际的业务需要,常常会遇到需要在特定时机终止某一线程的运行,使其进入到死亡状态。目前最通用的做法是设置一boolean型的变量,当条件满足时,使线程执行体快速执行完毕。如:

 1 public class ThreadTest {
 2
 3     public static void main(String[] args) {
 4
 5         MyRunnable myRunnable = new MyRunnable();
 6         Thread thread = new Thread(myRunnable);
 7
 8         for (int i = 0; i < 100; i++) {
 9             System.out.println(Thread.currentThread().getName() + " " + i);
10             if (i == 30) {
11                 thread.start();
12             }
13             if(i == 40){
14                 myRunnable.stopThread();
15             }
16         }
17     }
18 }
19
20 class MyRunnable implements Runnable {
21
22     private boolean stop;
23
24     @Override
25     public void run() {
26         for (int i = 0; i < 100 && !stop; i++) {
27             System.out.println(Thread.currentThread().getName() + " " + i);
28         }
29     }
30
31     public void stopThread() {
32         this.stop = true;
33     }
34
35 }

四.Java多线程的阻塞状态与线程控制

上文已经提到Java阻塞的几种具体类型。下面分别看下引起Java线程阻塞的主要方法。

1.join()

join —— 让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,知道B线程执行完为止,A才能得以继续执行。

 1 public class ThreadTest {
 2
 3     public static void main(String[] args) {
 4
 5         MyRunnable myRunnable = new MyRunnable();
 6         Thread thread = new Thread(myRunnable);
 7
 8         for (int i = 0; i < 100; i++) {
 9             System.out.println(Thread.currentThread().getName() + " " + i);
10             if (i == 30) {
11                 thread.start();
12                 try {
13                     thread.join();    // main线程需要等待thread线程执行完后才能继续执行
14                 } catch (InterruptedException e) {
15                     e.printStackTrace();
16                 }
17             }
18         }
19     }
20 }
21
22 class MyRunnable implements Runnable {
23
24     @Override
25     public void run() {
26         for (int i = 0; i < 100; i++) {
27             System.out.println(Thread.currentThread().getName() + " " + i);
28         }
29     }
30 }

2.sleep()

sleep —— 让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,出于sleep()中的线程也不会执行。因此sleep()方法常用来暂停线程执行。

前面有讲到,当调用了新建的线程的start()方法后,线程进入到就绪状态,可能会在接下来的某个时间获取CPU时间片得以执行,如果希望这个新线程必然性的立即执行,直接调用原来线程的sleep(1)即可。

 1 public class ThreadTest {
 2
 3     public static void main(String[] args) {
 4
 5         MyRunnable myRunnable = new MyRunnable();
 6         Thread thread = new Thread(myRunnable);
 7
 8         for (int i = 0; i < 100; i++) {
 9             System.out.println(Thread.currentThread().getName() + " " + i);
10             if (i == 30) {
11                 thread.start();
12                 try {
13                     Thread.sleep(1);   // 使得thread必然能够马上得以执行
14                 } catch (InterruptedException e) {
15                     e.printStackTrace();
16                 }
17             }
18         }
19     }
20 }
21
22 class MyRunnable implements Runnable {
23
24     @Override
25     public void run() {
26         for (int i = 0; i < 100; i++) {
27             System.out.println(Thread.currentThread().getName() + " " + i);
28         }
29     }
30 }

注:睡一个毫秒级够了,因为CPU不会空闲,会切换到新建的线程。

3.后台线程(Daemon Thread)

概念/目的:后台线程主要是为其他线程(相对可以称之为前台线程)提供服务,或“守护线程”。如JVM中的垃圾回收线程。

生命周期:后台线程的生命周期与前台线程生命周期有一定关联。主要体现在:当所有的前台线程都进入死亡状态时,后台线程会自动死亡(其实这个也很好理解,因为后台线程存在的目的在于为前台线程服务的,既然所有的前台线程都死亡了,那它自己还留着有什么用...伟大啊 ! !)。

设置后台线程:调用Thread对象的setDaemon(true)方法可以将指定的线程设置为后台线程。

 1 public class ThreadTest {
 2
 3     public static void main(String[] args) {
 4         Thread myThread = new MyThread();
 5         for (int i = 0; i < 100; i++) {
 6             System.out.println("main thread i = " + i);
 7             if (i == 20) {
 8                 myThread.setDaemon(true);
 9                 myThread.start();
10             }
11         }
12     }
13
14 }
15
16 class MyThread extends Thread {
17
18     public void run() {
19         for (int i = 0; i < 100; i++) {
20             System.out.println("i = " + i);
21             try {
22                 Thread.sleep(1);
23             } catch (InterruptedException e) {
24                 // TODO Auto-generated catch block
25                 e.printStackTrace();
26             }
27         }
28     }
29 }

判断线程是否是后台线程:调用thread对象的isDeamon()方法。

注:main线程默认是前台线程,前台线程创建中创建的子线程默认是前台线程,后台线程中创建的线程默认是后台线程。调用setDeamon(true)方法将前台线程设置为后台线程时,需要在start()方法调用之前。前天线程都死亡后,JVM通知后台线程死亡,但从接收指令到作出响应,需要一定的时间。

4.改变线程的优先级/setPriority():

每个线程在执行时都具有一定的优先级,优先级高的线程具有较多的执行机会。每个线程默认的优先级都与创建它的线程的优先级相同。main线程默认具有普通优先级。

设置线程优先级:setPriority(int priorityLevel)。参数priorityLevel范围在1-10之间,常用的有如下三个静态常量值:

MAX_PRIORITY:10

MIN_PRIORITY:1

NORM_PRIORITY:5

获取线程优先级:getPriority()。

注:具有较高线程优先级的线程对象仅表示此线程具有较多的执行机会,而非优先执行。

 1 public class ThreadTest {
 2
 3     public static void main(String[] args) {
 4         Thread myThread = new MyThread();
 5         for (int i = 0; i < 100; i++) {
 6             System.out.println("main thread i = " + i);
 7             if (i == 20) {
 8                 myThread.setPriority(Thread.MAX_PRIORITY);
 9                 myThread.start();
10             }
11         }
12     }
13
14 }
15
16 class MyThread extends Thread {
17
18     public void run() {
19         for (int i = 0; i < 100; i++) {
20             System.out.println("i = " + i);
21         }
22     }
23 }

5.线程让步:yield()

上一篇博文中已经讲到了yield()的基本作用,同时,yield()方法还与线程优先级有关,当某个线程调用yiled()方法从运行状态转换到就绪状态后,CPU从就绪状态线程队列中只会选择与该线程优先级相同或优先级更高的线程去执行。

 1 public class ThreadTest {
 2
 3     public static void main(String[] args) {
 4         Thread myThread1 = new MyThread1();
 5         Thread myThread2 = new MyThread2();
 6         myThread1.setPriority(Thread.MAX_PRIORITY);
 7         myThread2.setPriority(Thread.MIN_PRIORITY);
 8         for (int i = 0; i < 100; i++) {
 9             System.out.println("main thread i = " + i);
10             if (i == 20) {
11                 myThread1.start();
12                 myThread2.start();
13                 Thread.yield();
14             }
15         }
16     }
17
18 }
19
20 class MyThread1 extends Thread {
21
22     public void run() {
23         for (int i = 0; i < 100; i++) {
24             System.out.println("myThread 1 --  i = " + i);
25         }
26     }
27 }
28
29 class MyThread2 extends Thread {
30
31     public void run() {
32         for (int i = 0; i < 100; i++) {
33             System.out.println("myThread 2 --  i = " + i);
34         }
35     }
36 }

时间: 2024-11-03 20:48:39

Java 多线程的相关文章

Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例

概要 本章,我们对JUC包中的信号量Semaphore进行学习.内容包括:Semaphore简介Semaphore数据结构Semaphore源码分析(基于JDK1.7.0_40)Semaphore示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3534050.html Semaphore简介 Semaphore是一个计数信号量,它的本质是一个"共享锁". 信号量维护了一个信号量许可集.线程可以通过调用acquire()来获取信号量的许可

从JAVA多线程理解到集群分布式和网络设计的浅析

对于JAVA多线程的应用非常广泛,现在的系统没有多线程几乎什么也做不了,很多时候我们在何种场合如何应用多线程成为一种首先需要选择的问题,另外关于java多线程的知识也是非常的多,本文中先介绍和说明一些常用的,在后续文章中如果有必要再说明更加复杂的吧,本文主要说明多线程的一下几个内容: 1.在应用开发中什么时候选择多线程? 2.多线程应该注意些什么? 3.状态转换控制,如何解决死锁? 4.如何设计一个具有可扩展性的多线程处理器? 5.多线程联想:在多主机下的扩展-集群? 6.WEB应用的多线程以及

java多线程心得

多并发的时候,在什么情况下必须加锁?如果不加锁会产生什么样的后果. 加锁的场景跟java的new thread和Runnable的关系是什么? 看看java的concurrentMap源码. 还有spring 的web.xml启动执行源码 spring aop http://www.cnblogs.com/FDROSE1001/p/3661895.html activemq的本质是什么? java的jms hibernate由配置文件映射到实体类的本质是什么? java反射 spring aop

Rhythmk 一步一步学 JAVA (21) JAVA 多线程

1.JAVA多线程简单示例 1.1 .Thread  集成接口 Runnable 1.2 .线程状态,可以通过  Thread.getState()获取线程状态: New (新创建) Runnable (可以运行) Blocked  (被阻塞) Waiting  (等待) Timed waiting (计时等待) Terminated  (被终止) ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

(转载)Java多线程入门理解

转载出处http://blog.csdn.net/evankaka 写在前面的话:此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢?如果你觉得此文很简单,那推荐你看看Java并发包的的线程池(Java并发编程与技术内幕:线程池深入理解),或者看这个专栏:Java并发编程与技术内幕.你将会对Java里头的高并发场景下的线程有更加深刻的理解. 目录(?)[-] 一扩展javalangThread类 二实现javalan

Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

ReentrantLock介绍 ReentrantLock是一个可重入的互斥锁,又被称为"独占锁". 顾名思义,ReentrantLock锁在同一个时间点只能被一个线程锁持有:而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取.ReentrantLock分为"公平锁"和"非公平锁".它们的区别体现在获取锁的机制上是否公平."锁"是为了保护竞争资源,防止多个线程同时操作线程而出错,ReentrantLock在

synchronized与static synchronized 的差别、synchronized在JVM底层的实现原理及Java多线程锁理解

本Blog分为例如以下部分: 第一部分:synchronized与static synchronized 的差别 第二部分:JVM底层又是怎样实现synchronized的 第三部分:Java多线程锁,源码剖析 第一部分:synchronized与static synchronized的差别 1.synchronized与static synchronized 的差别 synchronized是对类的当前实例进行加锁,防止其它线程同一时候訪问该类的该实例的全部synchronized块.注意这里

深入聊聊Java多线程

一.背景 在没有学习Java多线程以前,总觉得多线程是个很神秘的东西,只有那些大神才能驾驭,新年假期没事就来学习和了解一下Java的多线程,本篇博客我们就来从头说一下多线程到底是怎么回事. 二.概述 1.进程的概念 每一个正在运行的程序都是一个进程,它是系统进行资源分配和调用的独立单位.且 每一个进程都有自己的内存空间和系统资源. 2.线程的概念 是进程中的单个顺序控制流,是一条执行路径.每个进程都可以拥有一个或者多个线程.各个线程之间都共享所属的那个进程的内存空间和系统资源. 3.单线程和多线

Java多线程 2 线程的生命周期和状态控制

一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable). 注意:不能对已经启动的线程再次调用start()方法,否则会出现Java.lang.IllegalThreadStateException异常. 2.就绪状态 处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它