谈谈java多线程(一)

      其实很早就想写一些java多线程方面的文章,只是奈何这东西看着简单,但要真要理解和正确使用可能还需要花费一定的精力,虽然平时的工作中会用到这方面的知识,可是更多的只是为了完成工作,至于详细的东西,也没有深入思考过。当然了网上关于这方面的知识很多,可是大部分都是东拼西凑,到处复制黏贴,虽然也有不少好的文章,但是还是那句老话,“纸上得来终觉浅,绝知此事要躬行”,因此就有了这个系列的文章。

     说起多线程,首先就要说说线程, 这里小编不会在解释一些理论性的东西,我觉得程序员用代码说话最简单,下面是java中两种构造线程的方法,具体的一些解释会在代码注释中详细列出,首先我们会列举出两种实现线程的方法,具体直接看代码:

    

 1 package com.liuyang.junit.test.thread;
 2 
 3 /**
 4  * demo1主要是用继承Thread类实现构造线程,在这里我们需要重写run方法
 5  * 来实现我们具体的逻辑
 6  * @author 刘洋
 7  */
 8 public class ThreadDemo1 extends Thread {
 9 
10     @Override
11     public void run() {
12         super.run();
13         System.out.println("当前正在运行的线程是:" + Thread.currentThread().getName());
14     }
15     
16     public static void main(String[] args) {
17         
18         //构造线程对象
19         ThreadDemo1 t1 = new ThreadDemo1();
20         //启动线程
21         t1.start();
22         //主线程输出
23         System.out.println("我是的demo1主线程");
24     }
25 }
26 
27 /**
28  * 这种方式是通过实现Runnable接口来实现多线程
29  * 
30  */
31 class ThreadDemo2 implements Runnable{
32 
33     @Override
34     public void run() {
35         System.out.println("当前正在运行的线程是:" + Thread.currentThread().getName());
36     }
37     
38     public static void main(String[] args) {
39         //构造接口对象,后面会用到
40         ThreadDemo2 demo2 = new ThreadDemo2();
41         /**
42          * 构造线程对象,变化点主要在这里,我们可以通过一个实现了runnable接口的对象
43          * 来构造线程,而且这种方式还可以直接给线程命名
44          */
45         Thread t1 = new Thread(demo2,"线程1");
46         //启动线程
47         t1.start();
48         //同样给主线程一个输出
49         System.out.println("我是的demo2主线程");
50         
51     }
52 }

       总结来说,有两种方式来实现线程对象的构造,当然一般采用Runnable接口线程有一定的好处,除了代码中我们看到的可以给线程命名外,采用接口编程的好处也是我们应该首选的原因。

       其次我们知道线程的调度是由CPU决定的(在没有优先级的情况下),所以上面的代码在执行多次情况下会出现不同的结果,为了让大家拿到代码就能用,我自己也试了下(但是如果只是上述的代码,这中效果不明显),为了让效果显示的比较明显,可以再主线程中增加一个简答的循环打印(如果了解线程sleep的话也不失为一种很好的解决方法),这样效果会很明显,直接上代码:

    

 1 /**
 2  * 这种方式是通过实现Runnable接口来实现多线程
 3  * 
 4  */
 5 class ThreadDemo2 implements Runnable{
 6 
 7     @Override
 8     public void run() {
 9         System.out.println("当前正在运行的线程是:" + Thread.currentThread().getName());
10     }
11     
12     public static void main(String[] args) {
13         //构造接口对象,后面会用到
14         ThreadDemo2 demo2 = new ThreadDemo2();
15         /**
16          * 构造线程对象,变化点主要在这里,我们可以通过一个实现了runnable接口的对象
17          * 来构造线程,而且这种方式还可以直接给线程命名
18          */
19         Thread t1 = new Thread(demo2,"线程1");
20         //启动线程
21         t1.start();
22         //主线程增加一个打印输出,这样在执行多次情况下会有不同的结果显示,充分证明了CPU的调度能力
23         for (int i = 0; i < 10; i++) {
24             System.out.println(i);
25         }
26         //同样给主线程一个输出
27         System.out.println("我是的demo2主线程");
28     }
29 }

 

     当然对于解决刚才CPU调度效果的展示问题,我们也可以使用线程的休眠方法,也就是sleep方法,下面我们一起来看看sleep的用法,接着上面的代码,我们俩看看sleep的用法:

 1 /**
 2  * 这种方式是通过实现Runnable接口来实现多线程
 3  * 
 4  */
 5 class ThreadDemo2 implements Runnable{
 6 
 7     @Override
 8     public void run() {
 9         //当前实现该runnable接口的线程休眠200ms
10         try {
11             Thread.sleep(200);
12         } catch (InterruptedException e) {
13             // TODO Auto-generated catch block
14             e.printStackTrace();
15         }
16         System.out.println("当前正在运行的线程是:" + Thread.currentThread().getName());
17     }
18     
19     public static void main(String[] args) {
20         //构造接口对象,后面会用到
21         ThreadDemo2 demo2 = new ThreadDemo2();
22         /**
23          * 构造线程对象,变化点主要在这里,我们可以通过一个实现了runnable接口的对象
24          * 来构造线程,而且这种方式还可以直接给线程命名
25          */
26         Thread t1 = new Thread(demo2,"线程1");
27         //启动线程
28         t1.start();
29         //主线程休眠200ms
30         try {
31             Thread.sleep(200);
32         } catch (InterruptedException e) {
33             e.printStackTrace();
34         }
35         //主线程增加一个打印输出,这样在执行多次情况下会有不同的结果显示,充分证明了CPU的调度能力
36         for (int i = 0; i < 10; i++) {
37             System.out.println(i);
38         }
39         //同样给主线程一个输出
40         System.out.println("我是的demo2主线程");
41     }
42 }

 

 

       说了这么多线程知识了,应该进入我们今天的主题多线程,其实上述两个例子,本身是一个多线程例子(一个是main主线程和一个子线程 线程1),下面我们通过代码来看看多线程到底能干啥,有哪些实际的作用,以及多线程带来的一些问题。

        首先说说多线程能干啥,说白了就是想最大限度的利用CPU资源,把以前单一串行的事情,通过多个线程实现并发(看清楚不是并行,当然现在计算机核心数已不再是传统的单核处理器,实现并行也是没有问题),就比如餐厅服务员这件事,以前呢老板给每一桌客人安排一个服务员,来负责这桌客人的所有服务,但是一个月下来老板发现者虽然客人多了,但是服务员的工资也不少,关键是这么多服务员在服务过程中还经常会出现无事可做的时候,这让作为一个资本家的老板很是头疼,老板经过仔细的思考后,决定裁员,由以前的一个人服务一桌,到现在一个人服务五桌,这样的安排之后老板发现即没有因为服务员人数的减少,而给顾客带来不便,因为每一个顾客也不一定都是同时来的(就算同时来了,稍微等几分钟也是可以的),所以当第一位客人点完餐后,第二位客人来了,这时候服务员刚好直接又去服务第二位客人,这样服务员的工作很饱和,而且成本也降低了。

        下面就写一个简单的例子,主要是看看多线程的使用,顺便引出我们经常性关注的问题,线程安全问题。

 1 package com.liuyang.junit.test.thread;
 2 
 3 /**
 4  * 使用多线程模拟抢票问题
 5  * 
 6  * @author 刘洋
 7  * @since 2018-08-19
 8  */
 9 public class Web12306 implements Runnable {
10 
11     /**
12      * 模拟50张票
13      */
14     private static int COUNT = 50;
15     
16     @Override
17     public void run() {
18         while(COUNT > 0){
19             System.out.println(Thread.currentThread().getName() + "抢到第" + COUNT-- + "张票");
20         }
21     }
22 
23     public static void main(String[] args) {
24         Web12306 web = new Web12306();
25         //启动三个线程来抢票
26         Thread t1 = new Thread(web,"甲");
27         Thread t2 = new Thread(web,"已");
28         Thread t3 = new Thread(web,"丙");
29         t1.start();
30         t2.start();
31         t3.start();
32     }
33 }

 

          我们看看来执行代码情况,

甲抢到第50张票
已抢到第48张票
丙抢到第49张票
丙抢到第45张票
已抢到第46张票
甲抢到第47张票
已抢到第43张票
丙抢到第44张票
已抢到第41张票
已抢到第39张票
已抢到第38张票
已抢到第37张票
已抢到第36张票
已抢到第35张票
已抢到第34张票
已抢到第33张票
已抢到第32张票
已抢到第31张票
已抢到第30张票
甲抢到第42张票
已抢到第29张票
丙抢到第40张票
已抢到第27张票
甲抢到第28张票
已抢到第25张票
丙抢到第26张票
丙抢到第22张票
丙抢到第21张票
丙抢到第20张票
丙抢到第19张票
丙抢到第18张票
丙抢到第17张票
丙抢到第16张票
丙抢到第15张票
丙抢到第14张票
丙抢到第13张票
丙抢到第12张票
丙抢到第11张票
丙抢到第10张票
丙抢到第9张票
丙抢到第8张票
丙抢到第7张票
丙抢到第6张票
丙抢到第5张票
丙抢到第4张票
丙抢到第3张票
丙抢到第2张票
丙抢到第1张票
已抢到第23张票
甲抢到第24张票

从上面的代码可以看出,共享的静态变量COUNT确实在3个线程中随机被瓜分完了,但是我们也注意到,这种结果并不是我们想要的,这就引出了我们下节要说的线程安全问题。

       好了天道酬勤、编码路上需要一颗持之以恒的心!!!!!

 

 

 

 

 

 

   

package com.liuyang.junit.test.thread;
/** * demo1主要是用继承Thread类实现构造线程,在这里我们需要重写run方法 * 来实现我们具体的逻辑 * @author 刘洋 */public class ThreadDemo1 extends Thread {
@Overridepublic void run() {super.run();System.out.println("当前正在运行的线程是:" + Thread.currentThread().getName());}public static void main(String[] args) {//构造线程对象ThreadDemo1 t1 = new ThreadDemo1();//启动线程t1.start();//主线程输出System.out.println("我是主线程");}}

 

posted @ 2018-08-19 21:55  倾慕已久  阅读(483)  评论(0编辑  收藏  举报