多线程的总结

 

Java多线程编程之使用多线程

标签: 多线程 线程
2624人阅读  评论(0)  收藏  举报
  分类:
多线程(11) 

目录(?)[+]

继承Thread类:

Java中,自带了对多线程技术的支持。继承Thread,还有实现Runnable接口。先看一下Thread类的结果,如下:

public class Thread implements Runnable

从上面的代码中发现,Thread类实现了Runnable接口,它们之间具有多态关系。

这种方法的局限是不支持多继承,因为Java是单继承的,所以为了多继承,完全可以实现Runnable接口的方法。

一边实现一边继承,但是这两种方法创建线程在工作时性质是一样的,没有本质区别。

[java]  view plain  copy
  1. public class MyThread extends Thread{  
  2.     @Override  
  3.     public void run() {  
  4.         super.run();  
  5.         System.out.println("MyThread");  
  6.     }  
  7. }  
  8.   
  9. public class Run {  
  10.     public static void main(String[] args) {  
  11.         MyThread myThread = new MyThread();  
  12.         myThread.start();  
  13.         System.out.println("运行结束!");  
  14.     }  
  15. }  

代码的运用结果:

从图结果看,MyThread类的run方法执行较晚,这也说明在使用多线程技术时,代码的执行结果与代码顺序或代码调用顺序无关的。

如果多次调用start()方法,则会出现java.lang.IllegalThreadStateException异常

上面介绍了线程的调用随机性,下面通过例子来演示线程的随机性:

创建自定义线程类MyThread.java,代码如下:

[java]  view plain  copy
  1. public class MyThread extends Thread{  
  2.     @Override  
  3.     public void run() {  
  4.         super.run();  
  5.         for(int i=0;i<10;i++){  
  6.             int time = (int)(Math.random()*1000);  
  7.             try {  
  8.                 Thread.sleep(time);  
  9.                 System.out.println("run="+Thread.currentThread().getName());  
  10.             } catch (InterruptedException e) {  
  11.                 e.printStackTrace();  
  12.             }  
  13.         }  
  14.     }  
  15. }  

再创建运行类:

[java]  view plain  copy
  1. public class Run {  
  2.     public static void main(String[] args) {  
  3.         MyThread myThread = new MyThread();  
  4.         myThread.setName("myThread");  
  5.         myThread.start();  
  6.         for(int i=0;i<10;i++){  
  7.             int time = (int)(Math.random()*1000);  
  8.             try {  
  9.                 Thread.sleep(time);  
  10.                 System.out.println("main="+Thread.currentThread().getName());  
  11.             } catch (InterruptedException e) {  
  12.                 e.printStackTrace();  
  13.             }  
  14.         }  
  15.     }  
  16. }  

在代码中,为了展现出线程具有随机性,所以使用随机数的形式来使用线程得到挂起效果,从而表现CPU执行哪个线程具有不确定性。

Thread类的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程具有异步执行的效果。如果调用代码thread.run()就不是异步执行,而是同步。那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完后才执行后面的代码。

以异步方式运行效果如下:



另外还需注意,执行start()方法的顺序并不代表线程启动的顺序。

线程类代码如下:

[java]  view plain  copy
  1. public class TheadT1 extends Thread{  
  2.       
  3.     private int i;  
  4.     public TheadT1(int i){  
  5.         this.i = i;  
  6.     }  
  7.       
  8.     public void run() {  
  9.         System.out.println(i);  
  10.     }  
  11. }  

执行类代码:

[java]  view plain  copy
  1. public class TheadT1Test {  
  2.     public static void main(String[] args) {  
  3.         TheadT1 t1 = new TheadT1(1);  
  4.         TheadT1 t2 = new TheadT1(2);  
  5.         TheadT1 t3 = new TheadT1(3);  
  6.         TheadT1 t4 = new TheadT1(4);  
  7.         TheadT1 t5 = new TheadT1(5);  
  8.         t1.start();  
  9.         t2.start();  
  10.         t3.start();  
  11.         t4.start();  
  12.         t5.start();  
  13.     }  
  14. }  

程序运用结果如下图:


实现Runnable接口:

