zoukankan      html  css  js  c++  java
  • 02.Java多线程并发库API使用

    1.        传统线程技术回顾

    继承线程与实现Runnable的差异?为什么那么多人都采取第二种方式?

    因为第二种方式更符合面向对象的思维方式。创建一个线程,线程要运行代码,而运行的代码都封装到一个独立的对象中去。一个叫线程,一个叫线程运行的代码,这是两个东西。两个东西一组合,就表现出了面向对象的思维。如果两个线程实现数据共享,必须用Runnable的方式。

    查看Thread类的run()方法的源代码,可以看到其实这两种方式都是在调用Thread对象的run方法,如果Thread类的run方法没有被覆盖,并且为该Thread对象设置了一个Runnable对象,该run方法会调用Runnable对象的run方法。

    问题如果在Thread子类覆盖的run方法中编写了运行代码,也为Thread子类对象传递了一个Runnable对象,那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?子类的run方法。

    示例代码

     1     new Thread(new Runnable() {
     2         public void run() {
     3             while (true) {
     4                 System.out.println("run:runnable");
     5             }
     6         }
     7     }) {
     8         public void run() {
     9             while (true) {
    10                 try {
    11                     Thread.sleep(1000);
    12                 } catch (Exception e) {
    13                 }
    14                 System.out.println("run:thread");
    15             }
    16         }
    17     }.start();

    该线程会运行重写的Thread中的run方法,而不是Runnable中的run方法,因为在传统的Thread的run方法是:

    1     public void run() {
    2         if (target != null) {
    3             target.run();
    4         }   
    5     }

    如果想要运行Runnable中的run方法,必须在Thread中调用,但是此时我重写了Thread中的run方法,导致if (target != null) {  target.run();     }不存在,所以调用不了Runnable中的run方法。

    注意:多线程的执行,会提高程序的运行效率吗?为什么会有多线程下载?

    不会,有时候还会降低程序的运行效率。因为CPU只有一个,在CPU上下文切换的时候,可能还会耽误时间。

    多线程下载:其实你的机器没有变快,而是你抢了服务器的带宽。如果你一个人下载,服务器给你提供的是20K的话,那么10个人的话,服务器提供的就是200K。这个时候你抢走200k,所以感觉变快了。

    2.        传统定时器技术回顾(jdk1.5以前)Timer、TimerTask

    Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。

    scheduleAtFixedRate(TimerTask task, Date firstTime, long period):

    安排指定的任务在指定的时间开始进行重复的固定速率执行。

    schedule(TimerTask task, long delay, long period): 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。

    作业调度框架 Quartz(专门用来处理时间时间的工具)。你能够用它来为执行一个作业而创建简单的或复杂的调度。{ http://www.oschina.net/p/quartz}

    问题:每天早晨3点来送报纸。

    问题:每个星期周一到周五上班,周六道周日不上班。

    示例代码:(间隔不同时间,执行不同事件)

     1 package com.chunjiangchao.thread;
     2 
     3 import java.util.Timer;
     4 import java.util.TimerTask;
     5 /**
     6  * 重复执行某项任务,但是时间间隔性不同是2,4这种状态
     7  * @author chunjiangchao
     8  *
     9  */
    10 public class TimerDemo02 {
    11     
    12     private static long count = 1;
    13     public static void main(String[] args) {
    14         Timer timer =  new Timer();
    15         timer.schedule(new Task(), 1000);
    16         /*
    17              测试打印结果如下:
    18              执行任务,当前时间为:1460613231
    19             执行任务,当前时间为:1460613235
    20             执行任务,当前时间为:1460613237
    21             执行任务,当前时间为:1460613241
    22          */
    23         
    24     new Thread(new Runnable() {
    25         public void run() {
    26             while (true) {
    27                 System.out.println("run:runnable");
    28             }
    29         }
    30     }) {
    31         public void run() {
    32             while (true) {
    33                 try {
    34                     Thread.sleep(1000);
    35                 } catch (Exception e) {
    36                 }
    37                 System.out.println("run:thread");
    38             }
    39         }
    40     }.start();
    41 
    42         
    43     }
    44     
    45     static class Task extends TimerTask{
    46 
    47         @Override
    48         public void run() {
    49             System.out.println("执行任务,当前时间为:"+System.currentTimeMillis()/1000);
    50             new Timer().schedule(new Task(), 2000*(1+count%2));
    51             count++;
    52         }
    53         
    54     }
    55 
    56 }

    3.        传统线程互斥技术

    本道例题:关键在于说明:要想实现线程间的互斥,线程数量必须达到两个或者两个以上,同时,访问资源的时候要用同一把锁(这个是必须的)。如果两个线程都访问不同的同步代码块,而且它们的锁对象都不相同,那么这些线程就没有达到同步的目的。

    示例代码:(访问同一个资源对象,但是锁对象不同,同样没有达到同步的目的)

     1 package com.chunjiangchao.thread;
     2 /**
     3  * 线程间同步与互斥
     4  * @author chunjiangchao
     5  * 因为print1方法与print3方法锁对象相同,所以在调用的时候,会产生互斥的现象,而print2的锁是当前正在执行对象print2方法的对象,
     6  * 与print1和print3同时执行,打印结果就不是期望的结果
     7  *
     8  */
     9 public class TraditionalThreadSynchronizedDemo {
    10 
    11     public static void main(String[] args) {
    12         final MyPrint myPrint = new MyPrint();
    13         //A
    14         new Thread(new Runnable() {
    15             
    16             @Override
    17             public void run() {
    18                 myPrint.print1("chunjiangchao");
    19             }
    20         }).start();
    21         //B
    22 //        new Thread(new Runnable() {
    23 //            
    24 //            @Override
    25 //            public void run() {
    26 //                myPrint.print2("fengbianyun");
    27 //            }
    28 //        }).start();
    29         //C
    30         new Thread(new Runnable() {
    31             
    32             @Override
    33             public void run() {
    34                 myPrint.print3("liushaoyue");
    35             }
    36         }).start();
    37     }
    38     static class MyPrint{
    39         public void print1(String str){
    40             synchronized (MyPrint.class) {
    41                 for(char c :str.toCharArray()){
    42                     System.out.print(c);
    43                     pSleep(200);
    44                 }
    45                 System.out.println("print1当前已经打印完毕");
    46             }
    47             
    48         }
    49         public synchronized void print2(String str){
    50             for(char c :str.toCharArray()){
    51                 System.out.print(c);
    52                 pSleep(200);
    53             }
    54             System.out.println("print2当前已经打印完毕");
    55         }
    56         public synchronized static void print3(String str){
    57             for(char c :str.toCharArray()){
    58                 System.out.print(c);
    59                 pSleep(200);
    60             }
    61             System.out.println("print3当前已经打印完毕");
    62         }
    63         private static void pSleep(long time){
    64             try {
    65                 Thread.sleep(time);
    66             } catch (InterruptedException e) {
    67                 e.printStackTrace();
    68             }
    69         }
    70         
    71     }
    72 
    73 }
    View Code

    4.        传统线程同步通信技术

    在设计的时候,最好将相关的代码封装到一个类中,不仅可以方便处理,还可以实现内部的高耦合。

    问题示例图

     

    经验总结:要用到共同数据(包括同步锁)或共同算法的若干个方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性。

    同步通信,互斥的问题不是写在线程上面的,而是直接写在资源里面的,线程是直接拿过来使用就可以了。好处就是,以后我的类,交给任何一个线程去访问,它天然就同步了,不需要考虑线程同步的问题。如果是在线程上面写,明天又有第三个线程来调用我,还得在第三个线程上面写互斥写同步。(全部在资源类的内部写,而不是在线程的代码上面去写)

    示例代码:(子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100, 如此循环50次,请写出程序。)

     1 package com.chunjiangchao.thread;
     2 
     3 public class TraditionalThreadCommunication {
     4     /**
     5      * 经验:涉及到线程互斥共享,应该想到将同步方法写在资源里面,而不是写在线程代码块中 在资源中判断标记的时候,最好用while语句
     6      */
     7     public static void main(String[] args) {
     8         final Output output = new Output();
     9         new Thread(new Runnable() {
    10             public void run() {
    11                 for (int i = 0; i < 50; i++) {
    12                     output.sub(10);
    13                 }
    14             }
    15         }).start();
    16         for (int i = 0; i < 50; i++) {
    17             output.main(i);
    18         }
    19     }
    20 }
    21 
    22 class Output {
    23     private boolean flag = true;
    24 
    25     public synchronized void sub(int i) {
    26         while (!flag) {// 用while比用if更加健壮,原因是即使线程被唤醒了,也判断一下是不是真的该它执行了
    27                         // 防止伪唤醒的事件发生。
    28             try {
    29                 this.wait();
    30             } catch (InterruptedException e) {
    31             }
    32         }
    33         for (int j = 0; j < 10; j++) {
    34             System.out.println(i + "子线程运行" + j);
    35         }
    36         flag = false;// 记得要改变一下标记的状态
    37         this.notify();// 最后要唤醒其他要使用该锁的线程
    38     }
    39 
    40     public synchronized void main(int i) {
    41         while (flag) {
    42             try {
    43                 this.wait();
    44             } catch (InterruptedException e) {
    45             }
    46         }
    47         for (int j = 0; j < 100; j++) {
    48             System.out.println(i + "主线程运行" + j);
    49         }
    50         flag = true;
    51         this.notify();
    52     }
    53 }

    5.        线程范围内共享变量的概念与作用

    线程范围内的数据共享:不管是A模块,还B模块,如果他们在同一个线程上运行,那么他们操作的数据应该是相同。而不应该是不管A模块和B模块在哪个线程上面运行他们的数据在每个线程中的数据是一样的。(应该是各自线程上的数据是独立的)

    线程间的事务处理:

    如图:

    不能出现这样的情况:thread1转入的data,还没有来得及操作。CPU时间片转入到thread2,该thread2来执行,转入、转出,最后直接提交事务。导致thread1的数据出现错误。

    线程范围内的变量有什么用?

    我这件事务在线程范围内搞定,不要去影响别的线程的事务。但是我这个线程内,几个模块之间是独立的,这几个模块又要共享同一个对象。它们既要共享又要独立,在线程内共享,在线程外独立。【对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。】

    示例代码(不同线程之间共享同一个Map对象,但是Map中的每个元素表示的是不同线程的数据)

     1 package com.chunjiangchao.thread;
     2 
     3 import java.util.HashMap;
     4 import java.util.Map;
     5 import java.util.Random;
     6 
     7 /**
     8  * 线程范围内共享数据
     9  * @author chunjiangchao
    10  *
    11  */
    12 public class ThreadScopeShareDataDemo {
    13     //所有线程共享的数据是datas,但是datas中的每个元素key是Thread,每个元素针对每个线程来说是独立的,value代表不同线程处理的数据
    14     private static Map<Thread,Integer> datas = new HashMap<Thread,Integer>();
    15     public static void main(String[] args) {
    16         for(int i=0;i<2;i++){
    17             new Thread(new Runnable() {
    18                 
    19                 @Override
    20                 public void run() {
    21                     int nextInt = new Random().nextInt();
    22                     datas.put(Thread.currentThread(), nextInt);
    23                     ///A模块与B模块是独立的,但是A与B共享当前当前线程中的数据
    24                     new ModuleA().getThreadData();
    25                     new ModuleB().getThreadData();
    26                 }
    27             }).start();
    28         }
    29         /*
    30              打印的结果为
    31              Thread-1的ModuleA获取的变量为:-918049793
    32             Thread-0的ModuleA获取的变量为:-1424853148
    33             Thread-0的ModuleB获取的变量为:-1424853148
    34             Thread-1的ModuleB获取的变量为:-918049793
    35  
    36          */
    37     }
    38     static class ModuleA{
    39         public void getThreadData(){
    40             System.out.println(Thread.currentThread().getName()+"的ModuleA获取的变量为:"+datas.get(Thread.currentThread()));
    41         }
    42     }
    43     static class ModuleB{
    44         public void getThreadData(){
    45             System.out.println(Thread.currentThread().getName()+"的ModuleB获取的变量为:"+datas.get(Thread.currentThread()));
    46         }
    47     }
    48 
    49 }

    6.        ThreadLocal类及应用技巧

    ThreadLocal就相当于一个Map。

    一个ThreadLocal代表一个变量,故其中只能放一个数据,你有两个变量都要线程范围内共享,则要定义两个ThreadLocal对象,如果有一个一百个变量要线程共享?那么就应该定义一个对象来装着一百个变量,然后在ThreadLocal中存储这一个对象。

    问题:怎么在线程结束的时候得到通知?提示:监听虚拟机结束(只是一种思路)。就我目前所学的知识来说,还没找到这个方法。

    最重要的一点,就是里面涉及到的设计方法。

    示例代码(演示ThreadLocal的使用,不同线程中使用相同类型对象的设计)

     1 package com.chunjiangchao.thread;
     2 
     3 import java.util.Random;
     4 
     5 /**
     6  * 使用ThreadLocal
     7  * @author chunjiangchao
     8  *
     9  */
    10 public class ThreadLocalDemo {
    11     private static ThreadLocal<Integer > localInteger = new ThreadLocal<Integer>();
    12     public static void main(String[] args) {
    13         //创建两个线程
    14         for(int i=0;i<2;i++){
    15             new Thread(new Runnable() {
    16                 
    17                 @Override
    18                 public void run() {
    19                     int randomInt = new Random().nextInt();
    20                     localInteger.set(randomInt);
    21                     new ModuleA().getDataShareData();
    22                     new ModuleB().getDataShareData();
    23                     
    24                     //给线程中的变量进行赋值
    25                     ThreadScopeData threadInstance = ThreadScopeData.getThreadInstance();
    26                     threadInstance.setName("每个线程里面都有不同的ThreadScopeData对象---"+randomInt);
    27                     threadInstance.setHabbies("每个线程里面都有不同的ThreadScopeData对象---设置不同对象的成员变量"+randomInt);
    28                     //取出数据
    29                     new ModuleC().getDataShareData();
    30                     new ModuleD().getDataShareData();
    31                     
    32                 }
    33             }).start();
    34         }
    35         //测试的结果为:
    36         /*
    37              Thread-0在ModuleA中访问的数据为:-1711153118
    38             Thread-1在ModuleA中访问的数据为:532928477
    39             Thread-1在ModuleB中访问的数据为:532928477
    40             Thread-0在ModuleB中访问的数据为:-1711153118
    41             Thread-1在ModuleC中访问的数据为:
    42             每个线程里面都有不同的ThreadScopeData对象---532928477
    43             每个线程里面都有不同的ThreadScopeData对象---设置不同对象的成员变量532928477
    44             Thread-0在ModuleC中访问的数据为:
    45             每个线程里面都有不同的ThreadScopeData对象----1711153118
    46             每个线程里面都有不同的ThreadScopeData对象---设置不同对象的成员变量-1711153118
    47             Thread-1在ModuleD中访问的数据为:
    48             每个线程里面都有不同的ThreadScopeData对象---532928477
    49             每个线程里面都有不同的ThreadScopeData对象---设置不同对象的成员变量532928477
    50             Thread-0在ModuleD中访问的数据为:
    51             每个线程里面都有不同的ThreadScopeData对象----1711153118
    52             每个线程里面都有不同的ThreadScopeData对象---设置不同对象的成员变量-1711153118
    53          * */
    54     }
    55     static class ModuleA{
    56         public void getDataShareData(){
    57             int data = localInteger.get();
    58             System.out.println(Thread.currentThread().getName()+"在ModuleA中访问的数据为:"+data);
    59         }
    60     }
    61     static class ModuleB{
    62         public void getDataShareData(){
    63             int data = localInteger.get();
    64             System.out.println(Thread.currentThread().getName()+"在ModuleB中访问的数据为:"+data);
    65         }
    66     }
    67     static class ModuleC{
    68         public void getDataShareData(){
    69             ThreadScopeData data = ThreadScopeData.getThreadInstance();
    70             System.out.println(Thread.currentThread().getName()+"在ModuleC中访问的数据为:");
    71             System.out.println(data.getName());
    72             System.out.println(data.getHabbies());
    73         }
    74     }
    75     static class ModuleD{
    76         public void getDataShareData(){
    77             ThreadScopeData data = ThreadScopeData.getThreadInstance();
    78             System.out.println(Thread.currentThread().getName()+"在ModuleD中访问的数据为:");
    79             System.out.println(data.getName());
    80             System.out.println(data.getHabbies());
    81         }
    82     }
    83     
    84 }

     不同线程中使用相同类型的对象设计:

     1 package com.chunjiangchao.thread;
     2 /**
     3  * 设计线程范围内共享的对象:我这个对象的实例是与每个线程都相关的,那么这个设计就交个我这个类去办,其他的用户在
     4  * 在任意的线程中调用我的方法,自然的就是与这个线程有关的实例。我们的类设计出来就是与线程绑定的,在线程的任意地
     5  * 方调用我的方法,我就会返回与这个线程有关的实例对象,这个实例对象不需要用户去创建。
     6  * struts2就是这个思想:每一个请求来了,就激活一个线程(一个请求就是一个容器),这个线程就会有一个容器对象,
     7  * 这个容器对象就会将这个请求相关的所有东西都装载容器里面,这个容器里面装的东西只对这个请求有效。即使来了另外一
     8  * 个请求,那也会有另外一个容器,与当前的容器没有关系。
     9  * 隐藏了ThreadLocal这个变量,不用对外面暴露。外面可以直接调用我的方法就可获取与当前线程有关的数据
    10  * 这个模式与单例设计模式差不多,单例设计模式是无论在程序中的什么地方调用,返回的都是同一个对象。
    11  * 而这种设计是:无论在一个线程范围内的什么地方调用,返回的都是同一个对象。在不同范围内调用返回的是不同对象。
    12  * 
    13  */
    14 class ThreadScopeData{
    15     //当前ThreadLocal成员变量隐藏起来
    16     private static ThreadLocal<ThreadScopeData> threadLocal = new ThreadLocal<ThreadScopeData>();
    17     private ThreadScopeData(){}
    18     public static ThreadScopeData getThreadInstance(){
    19         ThreadScopeData data = threadLocal.get();
    20         if(data==null){
    21             data = new ThreadScopeData();
    22             threadLocal.set(data);
    23         }
    24         return data;
    25     } 
    26     //设置成员变量
    27     private String name;
    28     private String habbies;
    29     public String getName() {
    30         return name;
    31     }
    32     public void setName(String name) {
    33         this.name = name;
    34     }
    35     public String getHabbies() {
    36         return habbies;
    37     }
    38     public void setHabbies(String habbies) {
    39         this.habbies = habbies;
    40     }
    41     
    42 }

     未完待续……

  • 相关阅读:
    MyBatis的动态SQL语句这么厉害的!
    连接数据库,使用c3p0技术连接MySQL数据库
    Servlet 常见的乱码解决方案
    超级签具体实现
    Xcode报错You don’t have permission.
    SpringBoot+Mybatis整合实例
    恢复mysql数据库误删数据
    日期(date)运用座谈会
    程序猿日记--学习怎样学习
    服务器数据库密码忘记
  • 原文地址:https://www.cnblogs.com/chun-jiang-chao-de-gu-shi/p/5392056.html
Copyright © 2011-2022 走看看