zoukankan      html  css  js  c++  java
  • Java高新技术7_多线程1(Timer,TimerTask,ThreadLocal,传统多线程安全问题和通信)


      1.多线程两个小问题:

    package com.itheima.thread;
    
    
    public class ThreadDemo1 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO 自动生成的方法存根
           new Thread(new Runnable(){
    
            @Override
             public void run() {
                // TODO 自动生成的方法存根
              System.out.println("runnable");
             }
            }){
             @Override
             public void run(){
                 System.out.println("thread");
             }
           }.start();
         
        }
    
    }
    /*
     问题一:
     如果在Thread子类覆盖的run方法中编写了运行代码,
     也为Thread子类对象传递了一个Runnable子类对象,
     那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?
     子类run方法的代码
    这是因为在Thread类的run方法:
      public void run() {
            if (target != null) {
                target.run();
            }
        }
    一旦复写,不在去找Runnable接口子类对象的run方法
    问题二:
     多线程机制会提高运行效率吗?为什么会有多线程下载吗?
       不会(类比一次拷贝一个文件,一次拷贝多个文件,第二种方式CPU在多个线程之间切换有额外开销,效率低于第一种)
      多线程下载,只是为了抢占更多的服务器资源
     */

    2.定时器与线程(感受思想)

    package com.itheima.thread;
    
    import java.util.Calendar;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class TraditionalTimerTest2 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO 自动生成的方法存根
            new Timer().schedule(new TimerTask(){
    
                @Override
                public void run() {//run方法中存放此计时器任务要执行的操作。
                    // TODO 自动生成的方法存根
                   System.out.println("BOMB!!!!!!!!!!");//10秒后炸弹爆炸,然后每隔3秒一爆炸
                }
                  
                
            }, 10000,3000);//task - 所要安排的任务。
                          //delay(延期)- 执行任务前的延迟时间,单位是毫秒。 
                         //period(周期)-执行各后续任务之间的时间间隔,单位是毫秒.        
          while(true){
                System.out.println(Calendar.getInstance().get(Calendar.SECOND));//1秒一打印
                try{
                  Thread.sleep(1000);//主线程睡1秒
                }
                catch(Exception e){
                    
                }
            }
        }
    
    }

    计时器

    针对以上例子,更复杂一点,如果实现2秒BOMB,4秒BOMB,2秒BOMB….

    方法一:两个TimerTask子类:你执行我的任务,我执行你的任务

    package com.itheima.thread;
    
    import java.util.Calendar;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class TraditionalTimerTest3 {
    
        /**
         * @param args
         */
         static class MyTask1 extends TimerTask{
    
                @Override
                public void run() {
                    // TODO 自动生成的方法存根
                  System.out.println("BomB!!!!");
                  new Timer().schedule(new MyTask2(),4000);
                 
            }
         }
             static class MyTask2 extends TimerTask{
    
                    @Override
                    public void run() {
                        // TODO 自动生成的方法存根
                      System.out.println("BoomB!!!!");
                      new Timer().schedule(new MyTask1(),2000);
                    }
                    
        }     
            
        
         //完成交替炸:2,4,2,4,2,4
        public static void main(String[] args) {
            // TODO 自动生成的方法存根
          
                 new Timer().schedule(new MyTask1(),2000);
         }
    }

    方法二:通过一个变量来控制该变量取值(0,1,0,1…)

    package com.itheima.thread;
    
    import java.util.Calendar;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class TraditionalTimerTest3 {
    
        /**
         * @param args
         */
         static int count=0;
        
    
        public static void main(String[] args) {
            // TODO 自动生成的方法存根
        
            class MyTask extends TimerTask{
                
                @Override
                public void run() {
                            System.out.println("BOMB!!!!");
                   count=(count+1)%2;
                   new Timer().schedule(new MyTask(),2000+2000*count);//即使是不同Timer对象,TimerTask对象不能使用同一个
                }                                                     //否则会报IllegalArgumentException,换成this可验证
                
            }
            new Timer().schedule(new MyTask(),2000);
            
             while(true){
                    System.out.println(Calendar.getInstance().get(Calendar.SECOND));
                    try{
                      Thread.sleep(1000);//主线程睡1秒
                    }
                    catch(Exception e){
                        
                    }  
               }
        }
    
    }

    计时器2

    3.传统多线程安全问题与通信几种设计方式:(体会思想)

    一.如果每个线程执行的代码相同,可以使用同一个Runnable接口子类对象,这个Runnable接口子类对象中有那个共享数据,例如,买票系统就可以这么做。

    二.如果每个线程执行的代码不同,这时候需要用不同的Runnable接口子类对象,有如下两种方式来实现这些Runnable接口子类对象之间的数据共享:

    1.将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable接口子类对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。

    2.将这些Runnable接口子类对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable接口子类对象调用外部类的这些方法。

    3.上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable接口子类对象作为外部类中的成员内部类或局部内部类。

    总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。

    //使用方式一
    class Ticket implements Runnable{
        private int ticket=100;
        @Override
        public synchronized void run() {
            // TODO 自动生成的方法存根
          while(true)
             if(ticket>0)
              System.out.println(Thread.currentThread().getName()+"..."+ticket—);
        }
    }
    class TicketTest{
        public static void main(String[] args){
            Ticket t=new Ticket();
            new Thread(t).start();
            new Thread(t).start();
        }
    }
    //方式二:1
    class Resource{
      private int j=0;
      public synchronized void add(){
           j=j+1;
          System.out.println(Thread.currentThread().getName()+"..."+j);
      }
      public synchronized void reduce(){
          j=j-1;
          System.out.println(Thread.currentThread().getName()+"..."+j);
      }
    }
    
    
    class addImp implements Runnable{
        private Resource r;
        public addImp(Resource r){//将Resource对象传递过来
         this.r=r;
        }
        @Override
        public void run() {
            // TODO 自动生成的方法存根
          r.add();
        }
    }
    class reduceImp implements Runnable{
           private Resource r;
            public reduceImp(Resource r){//将Resource对象传递过来
             this.r=r;
            }
            @Override
            public void run() {
                // TODO 自动生成的方法存根
              r.reduce();
            }
    }
    class MainClass{
        public static void main(String[] args){
            Resource r=new Resource();//用的依然是方式一定义的Resource类
            for(int i=0;i<2;++i)
             new Thread(new addImp(r)).start();
            for(int i=0;i<2;++i)
             new Thread(new reduceImp(r)).start();
        }
    }
    //方式二:2
    class MainClass2{
      private static int j=0;//共享数据作为这个外部类中的成员变量
      public static synchronized void add(){//每个线程对共享数据的操作方法也分配给外部类
          j=j+1;
         System.out.println(Thread.currentThread().getName()+"..."+j);
     }
     public static synchronized void reduce(){
         j=j-1;
         System.out.println(Thread.currentThread().getName()+"..."+j);
     }
     public static void main(String[] args){//这里静态只能访问静态,以上成员需要static修饰符,根据需要
         for(int i=0;i<2;++i){
             new Thread(new Runnable(){
                 @Override
                  public void run() {
                      add();
                  }
                 
             }).start();
             new Thread(new Runnable(){
                 @Override
                  public void run() {
                      reduce();
                  }
                 
             }).start();
         }
     }
     
    }
    //方式2:3
    class MultithreadShare7 {
      private static Resource r=new Resource();
      public static void main(String[] args){
       //final Resource r=new Resource();//r作为局部变量,被内部类访问需要final修饰
       for(int i=0;i<2;++i){
       new Thread(new Runnable(){
           @Override
            public void run() {
                r.add();
            }
           
       }).start();
       new Thread(new Runnable(){
           @Override
            public void run() {
                r.reduce();
            }
           
       }).start();
      }
      
     }
      
     
    }

    线程间通信例子:

    package com.itheima.thread;
    /*子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程循环
    100,如此循环50次,请写出程序*/
    //使用的3思想
    class RunCode{
        private boolean flag=false;//使用标记进一步控制
        public synchronized  void subThreadCode(){
                  while(flag)
                      try {
                        this.wait();
                       } catch (InterruptedException e) {
                    
                        e.printStackTrace();
                      }
                      for(int i=0;i<5;++i)
                          System.out
                          .println(Thread.currentThread().getName() + "..." + i);
                        flag=true;
                      this.notify();
                    
              
            }
            public synchronized  void mainThreadCode(){
              while(!flag)
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        // TODO 自动生成的 catch 块
                        e.printStackTrace();
                    }
                    for(int i=0;i<3;++i)
                          System.out
                                .println(Thread.currentThread().getName() + "..." + i);
                    
                   flag=false;
                   this.notify();
        
            }
    }
    public class ThreadInterviewQuestion4 {
    
        /**
         * @param args
         */
    public static void main(String[] args) {
           final RunCode rc=new RunCode();
           new Thread(new Runnable(){
            @Override
            public void run() {
             for(int i=0;i<10000;++i){//没有按照原题目,为了看是否有”奇迹”发生
                   rc.subThreadCode();
            
               }
            }
          }).start();
           for(int i=0;i<10000;++i){
             rc.mainThreadCode();
    
          }
        }
    
    }
    /*
    规范代码:
     1.多个线程执行代码放在共享资源中(这样做便于管理,扩展)
     2.等待唤醒机制一般都要使用标记
     3.尝试各种方法,发现最好使用if判断是否wait,然后线程执行代码,置换标记,唤醒(其它方式各种"奇迹")
     4.即使两个线程进行通信,依然建议while判断标记,api中一句话:对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用更加安全
    
     */

    4.ThreadLocal:

    用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。

    不使用ThreadLocal达到需求,采用 局部变量+Map集合

    package com.itheima.thread;
    /*
    如何达到当0线程执行时,A,B访问的是0线程的data,当1线程执行时,A,B访问的是1线程的data?
    方式一.考虑使用同步
      那么:A,B访问data也必须加入到同步中,很大的限制:只有A,B都访问完0线程,才能访问1线程,局限性比较强
    方式二.使用HashMap集合->将线程与对应的数据绑定
       使用局部的data,每个线程在栈中都将对应一个局部变量
       不能再使用类变量,依然可能出现线程间的data覆盖问题
    */
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Random;
    
    public class ThreadScopeShareData5 {
        //private static int data;
        private static Map<Thread,Integer> map=new HashMap<Thread,Integer>();
        public static void main(String[] args) {
            for(int i = 0; i <2; i ++) {
             new Thread(new Runnable() {
                @Override
                public void run() {
                    //synchronized(int.class){
                      int data = new Random().nextInt();
                      map.put(Thread.currentThread(),data);
                      System.out.println(Thread.currentThread().getName()+ "has put data:" + data);
                      new A().get();
                      new B().get();
                    //}
                    
                }
            }).start();
          }
            
        }
        static class A{
            public void get() {
    
                int data=map.get(Thread.currentThread());
                System.out.println("A " + Thread.currentThread().getName() + "get data :" +data);
            }
        }
        static class B{
            public void get() {
         
                int data=map.get(Thread.currentThread());
                System.out.println("B " + Thread.currentThread().getName() + "get data :" +data);
            }
        }
    }

    ThreadLocal1

    //使用ThreadLocal
    /*每个线程调用全局ThreadLocal对象的set方法,
    就相当于往其内部的map中增加一条记录,
    key分别是各自的线程,value是各自的set方法传进去的值。*/
    public class ThreadLocalTest6 {
        private  static ThreadLocal<Integer> tl=new ThreadLocal<Integer>();
        public static void main(String[] args) {
            for(int i = 0; i <3; i ++) {
             new Thread(new Runnable() {
                @Override
                public void run() {
                    
                      int data = new Random().nextInt();
                      tl.set(data);//将 当前线程=data 存入ThreadLocal内部集合
                      System.out.println(Thread.currentThread().getName()+ "has put data:" + data);
                      new A().get();
                      new B().get();
                 
                    
                }
            }).start();
          }
            
        }
        static class A{
            public void get() {
    
                System.out.println("A " + Thread.currentThread().getName() + "get data :" +tl.get());
            }
        }
        static class B{
            public void get() {
         
                System.out.println("B " + Thread.currentThread().getName() + "get data :" +tl.get());
            }
        }
    }
    //当有多个变量时,把多个变量封装到类中
    /*
    一个ThreadLocal对象只能操作一个变量,如上只能操作data.
    那么如果有多个变量,data1,data2.....
    考虑使用类这些变量封装,
    一般做法:ThreadLocal操作该类的对象(ThreadLocal.set(object),ThreadLocal.get(object))
    换一种思想:把ThreaLocal的get()与set()也封装到该类中,对外提供方法,直接返回当前线程绑定的实例
    */
    class ThreadLocalTest7{
        public static void main(String[] args) {
              for(int i = 0; i <3; i ++) {
                 new Thread(new Runnable() {
                    @Override
                    public void run() {
                          int data=new Random().nextInt(); 
                          MyThreadLocalData.getThreadInstance().setName("zhang"+data);
                          MyThreadLocalData.getThreadInstance().setAge(20+data);
                          new A().get();
                          new B().get();
                        
                        }
                        
                    }
                  ).start();
    
              }
        
             
            }
            static class A{
                public void get() {
                     
                    System.out.println("A " + Thread.currentThread().getName() + "get data :"
                       +MyThreadLocalData.getThreadInstance().getName()+".."+MyThreadLocalData.getThreadInstance().getAge());
                }
            }
            static class B{
                public void get() {
                    
                    System.out.println("B " + Thread.currentThread().getName() + "get data :"+
                            MyThreadLocalData.getThreadInstance().getName()+".."+MyThreadLocalData.getThreadInstance().getAge());
                }
            }
    }
    
    class MyThreadLocalData{
       private String name;
       private String sex;
       private int age;
       private static ThreadLocal<MyThreadLocalData> tl=new ThreadLocal<MyThreadLocalData>();
       
       //单例懒汉式改造
       public static MyThreadLocalData getThreadInstance(){ 
           MyThreadLocalData myData=tl.get();//每个线程对应一个myData,因此不使用同步
           if(myData==null){//判断当前线程是否有对应的实例,没有创建->绑定,有返回
               myData=new MyThreadLocalData();
               tl.set(myData);
           }
         return myData;       
      }
        
     
       public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getSex() {
            return sex;
        }
        public void setSex(String sex) {
            this.sex = sex;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
       
    }

    ThreadLocal2

  • 相关阅读:
    【集合遍历-Java】
    【eclipse】使用说明
    【Java IO流】浅谈io,bio,nio,aio
    【Mysql数据库】知识点总结
    【struts2】学习笔记
    【EL&JSTL】学习笔记
    思科交换机-常用命令及配置
    【JDBC-MVC模式】开发实例
    【JDBC】java连接MySQL数据库步骤
    【JDBC】Servlet实例
  • 原文地址:https://www.cnblogs.com/yiqiu2324/p/3233415.html
Copyright © 2011-2022 走看看