如果欲创建的线程类已经有一个父类的话,那只能通过实现Runnable接口实现多线程功能。

创建一个实现Runnable接口的类,代码如下:

[java]  view plain  copy
  1. public class MyRunnable implements Runnable{  
  2.   
  3.     @Override  
  4.     public void run() {  
  5.         System.out.println("MyRunnable");  
  6.     }  
  7.   
  8. }  
  9. public class RunnableRun {  
  10.     public static void main(String[] args) {  
  11.         MyRunnable run = new MyRunnable();  
  12.         Thread t = new Thread(run);  
  13.         t.start();  
  14.         System.out.println("运用结束!");  
  15.     }  
  16.   
  17. }  

运用结果:

使用Thread类的方式来开发多线程应用程序在设计上是有局限性的,因为Java是单继承,所以为了改变这种限制,可以使用Runnbale接口的方式来实现多线程技术。


实例变量与线程安全:

自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间进行交互时是很重要的一个技术点。

(1)   不共享数据的情况:

通过下面一个示例查看数据不共享情况。


示例代码如下:

[java]  view plain  copy
  1. public class SharedIsNoThread extends Thread{  
  2.     private int count = 5;  
  3.       
  4.     public SharedIsNoThread(String name){  
  5.         this.setName(name);  
  6.     }  
  7.       
  8.     @Override  
  9.     public void run() {  
  10.         super.run();  
  11.         while(count>0){  
  12.             count--;  
  13.             System.out.println("由"+Thread.currentThread().getName()+"计算,count="+count);  
  14.         }  
  15.     }  
  16. }  
  17. public class SharedIsNoThreadTest {  
  18.   
  19.     public static void main(String[] args) {  
  20.         SharedIsNoThread t1 = new SharedIsNoThread("A");  
  21.         SharedIsNoThread t2 = new SharedIsNoThread("B");  
  22.         SharedIsNoThread t3 = new SharedIsNoThread("C");  
  23.         t1.start();  
  24.         t2.start();  
  25.         t3.start();  
  26.     }  
  27.   
  28. }  

运行结果如下图:


通过程序表明,一共创建了3个线程,每个线程都有各自的count变量,自己减少自己的count变量的值。这样的情况就是变量不共享,此示例并不存在多个线程访问同一个实例变量的情况。

(2)   共享数据的情况

共享数据的情况如下图所示:


共享数据的情况就是多个线程可以访问同一个变量,比如在实现投票功能时,多个线程可以同时处理同一个人的票数。

下面通过一个示例看一下数据共享情况:

[java]  view plain  copy
  1. public class SharedThread extends Thread{  
  2.       
  3.     private int count = 5;  
  4.       
  5.     @Override  
  6.     public void run() {  
  7.         super.run();  
  8.         while(count>0){  
  9.             try {  
  10.                 Thread.sleep(1000);  
  11.             } catch (InterruptedException e) {  
  12.                 e.printStackTrace();  
  13.             }  
  14.             count--;  
  15.             System.out.println("由"+Thread.currentThread().getName()+"计算,count="+count);  
  16.         }  
  17.     }  
  18. }  
  19.   
  20. public class SharedThreadTest {  
  21.   
  22.     public static void main(String[] args) {  
  23.         SharedThread sharedThread = new SharedThread();  
  24.         Thread t1 = new Thread(sharedThread, "A");  
  25.         Thread t2 = new Thread(sharedThread, "B");  
  26.         Thread t3 = new Thread(sharedThread, "C");  
  27.         t1.start();  
  28.         t2.start();  
  29.         t3.start();  
  30.     }  
  31. }  

休眠一秒,是为了情景更容易出现。

运行结果如下图:


从输出结果来看,出现了负数,产生了“非线程安全”问题。而我们想要的打印结果却是不重复的,而是依次递减并且最小等于0的。

在某些jvm中,i—的操作要分成如下3步:

(1)   取得原有i值。

(2)   计算i-1。

(3)   对i进行赋值。

而且在我们程序中还有while判断这一步。如果出现多个线程访问,那么一定会出现非线程安全问题。

其实这个示例就是典型的销售场景:3个销售员,每个销售员卖出一个货品后不可以得出相同的剩余数量,必须在每个销售员卖完一个货品后,其他销售人员才可以在新的剩余物品数上继续减1操作。这时候就需要使多个线程间进行同步,也就是按顺序的方式进行减1操作。更改代码如下:

[java]  view plain  copy
  1. public class SharedThread extends Thread{  
  2.       
  3.     private int count = 5;  
  4.       
  5.     @Override  
  6.     synchronized public void run() {  
  7.         super.run();  
  8.         while(count>0){  
  9.             try {  
  10.                 Thread.sleep(1000);  
  11.             } catch (InterruptedException e) {  
  12.                 e.printStackTrace();  
  13.             }  
  14.             count--;  
  15.             System.out.println("有"+Thread.currentThread().getName()+"计算,count="+count);  
  16.         }  
  17.     }  
  18. }  

重新执行程序,就不会出现负数和值一样的情况。

通过run方法前加入synchronized关键字,使多个线程执行run()方法时,以排队的方式进行。当一个线程调用run方法前,先判断run方法有没有被上锁,如上锁,说明有其他线程正在调用run方法,必须等其他线程对run方法调用结束才可以调用run方法。这样也就实现了排队调用run方法的目的,也就达到了按顺序对count变量减1的效果。synchronized关键字可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。

当一个线程想要执行同步代码块中代码时,线程首先尝试获取锁,如果能拿到这把锁,那么这个线程就能执行同步代码块中的代码。如果不能拿到这把锁,那么这个线程会不断尝试拿这把锁,直到能够拿到为止,而且是有多个线程同时争抢这把锁。流程如下图:

留意i--与System.out.println()的异常:

上面介绍解决非线程安全问题使用的是synchronized关键字,这里通过程序案例细化一下println()方法与i++联合使用时“有可能”出现的另外一种异常情况,并说明其中的原因。

示例代码如下:

[java]  view plain  copy
  1. public class MyThread extends Thread{  
  2.       
  3.     private int i = 5;  
  4.       
  5.     @Override  
  6.     public void run() {  
  7.         super.run();  
  8.         System.out.println("i="+(i--)+" threadName="+Thread.currentThread().getName());  
  9.     }  
  10. }  
  11. public class Run {  
  12.     public static void main(String[] args) {  
  13.         MyThread thread = new MyThread();  
  14.         Thread t1 = new Thread(thread);  
  15.         Thread t2 = new Thread(thread);  
  16.         Thread t3 = new Thread(thread);  
  17.         Thread t4 = new Thread(thread);  
  18.         Thread t5 = new Thread(thread);  
  19.         t1.start();  
  20.         t2.start();  
  21.         t3.start();  
  22.         t4.start();  
  23.         t5.start();  
  24.     }  
  25. }  

程序的执行结果:

本测试的用例是:虽然println()方法内部是同步的,但是i--的操作却是在进入该方法前执行的,所以有发生非线程安全的问题的概率。


所以,为了防止发生非线程安全的问题,还是需要继续使用同步方法。

currentThread()方法

currentThread() 方法可返回代码段正在被哪个线程调用的信息。下面通过一个示例进行说明。具体如下:

[java]  view plain  copy
  1. public class Run {  
  2.     public static void main(String[] args) {  
  3.         System.out.print(Thread.currentThread().getName());  
  4.     }  
  5. }  

运行结果:



结果说明,main方法被名为main的线程调用。

继续实验,创建如下代码:

[java]  view plain  copy
  1. public class MyThread extends Thread{  
  2.     public MyThread(){  
  3.         System.out.println("构造方法的打印:"+Thread.currentThread().getName());  
  4.     }  
  5.       
  6.     @Override  
  7.     public void run() {  
  8.         System.out.println("run方法的打印:"+Thread.currentThread().getName());  
  9.     }  
  10. }  
  11.   
  12. public class Run {  
  13.     public static void main(String[] args) {  
  14.         Thread myThread = new MyThread();  
  15.         myThread.start();  
  16.     }  
  17. }  

执行结果如下:


通过结果可以看出,MyThread类的构造方法是被main线程调用的,而run方法是被名称为Thread-0的线程调用的,run方法是自动调用的。

代码改为如下:

