带你走进多线程的世界(多线程实现方式)

做性能测试的同学使用最多的就是LoadRunner和Jemter工具了吧,能够使用洪荒之力模拟多用户同时请求服务器,来观察服务器端的负载情况并定位性能瓶颈,听上去挺高大上的。无论任何性能工具,核心原理都离不开多线程。如何实现多线程?如何定位异常状态的线程找到性能瓶颈呢?别急,开始我们的多线程之旅吧~

什么是多线程?

举个简单的例子,比如你去一家餐馆吃饭,餐馆只有一个服务员,那么这个服务员给你点菜的时候,别的人就得等着。但如果这个餐厅有3个服务员A,B,C,那么同一时刻就可以给3个顾客(甲乙丙)去点菜,每个顾客点了不同的2道菜。我们把餐馆理解成一个进程,服务员A,B,C理解为3个线程,后厨做菜的厨师是cpu(假设是单核的,一个cpu)。

从A,B,C 三个服务员同时接待 3个顾客(甲乙丙)这个表象看线程是同步,并发执行的,但是厨师在做菜的过程中是有先后之分的,厨师会把甲乙丙三个人的菜分开来做,做完甲的菜,立刻开始做乙的菜,乙的菜可能需要时间蒸的时候,会去做丙的菜,就这样不停的切换做着甲乙丙三个顾客的菜,而在甲乙丙顾客看来他们桌子上都有菜吃,误以为是同事做出来的。但严格意义上讲,同一时刻只有一个线程运行,但是用户会觉得是多个线程同时运行的。

Java多线程实现

java多线程实现主要有三种方式:继承thread类,实现runnable接口,使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。这里我们只谈前两种。


1、继承Thread类实现多线程

我们先看一个例子:

package thread;

[java]  view plain  copy
 print ?
  1. public class Thread_1 extends Thread{  
  2.       
  3.     private String name;  
  4.   
  5.     public Thread_1(){  
  6.           
  7.     }  
  8.       
  9.     public Thread_1(String name){  
  10.         this.name = name ;   
  11.           
  12.     }  
  13.   
  14.     public void run(){  
  15.         for (int i = 0;i<5 ; i++){  
  16.             System.out.println(name + "运行" +i);  
  17.         }  
  18.     }  
  19.       
  20.     public static void main(String[] args){  
  21.         Thread_1 h1 = new Thread_1("A");  
  22.         Thread_1 h2 = new Thread_1("B");  
  23.         h1.run();  
  24.         h2.run();  
  25.           
  26.     }  
  27. }  

我们看一下运行结果:

A运行0

A运行1

A运行2

A运行3

A运行4

B运行0

B运行1

B运行2

B运行3

B运行4


我们会发现这些都是顺序执行的,并没有多线程执行,为什么呢?因为我们直接调用了run方法,启动线程唯一的方法是通过Thread类调用start()方法, start()方法是一个native方法,即本地操作系统方法,它将启动一个新线程,并执行run()方法,我们通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。

如果看一些start的源代码就会更容易理解:

[java]  view plain  copy
 print ?
  1. public synchronized void start() {  
  2.         /** 
  3.      * This method is not invoked for the main method thread or "system" 
  4.      * group threads created/set up by the VM. Any new functionality added 
  5.      * to this method in the future may have to also be added to the VM. 
  6.      * 
  7.      * A zero status value corresponds to state "NEW". 
  8.          */  
  9.         if (threadStatus != 0 || this != me)  
  10.             throw new IllegalThreadStateException();  
  11.         group.add(this);  
  12.         start0();  
  13.         if (stopBeforeStart) {  
  14.         stop0(throwableFromStop);  
  15.     }  
  16. }  
  17. private native void start0();  

我们看最后,说明此处调用的start0(),这个方法用了native关键字。

2、通过实现Runnable接口

Thread本质上也是实现了Runnable接口的一个实例,如果自己的类已经extends另一个类,就无法直接继承thread类,必须实现一个Runnable接口。

我们在看一个小例子:

[java]  view plain  copy
 print ?
  1. package thread;  
  2.   
  3. public class runnable_2 implements Runnable{  
  4.       
  5.     private String name ;   
  6.     public runnable_2(){  
  7.           
  8.     }  
  9.   
  10.     public runnable_2(String name){  
  11.         this.name = name;  
  12.     }  
  13.     @Override  
  14.     public void run() {  
  15.         // TODO Auto-generated method stub  
  16.         for(int i =0;i <5 ;i++){  
  17.             System.out.println(name +"运行"+i);  
  18.         }  
  19.           
  20.     }  
  21.     public static void main(String[] args){  
  22.         runnable_2 h1 = new runnable_2("线程A");  
  23.         Thread demo = new Thread(h1);  
  24.         runnable_2 h2 = new runnable_2("线程B");  
  25.         Thread demo1 = new Thread(h2);  
  26.         demo.start();  
  27.         demo1.start();  
  28.     }  
  29.   
  30. }  

运行结果:

线程A运行0

线程B运行0

线程B运行1

线程A运行1

线程B运行2

线程A运行2

线程B运行3

线程A运行3

线程B运行4

线程A运行4


我们是选择thread 类还是实现runnable接口呢?

其实thread类也是实现Runnable接口的:

[java]  view plain  copy
 print ?
  1. class Thread implements Runnable {  
  2.     //…  
  3. public void run() {  
  4.         if (target != null) {  
  5.              target.run();  
  6.         }  
  7.         }  
  8. }  

