java多线程总结

java中的多线程

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

对于直接继承Thread的类来说,代码大致框架是:

123456789101112class类名 extendsThread{方法1;方法2publicvoidrun(){// other code…}属性1属性2}

先看一个简单的例子:

12345678910111213141516171819202122232425262728/*** @author Rollen-Holt 继承Thread类,直接调用run方法* */classhello extendsThread {publichello() {}publichello(String name) {this.name = name;}publicvoidrun() {for(inti =0; i < 5; i++) {System.out.println(name + "运行 "+ i);}}publicstaticvoidmain(String[] args) {hello h1=newhello("A");hello h2=newhello("B");h1.run();h2.run();}privateString name;}

【运行结果】:

A运行 0

A运行 1

A运行 2

A运行 3

A运行 4

B运行 0

B运行 1

B运行 2

B运行 3

B运行 4

我们会发现这些都是顺序执行的,说明我们的调用方法不对,应该调用的是start()方法。

当我们把上面的主函数修改为如下所示的时候:

123456publicstaticvoidmain(String[] args) {hello h1=newhello("A");hello h2=newhello("B");h1.start();h2.start();}

然后运行程序,输出的可能的结果如下:

A运行 0

B运行 0

B运行 1

B运行 2

B运行 3

B运行 4

A运行 1

A运行 2

A运行 3

A运行 4

因为需要用到CPU的资源,所以每次的运行结果基本是都不一样的,呵呵。

注意:虽然我们在这里调用的是start()方法,但是实际上调用的还是run()方法的主体。

那么:为什么我们不能直接调用run()方法呢?

我的理解是:线程的运行需要本地操作系统的支持。

如果你查看start的源代码的时候,会发现:

1234567891011121314151617publicsynchronizedvoidstart() {/*** 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)thrownewIllegalThreadStateException();group.add(this);start0();if(stopBeforeStart) {stop0(throwableFromStop);}}privatenativevoidstart0();

注意我用红色加粗的那一条语句,说明此处调用的是start0()。并且这个这个方法用了native关键字,次关键字表示调用本地操作系统的函数。因为多线程的实现需要本地操作系统的支持。

但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

通过实现Runnable接口:

大致框架是:

123456789101112class类名 implementsRunnable{方法1;方法2publicvoidrun(){// other code…}属性1属性2}

来先看一个小例子吧:

123456789101112131415161718192021222324252627282930/*** @author Rollen-Holt 实现Runnable接口* */classhello implementsRunnable {publichello() {}publichello(String name) {this.name = name;}publicvoidrun() {for(inti =0; i < 5; i++) {System.out.println(name + "运行 "+ i);}}publicstaticvoidmain(String[] args) {hello h1=newhello("线程A");Thread demo= newThread(h1);hello h2=newhello("线程B");Thread demo1=newThread(h2);demo.start();demo1.start();}privateString name;}

【可能的运行结果】:

线程A运行 0

线程B运行 0

线程B运行 1

线程B运行 2

线程B运行 3

线程B运行 4

线程A运行 1

线程A运行 2

线程A运行 3

线程A运行 4

关于选择继承Thread还是实现Runnable接口?

其实Thread也是实现Runnable接口的:

12345678classThread implementsRunnable {//…publicvoidrun() {if(target !=null) {target.run();}}}

其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。关于代理模式,我曾经写过一个小例子呵呵,大家有兴趣的话可以看一下:http://www.cnblogs.com/rollenholt/archive/2011/08/18/2144847.html

Thread和Runnable的区别:

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

1234567891011121314151617181920212223/*** @author Rollen-Holt 继承Thread类,不能资源共享* */classhello extendsThread {publicvoidrun() {for(inti =0; i < 7; i++) {if(count >0) {System.out.println("count= "+ count--);}}}publicstaticvoidmain(String[] args) {hello h1 = newhello();hello h2 = newhello();hello h3 = newhello();h1.start();h2.start();h3.start();}privateintcount =5;}

【运行结果】:

count= 5

count= 4

count= 3

count= 2

count= 1

count= 5

count= 4

count= 3

count= 2

count= 1

count= 5

count= 4

count= 3

count= 2

count= 1

大家可以想象,如果这个是一个买票系统的话,如果count表示的是车票的数量的话,说明并没有实现资源的共享。

我们换为Runnable接口

123456789101112131415161718192021classMyThread implementsRunnable{privateintticket =5; //5张票publicvoidrun() {for(inti=0; i<=20; i++) {if(this.ticket >0) {System.out.println(Thread.currentThread().getName()+"正在卖票"+this.ticket--);}}}}publicclasslzwCode {publicstaticvoidmain(String [] args) {MyThread my = newMyThread();newThread(my,"1号窗口").start();newThread(my,"2号窗口").start();newThread(my,"3号窗口").start();}}

  

【运行结果】:

count= 5

count= 4

count= 3

count= 2

count= 1

总结一下吧:

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

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

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

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

所以,本人建议大家劲量实现接口。

123456789101112131415161718/*** @author Rollen-Holt * 取得线程的名称* */classhello implementsRunnable {publicvoidrun() {for(inti =0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}publicstaticvoidmain(String[] args) {hello he = newhello();newThread(he,"A").start();newThread(he,"B").start();newThread(he).start();}}

【运行结果】:

A

A

A

B

B

B

Thread-0

Thread-0

Thread-0

说明如果我们没有指定名字的话,系统自动提供名字。

提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。

判断线程是否启动

123456789101112131415161718/*** @author Rollen-Holt 判断线程是否启动* */classhello implementsRunnable {publicvoidrun() {for(inti =0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}publicstaticvoidmain(String[] args) {hello he = newhello();Thread demo = newThread(he);System.out.println("线程启动之前---》"+ demo.isAlive());demo.start();System.out.println("线程启动之后---》"+ demo.isAlive());}}

【运行结果】

线程启动之前—》false

线程启动之后—》true

Thread-0

Thread-0

Thread-0

主线程也有可能在子线程结束之前结束。并且子线程不受影响,不会因为主线程的结束而结束。

线程的强制执行:

1234567891011121314151617181920212223242526/*** @author Rollen-Holt 线程的强制执行* */classhelloimplementsRunnable {publicvoidrun() {for(inti =0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}publicstaticvoidmain(String[] args) {hello he = newhello();Thread demo = newThread(he,"线程");demo.start();for(inti=0;i<50;++i){if(i>10){try{demo.join(); //强制执行demo}catch(Exception e) {e.printStackTrace();}}System.out.println("main 线程执行-->"+i);}}}

【运行的结果】:

main线程执行–>0

main线程执行–>1

main线程执行–>2

main线程执行–>3

main线程执行–>4

main线程执行–>5

main线程执行–>6

main线程执行–>7

main线程执行–>8

main线程执行–>9

main线程执行–>10

线程

线程

线程

main线程执行–>11

main线程执行–>12

main线程执行–>13

...

线程的休眠:

123456789101112131415161718192021/*** @author Rollen-Holt 线程的休眠* */classhello implementsRunnable {publicvoidrun() {for(inti =0; i < 3; i++) {try{Thread.sleep(2000);} catch(Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + i);}}publicstaticvoidmain(String[] args) {hello he = newhello();Thread demo = newThread(he,"线程");demo.start();}}

【运行结果】:(结果每隔2s输出一个)

线程0

线程1

线程2

线程的中断:

12345678910111213141516171819202122232425262728/*** @author Rollen-Holt 线程的中断* */classhello implementsRunnable {publicvoidrun() {System.out.println("执行run方法");try{Thread.sleep(10000);System.out.println("线程完成休眠");} catch(Exception e) {System.out.println("休眠被打断");return;//返回到程序的调用处}System.out.println("线程正常终止");}publicstaticvoidmain(String[] args) {hello he = newhello();Thread demo = newThread(he,"线程");demo.start();try{Thread.sleep(2000);}catch(Exception e) {e.printStackTrace();}demo.interrupt(); //2s后中断线程}}

【运行结果】:

执行run方法

休眠被打断

在java程序中,只要前台有一个线程在运行,整个java程序进程不会小时,所以此时可以设置一个后台线程,这样即使java进程小时了,此后台线程依然能够继续运行。

1234567891011121314151617/*** @author Rollen-Holt 后台线程* */classhello implementsRunnable {publicvoidrun() {while(true) {System.out.println(Thread.currentThread().getName() +"在运行");}}publicstaticvoidmain(String[] args) {hello he = newhello();Thread demo = newThread(he,"线程");demo.setDaemon(true);demo.start();}}

虽然有一个死循环,但是程序还是可以执行完的。因为在死循环中的线程操作已经设置为后台运行了。

线程的优先级:

1234567891011121314151617181920212223/*** @author Rollen-Holt 线程的优先级* */classhello implementsRunnable {publicvoidrun() {for(inti=0;i<5;++i){System.out.println(Thread.currentThread().getName()+"运行"+i);}}publicstaticvoidmain(String[] args) {Thread h1=newThread(newhello(),"A");Thread h2=newThread(newhello(),"B");Thread h3=newThread(newhello(),"C");h1.setPriority(8);h2.setPriority(2);h3.setPriority(6);h1.start();h2.start();h3.start();}}

【运行结果】:

A运行0

A运行1

A运行2

A运行3

A运行4

B运行0

C运行0

C运行1

C运行2

C运行3

C运行4

B运行1

B运行2

B运行3

B运行4

。但是请读者不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源、

另外,主线程的优先级是5.

线程的礼让。

在线程操作中,也可以使用yield()方法,将一个线程的操作暂时交给其他线程执行。

12345678910111213141516171819202122/*** @author Rollen-Holt 线程的优先级* */classhello implementsRunnable {publicvoidrun() {for(inti=0;i<5;++i){System.out.println(Thread.currentThread().getName()+"运行"+i);if(i==3){System.out.println("线程的礼让");Thread.currentThread().yield();}}}publicstaticvoidmain(String[] args) {Thread h1=newThread(newhello(),"A");Thread h2=newThread(newhello(),"B");h1.start();h2.start();}}

A运行0

A运行1

A运行2

A运行3

线程的礼让

A运行4

B运行0

B运行1

B运行2

B运行3

线程的礼让

B运行4

同步和死锁:

【问题引出】:比如说对于买票系统,有下面的代码:

12345678910111213141516171819202122232425262728/*** @author Rollen-Holt * */classhello implementsRunnable {publicvoidrun() {for(inti=0;i<10;++i){if(count>0){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(count--);}}}publicstaticvoidmain(String[] args) {hello he=newhello();Thread h1=newThread(he);Thread h2=newThread(he);Thread h3=newThread(he);h1.start();h2.start();h3.start();}privateintcount=5;}

【运行结果】:

5

4

3

2

1

0

-1

这里出现了-1,显然这个是错的。,应该票数不能为负值。

如果想解决这种问题,就需要使用同步。所谓同步就是在统一时间段中只有有一个线程运行,

其他的线程必须等到这个线程结束之后才能继续执行。

【使用线程同步解决问题】

采用同步的话,可以使用同步代码块和同步方法两种来完成。

【同步代码块】:

语法格式:

synchronized(同步对象){

//需要同步的代码

}

但是一般都把当前对象this作为同步对象。

比如对于上面的买票的问题,如下:

123456789101112131415161718192021222324252627282930/*** @author Rollen-Holt * */classhello implementsRunnable {publicvoidrun() {for(inti=0;i<10;++i){synchronized(this) {if(count>0){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(count--);}}}}publicstaticvoidmain(String[] args) {hello he=newhello();Thread h1=newThread(he);Thread h2=newThread(he);Thread h3=newThread(he);h1.start();h2.start();h3.start();}privateintcount=5;}

【运行结果】:(每一秒输出一个结果)

5

4

3

2

1

【同步方法】

也可以采用同步方法。

语法格式为synchronized方法返回类型方法名(参数列表){

//其他代码

}

现在,我们采用同步方法解决上面的问题。

123456789101112131415161718192021222324252627282930313233/*** @author Rollen-Holt* */classhello implementsRunnable {publicvoidrun() {for(inti =0; i < 10; ++i) {sale();}}publicsynchronizedvoidsale() {if(count >0) {try{Thread.sleep(1000);} catch(InterruptedException e) {e.printStackTrace();}System.out.println(count--);}}publicstaticvoidmain(String[] args) {hello he = newhello();Thread h1 = newThread(he);Thread h2 = newThread(he);Thread h3 = newThread(he);h1.start();h2.start();h3.start();}privateintcount =5;}

【运行结果】(每秒输出一个)

5

4

3

2

1

提醒一下,当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁。

此处列举经典的生产者和消费者问题。

【生产者和消费者问题】

先看一段有问题的代码。

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889classInfo {publicString getName() {returnname;}publicvoidsetName(String name) {this.name = name;}publicintgetAge() {returnage;}publicvoidsetAge(intage) {this.age = age;}privateString name ="Rollen";privateintage =20;}/*** 生产者* */classProducer implementsRunnable{privateInfo info=null;Producer(Info info){this.info=info;}publicvoidrun(){booleanflag=false;for(inti=0;i<25;++i){if(flag){this.info.setName("Rollen");try{Thread.sleep(100);}catch(Exception e) {e.printStackTrace();}this.info.setAge(20);flag=false;}else{this.info.setName("chunGe");try{Thread.sleep(100);}catch(Exception e) {e.printStackTrace();}this.info.setAge(100);flag=true;}}}}/*** 消费者类* */classConsumer implementsRunnable{privateInfo info=null;publicConsumer(Info info){this.info=info;}publicvoidrun(){for(inti=0;i<25;++i){try{Thread.sleep(100);}catch(Exception e) {e.printStackTrace();}System.out.println(this.info.getName()+"<---->"+this.info.getAge());}}}/*** 测试类* */classhello{publicstaticvoidmain(String[] args) {Info info=newInfo();Producer pro=newProducer(info);Consumer con=newConsumer(info);newThread(pro).start();newThread(con).start();}}

【运行结果】:

Rollen<—->100

chunGe<—->20

chunGe<—->100

Rollen<—->100

chunGe<—->20

Rollen<—->100

Rollen<—->100

Rollen<—->100

chunGe<—->20

chunGe<—->20

chunGe<—->20

Rollen<—->100

chunGe<—->20

Rollen<—->100

chunGe<—->20

Rollen<—->100

chunGe<—->20

Rollen<—->100

chunGe<—->20

Rollen<—->100

chunGe<—->20

Rollen<—->100

chunGe<—->20

Rollen<—->100

chunGe<—->20

大家可以从结果中看到,名字和年龄并没有对于。

那么如何解决呢?

1)加入同步

2)加入等待和唤醒

先来看看加入同步会是如何。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899classInfo {publicString getName() {returnname;}publicvoidsetName(String name) {this.name = name;}publicintgetAge() {returnage;}publicvoidsetAge(intage) {this.age = age;}publicsynchronizedvoidset(String name,intage){this.name=name;try{Thread.sleep(100);}catch(Exception e) {e.printStackTrace();}this.age=age;}publicsynchronizedvoidget(){try{Thread.sleep(100);}catch(Exception e) {e.printStackTrace();}System.out.println(this.getName()+"<===>"+this.getAge());}privateString name ="Rollen";privateintage =20;}/*** 生产者* */classProducer implementsRunnable {privateInfo info =null;Producer(Info info) {this.info = info;}publicvoidrun() {booleanflag =false;for(inti =0; i < 25; ++i) {if(flag) {this.info.set("Rollen",20);flag = false;} else{this.info.set("ChunGe",100);flag = true;}}}}/*** 消费者类* */classConsumer implementsRunnable {privateInfo info =null;publicConsumer(Info info) {this.info = info;}publicvoidrun() {for(inti =0; i < 25; ++i) {try{Thread.sleep(100);} catch(Exception e) {e.printStackTrace();}this.info.get();}}}/*** 测试类* */classhello {publicstaticvoidmain(String[] args) {Info info = newInfo();Producer pro = newProducer(info);Consumer con = newConsumer(info);newThread(pro).start();newThread(con).start();}}

【运行结果】:

Rollen<===>20

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

Rollen<===>20

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

从运行结果来看,错乱的问题解决了,现在是Rollen对应20,ChunGe对于100

,但是还是出现了重复读取的问题,也肯定有重复覆盖的问题。如果想解决这个问题,就需要使用Object类帮忙了、

,我们可以使用其中的等待和唤醒操作。

要完成上面的功能,我们只需要修改Info类饥渴,在其中加上标志位,并且通过判断标志位完成等待和唤醒的操作,代码如下:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119classInfo {publicString getName() {returnname;}publicvoidsetName(String name) {this.name = name;}publicintgetAge() {returnage;}publicvoidsetAge(intage) {this.age = age;}publicsynchronizedvoidset(String name,intage){if(!flag){try{super.wait();}catch(Exception e) {e.printStackTrace();}}this.name=name;try{Thread.sleep(100);}catch(Exception e) {e.printStackTrace();}this.age=age;flag=false;super.notify();}publicsynchronizedvoidget(){if(flag){try{super.wait();}catch(Exception e) {e.printStackTrace();}}try{Thread.sleep(100);}catch(Exception e) {e.printStackTrace();}System.out.println(this.getName()+"<===>"+this.getAge());flag=true;super.notify();}privateString name ="Rollen";privateintage =20;privatebooleanflag=false;}/*** 生产者* */classProducer implementsRunnable {privateInfo info =null;Producer(Info info) {this.info = info;}publicvoidrun() {booleanflag =false;for(inti =0; i < 25; ++i) {if(flag) {this.info.set("Rollen",20);flag = false;} else{this.info.set("ChunGe",100);flag = true;}}}}/*** 消费者类* */classConsumer implementsRunnable {privateInfo info =null;publicConsumer(Info info) {this.info = info;}publicvoidrun() {for(inti =0; i < 25; ++i) {try{Thread.sleep(100);} catch(Exception e) {e.printStackTrace();}this.info.get();}}}/*** 测试类* */classhello {publicstaticvoidmain(String[] args) {Info info = newInfo();Producer pro = newProducer(info);Consumer con = newConsumer(info);newThread(pro).start();newThread(con).start();}} 123456789101112131415161718192021222324252627【程序运行结果】:Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20ChunGe<===>100Rollen<===>20先在看结果就可以知道,之前的问题完全解决。

《完》

PS(写在后面):

本人深知学的太差,所以希望大家能多多指点。另外,关于多线程其实有很多的知识,由于目前我也就知道的不太多,写了一些常用的。虽然在操作系统这门课上学了很多的线程和进程,比如银行家算法等等的,以后有时间在补充,大家有什么好资料可以留个言,大家一起分享一下,谢谢了。

谦受益,满招损。

java多线程总结

相关文章:

你感兴趣的文章:

标签云: