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

posted @ 2013-08-02 19:56  伊秋  阅读(588)  评论(0编辑  收藏  举报