[java]  view plain  copy
  1. public class Run {  
  2.     public static void main(String[] args) {  
  3.         Thread myThread = new MyThread();  
  4.         //myThread.start();  
  5.         myThread.run();  
  6.     }  
  7. }  


在来测试一个比较复杂的,示例代码如下:

[java]  view plain  copy
  1. public class CountOperate extends Thread{  
  2.     public CountOperate(){  
  3.         System.out.println("----CountOperate begin----");  
  4.         System.out.println("CountOperate Thread.currentThread().getName:"+Thread.currentThread().getName());  
  5.         System.out.println("CountOperate this.getName:"+this.getName());  
  6.         System.out.println("----CountOperate end----");  
  7.     }  
  8.       
  9.     @Override  
  10.     public void run() {  
  11.         System.out.println("----run begin----");  
  12.         System.out.println("run Thread.currentThread().getName:"+Thread.currentThread().getName());  
  13.         System.out.println("run this.getName:"+this.getName());  
  14.         System.out.println("----run end----");  
  15.     }  
  16. }  
  17.   
  18. public class Run {  
  19.     public static void main(String[] args) {  
  20.         CountOperate c = new CountOperate();  
  21.         Thread t1 = new Thread(c,"A");  
  22.         System.out.println(t1);  
  23.         t1.start();  
  24.     }  
  25. }  
运行结果如下:


getName()和Thread.currentThread().getName()返回的值不同,你可以修改代码如下:

