Java多线程全面解析

来源:互联网 发布:跳跃网络 垃圾公司 编辑:程序博客网 时间:2024/06/06 14:24
        在说Java多线程之前我觉得有必要先来认识一下进程、线程的概念。

       一般来说,当运行一个应用程序的时候就启动了一个进程。但是需要注意的是进程是一个静态概念,是不可以执行的。而线程则是一个程序中不同的执行路径,是在进程中执行的一个任务。线程需要较少的资源来创建和驻留在进程中,并且可以共享进程中的资源。知道了什么是进程和线程,也就必须要了解一下并发执行了,其实并发执行就是多个线程同时运行。

       下面是从网上看到的一个举例,个人感觉很形象生动易于理解。


       这幅图是一个双向多车道的道路图,我们可以把整个道路看成是一个“进程”,那么图中白色虚线分隔开来的各个车道就是进程中的各个“线程”了。

       下面从几个角度来分析进程(道路)和线程(车道)的关系

       1.共享:这些线程(车道)共享了进程(道路)的公共资源(土地);

       2.依赖:这些线程(车道)绝不可以脱离进程(道路)而存在;

       3.并发和同步这些线程(车道)之间可以并发执行(各个道路,各走各的),也可以互相同步(某些车道在交通灯亮时禁止继续前行,必须等待其他车道的车辆通行完毕);

       4.死锁:这些线程(车道)由代码逻辑(交通灯)来控制运行,一旦代码逻辑有误(交通灯坏了),线程将陷入混乱、无序中(死锁,多个线程同时竞争一个资源)。

       5.调度:这些线程(车道)之间谁先运行是未知的,只有在线程刚好分配到CPU时间片(交通灯变化)的那一刻才知道。

           
       通过一个例子我们大概了解了进程和线程之间的关系,接下来就是看看在实际应用中的进程和线程吧。当我们启动一个Java
应用程序时,就会启动一个JVM进程。在这个里面,只有它自己一个进程,但是会有多个线程。其中从程序的入口点main()方法就是一个线程,我们常称之为主线程。当main方法执行完毕后,主线程运行完成,JVM进程也随之结束。

      Java语言对多线程的支持主要有以下两种方式:继承Thread类和实现Ruunnable接口。

      第一种方式:继承Thread

<span style="font-family:SimSun;font-size:18px;"><span style="font-size:18px;"><span style="font-family:SimSun;font-size:18px;">public class MyThread extends Thread {  public void run() {   System.out.println("MyThread.run()");  }}</span></span></span>

      其实Thread类本质上也是一个实现了Runnable接口的实例,在自己的类中可以重写run方法,这样通过start()就可以启动一个新的线程并且执行自己的run方法了。(一个线程类可以启动多个线程)

<span style="font-family:SimSun;font-size:18px;"><span style="font-size:18px;"><span style="font-family:SimSun;font-size:18px;">MyThread thread1 = new MyThread();MyThread thread2 = new MyThread();thread1.start();thread2.start(); </span></span></span>

      第二种方式:实现Runnable接口

<span style="font-family:SimSun;font-size:18px;"><span style="font-size:18px;"><span style="font-family:SimSun;font-size:18px;">public class MyThread implements Runnable {  public void run() {   System.out.println("MyThread.run()");  }}</span></span></span>

      这是个人比较推荐的一种方式,因为众所周知,java是不支持多继承的,也就是说如果我通过第一方式,让MyThread extends Tread的话,MyThread就不能再继承其他类了,这显然是很不灵活的。

      通过实现Runnable接口方式来创建线程,需要首先实例化一个Thread类,并将自己的MyThread实例传入。

<span style="font-family:SimSun;font-size:18px;"><span style="font-size:18px;"><span style="font-family:SimSun;font-size:18px;">MyThread myThread = new MyThread();Thread thread = new Thread(myThread);thread.start();</span></span></span>

     
       Runnable
接口中只有一个方法,就是run(),用来定义线程运行体的。在实现Runnable接口的类中我们就可以实现该接口的run方法来定义自己的代码逻辑。同时,在run方法中,我们还可以使用Thread的静态方法currentThread()来获取当前线程的引用。

       说到这里就该说一说一个大家很容易错误理解的地方了,起码我曾经是理解错误的。那就是当start方法调用之后就立即执行线程代码了,其实不是的。Start方法调用之后只是使得该线程变成了就绪状态(Runnable,什么时候运行则是操作系统来决定的。

      既然已经说到了就绪状态(Runnable),那就正好来看看线程的状态转换吧。还是来一张图:

                           


      可以看出,线程共有五个状态,下面就一一说明它每个状态都做了什么事情,已经五个状态间是如何转换的。

      1.新建状态(New):新创建了一个线程对象。MyThread thread1 = new MyThread();

      2.就绪状态(Runnable):调用该线程的start方法后,该线程变成可运行的状态,等待获取cpu的使用权;thread.start();

      3.运行状态(Running):就绪状态的线程获得了CPU的使用权,执行程序代码;

      4.阻塞状态(Blocked):线程因为某些原因暂时停止运行,放弃CPU使用权。阻塞的情况分为以下三种,

          a) 等待阻塞:运行的线程执行wait()方法, JVM就把该线程放入了等待池中。(notify()和notifyAll()则是唤醒等待池中的一个/所有等待线程)。

         b) 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM把该线程放入锁。

         c) 其他阻塞:运行的线程执行sleep()或join()方法,或者发出I/O请求时,JVM将该线程置为阻塞状态。(当sleep()状态超时、join()等待线程终止或超时、I/O处理完毕后线程重新转入就绪状态)

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

      关于以上五种状态间详细转换见下图:

                          

 

      在上述讲解关于线程状态转换中,涉及到多个关于线程调度的API,接下来就一一介绍:

      1.睡眠:sleep()将当前线程睡眠,并指定毫秒数。

      2.等待:wait()(Object类中的方法)| | 唤醒:notify()(Object类中的方法)

当前线程进入等待池中。

      3.加入:join()将当前线程与该线程“合并”,即等待该线程结束后,再恢复当前线程的运行。同方法调用一样,是同步执行。

      4.让步:yield()让出CPU,当前线程置为就绪状态,等待调度。

      5.终止:stop()强行终止线程。这种方式是不推荐的,因为它可能发生不可预料的结果,我们一般可以使用退出标识终止线程。通过while(flag){…}来处理。


      关于这几个 API的区别主要有

      1.线程状态:调用sleep和wait方法后,线程进入block状态 | | 调用yield方法后,线程进入Runnable状态

      2.相互关系:wait方法体现了线程之间的互斥关系 | | join方法体现了线程之间的同步关系

      3.解锁:sleep方法到时间后自动“醒来”| | wait方法必须通过notify方法来“醒来”

 

      最后就要说说关于使用多线程的安全问题了。在使用多线程时,很容易出现以下情况:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。解决办法就是“同步-加锁“(synchronized )。可以在代码块上加锁,也可以在方法上加锁。

     最后的最后,为什么要使用多线程呢?两个原因,一是因为多线程并发的执行可以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。二是因为多个线程共享堆内存(heap memory),因此创建多个线程去执行一些任务会比创建多个进程更好。

 

1 0
原创粉丝点击