浅谈Java中的多线程

来源:互联网 发布:数据库设计规则 编辑:程序博客网 时间:2024/04/30 03:04

导论

并发和并行

我们知道,在单核机器上,“多进程”并不是真正的多个进程在同时执行,而是通过CPU时间分片,操作系统快速在进程间切换而模拟出来的多进程。我们通常把这种情况成为并发,也就是多个进程的运行行为是“一并发生”的,但不是同时执行的,因为CPU核数的限制(PC和通用寄存器只有一套,严格来说在同一时刻只能存在一个进程的上下文)。

现在,我们使用的计算机基本上都搭载了多核CPU,这时,我们能真正的实现多个进程并行执行,这种情况叫做并行,因为多个进程是真正“一并执行”的(具体多少个进程可以并行执行取决于CPU核数)。综合以上,我们知道,并发是一个比并行更加宽泛的概念。也就是说,在单核情况下,并发只是并发;而在多核的情况下,并发就变为了并行。下文中我们将统一用并发来指代这一概念。

1  基本概念

    进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

    线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

    同时执行是人的感觉,在线程之间实际上轮换执行。

    在Java中线程指两件不同的事情:

    1)java.lang.Thread类的一个实例;

    2)线程的执行

   关于Java中线程的生命周期,下面给出的这幅图总结的非常详细生动,它包含了Java多线程中的所有重要知识点。

Java线程具有5种基本状态:

新建状态(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处理完毕时,线程重新转入就绪状态。

Thread类中的方法

sleep方法,这是一个静态方法,作用是让当前线程进入休眠状态(但线程不会释放已获取的锁),这个休眠状态其实就是我们上面提到过的Time Waiting状态,从休眠状态“苏醒”后,线程会进入到Runnable状态。

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

join方法实现是通过wait(小提示:Object 提供的方法)。当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。

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

yield方法,这是一个静态方法,作用是让当前线程“让步”,目的是为了让优先级不低于当前线程的线程有机会运行,这个方法不会释放锁。

interrupt方法,这是一个实例方法。每个线程都有一个中断状态标识,这个方法的作用就是将相应线程的中断状态标记为true,这样相应的线程调用isInterrupted方法就会返回true。通过使用这个方法,能够终止那些通过调用可中断方法进入阻塞状态的线程。常见的可中断方法有sleep、wait、join,这些方法的内部实现会时不时的检查当前线程的中断状态,若为true会立刻抛出一个InterruptedException异常,从而终止当前线程。

2  Java多线程的创建与启动

Java中线程的创建有以下三种基本形式

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

class MyThread extends Thread {@Overridepublic void run(){//重写run()方法,这是线程的执行体System.out.println(Thread.currentThread().getName());}}public class MyThreadTest{  public static void main(String []args){  Thread m1=new MyThread();//创建线程1,进入新建状态  Thread m2=new MyThread();//创建线程2,进入新建状态  m1.start();//线程1进入就绪状态  m2.start();//线程2进入就绪状态  }  }

2)实现Runnable接口,并重写该接口的run()方法,该方法同样是线程的执行体,先创建MyRunnable对象,并将该对象作为Thread类的target来创建Thread对象,该对象才是真正的线程的对象。

class MyRunnable implements Runnable{@Overridepublic void run(){//重写run()方法,这是线程的执行体System.out.println(Thread.currentThread().getName());}}public class MyThreadTest{  public static void main(String []args){  Runnable myRunnable=new MyRunnable();//创建Runnable对象  Thread t1=new Thread(myRunnable);//将myRunnable作为Thread类的target创建线程1,该对象才是真正的线程对象  Thread t2=new Thread(myRunnable);//创建线程2  t1.start();//线程1进入就绪状态  t2.start();//线程2进入就绪状态  }  }
那么Thread类和Runnable接口二者有什么联系呢?其实,Thread类本身也实现了Runnable接口,run()方法最先是在Runnable接口中定义的。

public interface Runnable {  public abstract void run();}
Thread类中对run()方法进行了重写(源于Thread源码)。

 @Override  public void run() {     if(target!=null){ target.run();     }}
也就是说当执行到Thread类中的run()方法时,会先判断target(构造函数中的Runnable对象)是否存在,如果存在则执行target中的run方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。


上面两种方法它们有一个共同的缺点,那就是异步方法run没有返回值,也就是说我们无法直接获取它的执行结果,只能通过共享变量或者线程间通信等方式来获取。下面这种方法有返回值。

3)使用Callable和Future接口创建线程。具体方法是:首先创建Callable接口的实现类,并在该类中重写call()方法,然后使用FutureTask类来包装Callable实现类的对象,并以此FutureTask类的对象作为Thread类的target来创建线程。

class MyCallable implements Callable<Integer>{@Overridepublic Integer call(){//区别于run(),call()方法具有返回值System.out.println(Thread.currentThread().getName());return 1;}}public class MyThreadTest{  public static void main(String []args){ Callable<Integer> myCallable=new MyCallable();//创建Callable实现类对象 FutureTask<Integer> ft=new FutureTask<Integer>(myCallable);//使用FutureTask类来包装Callable实现类对象 Thread thread=new Thread(ft);//把ft作为Tread类的target来创建线程 thread.start();//线程进入就绪状态 try{ int sum=ft.get();//获取新创建线程的call()返回的结果 System.out.println(sum); }catch (InterruptedException e) {//当一个线程处于等待,睡眠,或者占用,也就是说阻塞状态,而这时线程被中断就会抛出这类错误thread.interrupt();//手动把自己的线程中断,这样可以保证线程一定能够被及时中断 e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }  }  }

3  Java多线程的就绪、运行和消亡状态

就绪->运行:当此线程得到cpu资源;

运行->就绪:此线程主动调用yield()方法或在运行过程中失去cpu资源

运行->消亡:此线程的执行体执行完毕或者发生异常。

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

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

class MyRunnable implements Runnable{   private Boolean stop;@Overridepublic void run() {if(!stop){System.out.println(Thread.currentThread().getName());}}public  void stopThread() {this.stop = true;}}public class ThreadTest {public static void main(String[] args) {MyRunnable m=new MyRunnable();Thread t=new Thread(m);t.start();//进入就绪状态m.stopThread();//使线程消亡}}




0 0