Thread和Runnable的区别?

如果一个类继承Thread,则不适合资源共享,如果实现了Runnable接口,则很容易资源共享。


看下面的例子:

[java]  view plain  copy
 print ?
  1. package thread;  
  2. /** 
  3.  * 继承Thread类,不能资源共享 
  4.  * @author shangwei 
  5.  * 
  6.  */  
  7. public class thread_share_3 extends Thread{  
  8.         private int count = 5;  
  9.         public void run(){  
  10.             for(int i=0;i<10;i++){  
  11.                 if(count >0){  
  12.                     System.out.println(Thread.currentThread().getName()+"="+count--);  
  13.                 }  
  14.             }  
  15.         }  
  16.         public static void main(String[] args){  
  17.             thread_share_3 h1 = new thread_share_3();  
  18.             thread_share_3 h2 = new thread_share_3();  
  19.             thread_share_3 h3 = new thread_share_3();  
  20.             h1.start();  
  21.             h2.start();  
  22.             h3.start();  
  23.               
  24.         }  
  25.   
  26. }  



运行接口

Thread-1=5

Thread-3=5

Thread-2=5

Thread-3=4

Thread-1=4

Thread-3=3

Thread-2=4

Thread-2=3

Thread-3=2

Thread-1=3

Thread-3=1

Thread-2=2

Thread-1=2

Thread-1=1

Thread-2=1


我们把count看成是一个需要共享的资源,比如我们的抢票系统,系统里有5张票,3个人去抢票,实际上每个人都抢了5张票。一共15张票。



我们换成Runnale接口试一下:

[java]  view plain  copy
 print ?
  1. package thread;  
  2.   
  3. public class runnable_share_4 implements Runnable {  
  4.       
  5.     private int count = 5;  
  6.       
  7.     public static void main(String[] args){  
  8.         runnable_share_4 h1 = new runnable_share_4();  
  9.         Thread t1 = new Thread(h1,"1号窗口");  
  10.         t1.start();  
  11.         Thread t2 = new Thread(h1,"2号窗口");  
  12.         t2.start();  
  13.         Thread t3 = new Thread(h1,"3号窗口");  
  14.         t3.start();  
  15.           
  16.     }  
  17.   
  18.     @Override  
  19.     public void run() {  
  20.         for(int i=0;i<10;i++){  
  21.             if(count >0){  
  22.                 System.out.println("count"+"正在卖"+count--);  
  23.             }  
  24.         }  
  25.         // TODO Auto-generated method stub  
  26.           
  27.     }  
  28.   
  29. }  



运行结果:

count正在卖5

count正在卖3

count正在卖4

count正在卖1

count正在卖2



这里我们看到3个线程都共享了同一个实例,实现了资源的共享,3个线程抢5张票。
[java]  view plain  copy
 print ?
  1. 那么为什么Thread类不能共享同一个实例呢?我们试试呗。  
[java]  view plain  copy
 print ?
  1. </pre><pre code_snippet_id="1856515" snippet_file_name="blog_20160829_6_1221599" name="code" class="java" style="font-size: 14px;">package thread;  
  2.   
  3. public class Thread_1 extends Thread{  
  4.       
  5.     private String name;  
  6.   
  7.     public Thread_1(){  
  8.           
  9.     }  
  10.       
  11.     public Thread_1(String name){  
  12.         this.name = name ;   
  13.           
  14.     }  
  15.   
  16.     public void run(){  
  17.         for (int i = 0;i<5 ; i++){  
  18.             System.out.println(name + "运行" +i);  
  19.         }  
  20.     }  
  21.       
  22.     public static void main(String[] args){  
  23.         Thread_1 h1 = new Thread_1("A");  
  24.         Thread_1 h2 = new Thread_1("B");  
  25.         //h1.run();  
  26.         //h2.run();  
  27.         h1.start();  
  28.         h1.start();  
  29.           
  30.           
  31.     }  
  32. }  

在main方法里,我们对同一个实例都start()开启线程,看会出现什么情况呢?

运行结果:

A运行0Exception in thread "main" 

A运行1

A运行2

A运行3

A运行4

java.lang.IllegalThreadStateException

at java.lang.Thread.start(Thread.java:671)

at thread.Thread_1.main(Thread_1.java:28)



总结一下吧:

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。


线程调度

线程有5种基本状态:
1、新建状态(new):当创建线程对象后,就进入了新建状态,比如:Thread t  = new Thread();

2、 就绪状态(runnable):当线程对象被创建后,其他线程调用了该线程的start()方法,该线程就进入了队列,说明此线程做好了准备,随时等待cpu调度执行。

3、运行状态(running)当cpu时间片分给就绪状态的线程时,该线程就真正的执行起来,进入了运行状态。

4、阻塞状态(blocked)阻塞状态是因为线程因为某种原因放弃了cpu的使用权,暂时停止运行,直到线程进入就绪状态,才有机会运行。
阻塞状态分为3种情况:
等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。




处于阻塞状态的线程往往存在性能瓶颈,也是我们需要特别关注的。如果是因为竞争同步锁引发的死锁或者的响应时间的延长,需要找到这些处于blocked状态的线程在等待什么资源,通常情况下是数据库连接或者是日志的输出。
注:转自 http://blog.youkuaiyun.com/weiweicao0429/article/details/52353866
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值