做性能测试的同学使用最多的就是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;
public class Thread_1 extends Thread{
private String name;
public Thread_1(){
}
public Thread_1(String name){
this.name = name ;
}
public void run(){
for (int i = 0;i<5 ; i++){
System.out.println(name + "运行" +i);
}
}
public static void main(String[] args){
Thread_1 h1 = new Thread_1("A");
Thread_1 h2 = new Thread_1("B");
h1.run();
h2.run();
}
}
我们看一下运行结果:
A运行0
A运行1
A运行2
A运行3
A运行4
B运行0
B运行1
B运行2
B运行3
B运行4
如果看一些start的源代码就会更容易理解:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0 || this != me)
throw new IllegalThreadStateException();
group.add(this);
start0();
if (stopBeforeStart) {
stop0(throwableFromStop);
}
}
private native void start0();
2、通过实现Runnable接口
Thread本质上也是实现了Runnable接口的一个实例,如果自己的类已经extends另一个类,就无法直接继承thread类,必须实现一个Runnable接口。
我们在看一个小例子:
package thread;
public class runnable_2 implements Runnable{
private String name ;
public runnable_2(){
}
public runnable_2(String name){
this.name = name;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i =0;i <5 ;i++){
System.out.println(name +"运行"+i);
}
}
public static void main(String[] args){
runnable_2 h1 = new runnable_2("线程A");
Thread demo = new Thread(h1);
runnable_2 h2 = new runnable_2("线程B");
Thread demo1 = new Thread(h2);
demo.start();
demo1.start();
}
}
线程A运行0
线程B运行0
线程B运行1
线程A运行1
线程B运行2
线程A运行2
线程B运行3
线程A运行3
线程B运行4
线程A运行4
其实thread类也是实现Runnable接口的:
class Thread implements Runnable {
//…
public void run() {
if (target != null) {
target.run();
}
}
}
Thread和Runnable的区别?
如果一个类继承Thread,则不适合资源共享,如果实现了Runnable接口,则很容易资源共享。
看下面的例子:
package thread;
/**
* 继承Thread类,不能资源共享
* @author shangwei
*
*/
public class thread_share_3 extends Thread{
private int count = 5;
public void run(){
for(int i=0;i<10;i++){
if(count >0){
System.out.println(Thread.currentThread().getName()+"="+count--);
}
}
}
public static void main(String[] args){
thread_share_3 h1 = new thread_share_3();
thread_share_3 h2 = new thread_share_3();
thread_share_3 h3 = new thread_share_3();
h1.start();
h2.start();
h3.start();
}
}
运行接口
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张票。
package thread;
public class runnable_share_4 implements Runnable {
private int count = 5;
public static void main(String[] args){
runnable_share_4 h1 = new runnable_share_4();
Thread t1 = new Thread(h1,"1号窗口");
t1.start();
Thread t2 = new Thread(h1,"2号窗口");
t2.start();
Thread t3 = new Thread(h1,"3号窗口");
t3.start();
}
@Override
public void run() {
for(int i=0;i<10;i++){
if(count >0){
System.out.println("count"+"正在卖"+count--);
}
}
// TODO Auto-generated method stub
}
}
count正在卖5
count正在卖3
count正在卖4
count正在卖1
count正在卖2
那么为什么Thread类不能共享同一个实例呢?我们试试呗。
package thread; public class Thread_1 extends Thread{ private String name; public Thread_1(){ } public Thread_1(String name){ this.name = name ; } public void run(){ for (int i = 0;i<5 ; i++){ System.out.println(name + "运行" +i); } } public static void main(String[] args){ Thread_1 h1 = new Thread_1("A"); Thread_1 h2 = new Thread_1("B"); //h1.run(); //h2.run(); h1.start(); h1.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):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。