[java]  view plain  copy
  1. public class CountOperate extends Thread{  
  2.     public CountOperate(String name){  
  3.         super(name);  
  4.         System.out.println("----CountOperate begin----");  
  5.         System.out.println("CountOperate Thread.currentThread().getName:"+Thread.currentThread().getName());  
  6.         System.out.println("CountOperate this.getName:"+this.getName());  
  7.         System.out.println("----CountOperate end----");  
  8.     }  
  9.   
  10. public class RunCountOperate {  
  11.     public static void main(String[] args) {  
  12.         CountOperate countOperate = new CountOperate("M");  
  13.         countOperate.setName("N");  
  14.         Thread t1 = new Thread(countOperate);  
  15.         t1.setName("A");  
  16.         t1.start();  
  17.     }  
  18. }  

执行结果如下:


通过这个结果可以看出:this.getName()方法是指当前对象的线程名称,而Thread.currentThread.getName()方法表示正在运行的线程的名称。

isAlive()方法:

isAlive()方法的功能是判断当前线程是否处于活动状态。

创建示例代码,如下:

[java]  view plain  copy
  1. public class MyThread extends Thread{  
  2.     @Override  
  3.     public void run() {  
  4.         System.out.println("---run:"+this.isAlive()+"---");  
  5.     }  
  6. }  
  7.   
  8. public class Run {  
  9.     public static void main(String[] args) {  
  10.         MyThread m = new MyThread();  
  11.         System.out.println("begin="+m.isAlive());  
  12.         m.start();  
  13.         System.out.println("end="+m.isAlive());  
  14.     }  
  15. }  

程序运行结果如下:



方法isAlive()的作用是测试线程是否处于活动状态。什么是活动状态?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始的状态,就认为线程是存活的。

需要说明,如下代码:

[java]  view plain  copy
  1. System.out.println("end="+m.isAlive());  

虽然上面示例打印的结果是true,但此值是不确定的。打印true是因为MyThread线程还没有执行完毕,所以输出true。如果代码改为如下:

[java]  view plain  copy
  1. public class Run {  
  2.     public static void main(String[] args) throws InterruptedException {  
  3.         MyThread m = new MyThread();  
  4.         System.out.println("begin="+m.isAlive());  
  5.         m.start();  
  6.         //线程休息一秒  
  7.         Thread.sleep(1000);  
  8.         System.out.println("end="+m.isAlive());  
  9.     }  
  10. }  

执行结果:



上述代码输出为false,因为MyThread线程在1秒钟内执行完毕了。

另外,在使用isAlive()方式时,如果将线程对象以构造参数的形式传递给Thread对象进行start()启动时,运行的结果和前面是有差异的。造成这样的差异原因是Thread.currentThread()和this的区别。下面通过代码测试一下:

[java]  view plain  copy
  1. public class CountOperate extends Thread{  
  2.     public CountOperate(String name){  
  3.         super(name);  
  4.         System.out.println("----CountOperate begin----");  
  5.         System.out.println("CountOperate Thread.currentThread().getName:"+Thread.currentThread().getName());  
  6.         System.out.println("CountOperate Thread.currentThread().isAlive:"+Thread.currentThread().isAlive());  
  7.         System.out.println("CountOperate this.getName:"+this.getName());  
  8.         System.out.println("CountOperate this.getName:"+this.isAlive());  
  9.         System.out.println("----CountOperate end----");  
  10.     }  
  11.       
  12.     @Override  
  13.     public void run() {  
  14.         System.out.println("----run begin----");  
  15.         System.out.println("run Thread.currentThread().getName:"+Thread.currentThread().getName());  
  16.         System.out.println("run Thread.currentThread().isAlive:"+Thread.currentThread().isAlive());  
  17.         System.out.println("run this.getName:"+this.getName());  
  18.         System.out.println("run this.isAlive:"+this.isAlive());  
  19.         System.out.println("----run end----");  
  20.     }  
  21. }  
  22.   
  23. public class RunCountOperate {  
  24.     public static void main(String[] args) {  
  25.         CountOperate countOperate = new CountOperate("M");  
  26.         countOperate.setName("N");  
  27.         Thread t1 = new Thread(countOperate);  
  28.         System.out.println("main begin t1 isAlive="+t1.isAlive());  
  29.         t1.setName("A");  
  30.         t1.start();  
  31.         System.out.println("main end t1 isAlive="+t1.isAlive());  
  32.     }  
  33. }  

执行结果如下:

sleep()方法

方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。

通过一个示例说明,如下:

[java]  view plain  copy
  1. public class MyThread1 extends Thread{  
  2.     @Override  
  3.     public void run() {  
  4.         System.out.println("run threadName="+this.currentThread().getName()+" begin");  
  5.           
  6.         try {  
  7.             Thread.sleep(2000);  
  8.         } catch (InterruptedException e) {  
  9.             e.printStackTrace();  
  10.         }  
  11.           
  12.         System.out.println("run threadName="+this.currentThread().getName()+" end");  
  13.     }  
  14. }  
  15.   
  16. public class Run1 {  
  17.   
  18.     public static void main(String[] args) {  
  19.         MyThread1 t1 = new MyThread1();  
  20.         System.out.println("begin="+System.currentTimeMillis());  
  21.         t1.run();  
  22.         System.out.println("end="+System.currentTimeMillis());  
  23.     }  
  24. }  

运行代码结果如下:


继续修改代码,如下:

[java]  view plain  copy
  1. public class MyThread2 extends Thread{  
  2.     @Override  
  3.     public void run() {  
  4.         System.out.println("run threadName="+this.currentThread().getName()+" begin="+System.currentTimeMillis());  
  5.         try {  
  6.             Thread.sleep(2000);  
  7.         } catch (InterruptedException e) {  
  8.             e.printStackTrace();  
  9.         }  
  10.         System.out.println("run threadName="+this.currentThread().getName()+" end="+System.currentTimeMillis());  
  11.   
  12.     }  
  13. }  
  14.   
  15. public class Run2 {  
  16.     public static void main(String[] args) {  
  17.         MyThread2 t2 = new MyThread2();  
  18.         System.out.println("begin="+System.currentTimeMillis());  
  19.         t2.start();  
  20.         System.out.println("end="+System.currentTimeMillis());  
  21.     }  
  22. }  

运行结果如下:

由于main线程与Thread2线程是异步执行的,所以首先打印的信息begin和end。而MyThread2稍后执行,在最后两行打印run begin 和 run end相关的信息。

getId()方法

getId()方法的作用是取得线程的唯一标识。、

创建示例代码如下:


[java]  view plain  copy
  1. public class Run1 {  
  2.     public static void main(String[] args) {  
  3.         Thread thread = Thread.currentThread();  
  4.         System.out.println(thread.getName()+" "+thread.getId());  
  5.     }  
  6. }  

程序运行结果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值