多线程

来源:互联网 发布:网络直销做什么产品好 编辑:程序博客网 时间:2024/05/16 08:53

1.进程和线程的区别

Linux中定义:
进程(process):程序被触发以后,执行者的权限与属性、程序的程序代码与所需数据等都会被加载到内存中,操作系统并给与这个内存内的单元一个标识符(PID),可以说,进程就是一个正在运行中的程序。

线程:是共享在内存空间中并发的多通道执行路径,它们共享一个进程的资源,如文件描述和信号处理。
理解:

  • 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。
  • 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

二者关系:
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。多进程是指操作系统能同时运行多个任务(程序)。多线程是指在同一程序中有多个顺序流在执行。
关系如下图所示:
这里写图片描述

2.线程的切换

线程间的切换图:
这里写图片描述
2.1、初始状态(New)

  • 实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来线程就进入了初始状态

2.2、可运行状态(Runnable)

  • 线程对象创建以后,其他线程调用了该对象的start()方法。该状态的线程位于可运行池中,变得可运行,等待CPU的使用权
  • 当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入可运行状态。
  • 当前线程时间片用完了,调用当前yield方法,当前线程进入可运行状态。
  • 锁池里的线程拿到对象锁后,进入可运行状态。

2.3、运行状态(Running)

  • 线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

2.4、阻塞状态(Blocking)
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

  • 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
  • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

2.5、死亡状态(Dead)

  • 当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。
  • 在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

2.6、等待队列
调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
与等待队列相关的步骤和图:
这里写图片描述

  • 线程1获取对象A的锁,正在使用对象A。
  • 线程1调用对象A的wait()方法。
  • 线程1释放对象A的锁,并马上进入等待队列。
  • 锁池里面的对象争抢对象A的锁。
  • 线程5获得对象A的锁,进入synchronized块,使用对象A。
  • 线程5调用对象A的notifyAll()方法,唤醒所有线程,所有线程进入锁池。线程5调用对象A的notify()方法,唤醒一个线程,不知道会唤醒谁,被唤醒的那个线程进入锁池。notifyAll()方法所在synchronized结束,线程5释放对象A的锁。
  • 锁池里面的线程争抢对象锁,但线程1什么时候能抢到就不知道了。||||| 原本锁池+第6步被唤醒的线程一起争抢对象锁。

2.7 锁池状态

  • 当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入锁池状态。简言之,锁池里面放的都是想争夺对象锁的线程。
  • 当一个线程1被另外一个线程2唤醒时,1线程进入锁池状态,去争夺对象锁。
  • 锁池是在同步的环境下才有的概念,一个对象对应一个锁池。

2.8 线程同步

  • synchronized关键字的作用域有二种:
  • 是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
  • 是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
  • 除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){区块},它的作用域是当前对象;
  • synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
    **总的说来,**synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。

注意

  • 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
  • 每个对象只有一个锁(lock)与之相关联。
  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

2.9 线程中几种方法

  • Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入阻塞,但不释放对象锁,millis后线程自动苏醒进入可运行状态。作用:给其它线程执行机会的最佳方式。
  • Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
  • t.join()/t.join(long millis),当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。
  • obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
  • obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

3. 线程间的通信

当线程共享同一数据时,为了实现数据的同步性,比如卖票,一张票只能卖一次。所以要解决线程同步问题。

  • 通过锁(Lock)对象的方式解决线程安全问题
  • 通过synchronied关键字的方式解决线程安全问题

线程间的通信机制

  • synchronied关键字等待/通知机制:
  • 条件对象的等待/通知机制

参考博客
http://blog.csdn.net/javazejian/article/details/50878665

4. Java实现多线程

在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。
4.1 扩展java.lang.Thread类
MyThread1.java

package InterviewTitle.ProcessThread;public class MyThread1 extends Thread{    private String name;    public MyThread1(String name){        this.name=name;    }    public void run(){        for(int i=0;i<3;i++){            System.out.println(name+"运行"+i);        }        try{            Thread.sleep((int)Math.random()*10);        }catch(InterruptedException ex){            ex.printStackTrace();        }    }}

MyThread1Test.java

package InterviewTitle.ProcessThread;import static org.junit.Assert.*;import org.junit.Test;public class MyThread1Test {    @Test    public void test() {        MyThread1 myThread1=new MyThread1("cat");        MyThread1 myThread2=new MyThread1("dog");        myThread1.start();        myThread2.start();    }}

输出结果

dog运行0
cat运行0
cat运行1
cat运行2
cat运行3
dog运行1
cat运行4
dog运行2
dog运行3
dog运行4

再运行一次

cat运行0
dog运行0
dog运行1
dog运行2
dog运行3
dog运行4
cat运行1
cat运行2
cat运行3
cat运行4

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MyThread1的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。
从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
注意:
start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。

4.2 实现java.lang.Runnable接口
MyThread2.java

package InterviewTitle.ProcessThread;public class MyThread2 implements Runnable{    private String name;    public MyThread2(String name) {        // TODO Auto-generated constructor stub        this.name=name;    }    @Override    public void run() {        // TODO Auto-generated method stub        for(int i=0;i<5;i++){            System.out.println(name+"运行"+i);        }        try{            Thread.sleep((int)Math.random()*10);        }catch(InterruptedException ex){            ex.printStackTrace();        }    }}

MyThread2Test.java

package InterviewTitle.ProcessThread;import static org.junit.Assert.*;import org.junit.Test;public class MyThread2Test {    @Test    public void test() {        new Thread(new MyThread2("cat")).start();        new Thread(new MyThread2("dog")).start();    }}

运行结果

cat运行0
cat运行1
cat运行2
cat运行3
cat运行4
dog运行0
dog运行1
dog运行2
dog运行3
dog运行4

MyThread2实现了Runnable接口,使得该类有了多线程的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

4.3 Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
继承Thread类时
MyThread1.java

package InterviewTitle.ProcessThread;public class MyThread1 extends Thread{    private int count =5;    private String name;    public MyThread1(String name){        this.name=name;    }    public void run(){        for(int i=0;i<5;i++){            System.out.println(name+"运行 count="+count--);        }        try{            Thread.sleep((int)Math.random()*10);        }catch(InterruptedException ex){            ex.printStackTrace();        }    }    public static void main(String[] args) {        MyThread1 myThread1=new MyThread1("cat");        MyThread1 myThread2=new MyThread1("dog");        myThread1.start();        myThread2.start();    }}

运行结果:

dog运行 count=5
dog运行 count=4
cat运行 count=5
cat运行 count=4
cat运行 count=3
cat运行 count=2
cat运行 count=1
dog运行 count=3
dog运行 count=2
dog运行 count=1

由结果可以看出不同线程间的count是不同的。二者不互相共享资源

应用Runnable接口时
MyThread.java

package InterviewTitle.ProcessThread;public class MyThread2 implements Runnable{    private int count=5;    @Override    public void run() {        // TODO Auto-generated method stub        for(int i=0;i<5;i++){            System.out.println(Thread.currentThread().getName()+"运行count"+count--);        }        try{            Thread.sleep((int)Math.random()*10);        }catch(InterruptedException ex){            ex.printStackTrace();        }    }    public static void main(String[] args) {        MyThread2 thread2=new MyThread2();        new Thread(thread2,"cat").start();        new Thread(thread2,"dog").start();    }}

运行结果:

cat运行count5
cat运行count4
cat运行count3
cat运行count2
cat运行count1
dog运行count0
dog运行count-1
dog运行count-2
dog运行count-3
dog运行count-4

可以看出线程间可以共享数据count,这是因为每个线程都是用同一个实例化对象,如果不是同一个实例化对象,则将输出的结果和前面一样了。

总结:
实现Runnable接口比继承Thread类所具有的优势:
* 适合多个相同的程序代码的线程去处理同一个资源
* 可以避免java中的单继承的限制
* 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

5. 线程池

主要在java.util.concurrent包中

http://www.cnblogs.com/shijiaqi1066/p/3412300.html

6. 并发

http://ifeve.com/java-concurrency-thread-directory/

参考博客

http://zy19982004.iteye.com/blog/1626916
http://blog.csdn.net/hangelsing/article/details/44037675
http://www.mamicode.com/info-detail-517008.html
http://www.cnblogs.com/lmule/archive/2010/08/18/1802774.html

0 0
原创粉丝点击