传统线程技术回顾

来源:互联网 发布:js获取标签自定义属性 编辑:程序博客网 时间:2024/06/10 14:53

------------------------------------------------------------------------------------
多线程:
线程就像是放飞的风筝,看起来已经飞的很高了,可线却掌握在主人的手中.
Thread类中的start方法的运行原理:
1.start方法将会调用run方法.
2.run方法将会调用Runnable对象中的run方法(存在Runnable对象).
案例:
构造一个定时器,让程序运行指定时间后停止.
for(long start=System.currentTimeMillis(),end=start;(end-start)<10000;end = System.currentTimeMillis())
{
 System.out.println(System.currentTimeMillis());
}
案例:
举例说明run方法的运行原理:
new Thread(new Runnable()
{
 @Override
 public void run()
 {
  System.out.println("我是父类");
 }
}){
 public void run()       //覆盖了父类中的run方法.
 {
  System.out.println("我是子类");    //这是打印结果
 };
}.start();
原理:
1.父类和子类中都有run方法.
2.子类中的run方法,已覆盖掉了父类中的run方法.
----------------------------------------------------------------------------------
线程间的互斥与通信:
1.互斥,也叫同步,也叫线程安全,
2.如果还要实现线程间的通信,就要使用wait+notify方法,
3.整个的线程的技术,都是基于共享了一个锁.
传统的锁模型:
Object
   |--synchronized
           |--wait
           |--notify
共2种同步和互斥的技术:
1.同步代码块.
synchronized(this){}
2.同步函数.
synchronized void method(){}
同步函数的锁是哪一个:
1.一般同步函数的锁是this.
2.静态同步函数的锁是Class对象.
案例:
构造一个线程不同步的例子.
 static void printName(String name)   //加synchronized搞定.
{
 for (int i = 0; i < name.length(); i++)
 {
  System.out.print(name.charAt(i));
 }
 System.out.println();
}
public static void main(String[] args)
{
 newThread("传智播客");
 newThread("黑马训练营");
}
private static void newThread(final String name)
{
 new Thread(new Runnable()
 {
  @Override
  public void run()
  {
   for (;;)
   {
    printName(name);
   }
  }
 }).start();
}
------------------------------------------------------------------------------------
线程间的通信技术:
1.线程间通信的核心,还是共享了同一个锁对象.
2.需要在这个锁对象上使用wait和notify方法.
案例:
面试题,子线程循环10次,主线程循环100,整体循环50次.
public class Test
{
 public static void main(String[] args) throws InterruptedException
 {
  final Flag obj = new Flag();  //提供了需要共享的锁对象和需要共享的flag变量.
  new Thread(new Runnable()
  {
   @Override
   public void run()
   {
    for (int i = 1; i <= 50; i++)
    {
     synchronized (obj)
     {
      while (!obj.flag)  //用while替代if,可以防止伪唤醒.
       try
       {
        obj.wait(); // 谨慎的态度,上来就wait.
       } catch (InterruptedException e)
       {
        e.printStackTrace();
       }
      for (int j = 1; j <= 10; j++)
      {
       System.out.println("子线程" + i + "之" + j);
      }
      obj.flag = false;
      obj.notify();
      if(i==50)break;  //防止资源的浪费.
     }
    }
   }
  }).start();
  for (int i = 1; i <= 50; i++)
  {
   synchronized (obj)
   {
    while (obj.flag)  //用while替代if,可以防止伪唤醒.
     obj.wait(); // 谨慎的态度,上来就wait.
    for (int j = 1; j <= 100; j++)
    {
     System.out.println("主线程" + i + "之" + j);
    }
    obj.flag = true;
    obj.notify();
    if(i==50)break;  //防止资源的浪费.
   }
  }
 }
}
class Flag
{
 boolean flag = true;  //构建对象,突破final的限制.
}
看点:
1.如何让子线程先运行,也就是如何能够共享flag的问题.
2.本例中使用把final的变量作为一个对象,从而突破final的限制.
如何突破final的限制:
1.可以把final变量升级为成员变量.
2.可以把final的变量作为一个对象,而对对象中的变量进行改变.
3.可以通过桥接的方法,此时,只能由外向内修改,无法由内向外修改.
案例:
把上面需要同步的代码提取出来,以实现高内聚低耦合.
public class TestTest
{
 public static void main(String[] args)
 {
  final Business obj = new Business();
  new Thread(new Runnable()
  {
   @Override
   public void run()
   {
    for (int i = 1; i <= 50; i++)
    {
     obj.sub(i);
     if(i==50)break;
    }
   }
  }).start();
  for (int i = 1; i <= 50; i++)
  {
   obj.main(i);
   if (i == 50)break;
  }
 }
}
class Business  //把需要同步的代码提取出来,并封装到一个类中.
{
 boolean flag = true;
 synchronized void sub(int i)
 {
  while (!flag)
   try
   {
    this.wait();
   } catch (InterruptedException e)
   {
    e.printStackTrace();
   }
  for (int j = 1; j <= 10; j++)
  {
   System.out.println("子线程" + i + "之" + j);
  }
  flag = !flag;
  this.notify();
 }
 synchronized void main(int i)
 {
  while (flag)
   try
   {
    this.wait();
   } catch (InterruptedException e)
   {
    e.printStackTrace();
   }
  for (int j = 1; j <= 100; j++)
  {
   System.out.println("主线程" + i + "之" + j);
  }
  flag = !flag;
  this.notify();
 }
}
思路:
以上这个例子,还是比较复杂的.
--------------------------------------------------------------------------------------
线程范围内的共享数据:
1.同一类的多个线程将共享同一个"代码区"和"数据空间".
2.每个线程又都维护有自己的独立的"运行栈"和"程序计数器".
案例:
线程范围内的数据共享失败.
static int data;
public static void main(String[] args)
{
 for(int i=1;i<=3;i++)
 {
  new Thread(new Runnable()
  {
   @Override
   public void run()
   {
    data = new Random().nextInt();
    System.out.println(Thread.currentThread().getName()+"存入"+data);
    new A().get();
    new B().get();
   }
  }).start();
 }
}
static class A     //A模块
{
 void get()
 {
  System.out.println(Thread.currentThread().getName()+"取出"+data);
 }
}
static class B     //B模块
{
 void get()
 {
  System.out.println(Thread.currentThread().getName()+"取出"+data);
 }
}
案例:
把多线程共享的数据变成单线程独享的数据.
static Map<Thread,Integer> threadData = new HashMap<Thread,Integer>();
public static void main(String[] args)
{
 for(int i=1;i<=3;i++)
 {
  new Thread(new Runnable()
  {
   @Override
   public void run()
   {
    int data = new Random().nextInt();
    threadData.put(Thread.currentThread(),data);
    System.out.println(Thread.currentThread().getName()+"存入"+data);
    new A().get();
    new B().get();
   }
  }).start();
 }
}
static class A
{
 void get()
 {
  System.out.println(Thread.currentThread().getName()+"取出"+threadData.get(Thread.currentThread()));
 }
}
static class B
{
 void get()
 {
  System.out.println(Thread.currentThread().getName()+"取出"+threadData.get(Thread.currentThread()));
 }
}
思路:
不再使用共享数据空间来存储数据,而把数据转存在Map集合中,Map<线程,数据>.
扩展:
每一个线程应该拿到一个数据库连接,Map<线程,连接>,道理是一样的.
------------------------------------------------------------------------------------------------
使用ThreadLocal类,实现线程范围内的数据共享:
---->java.lang.ThreadLocal<T>类:
1.这个类就相当于一个Map,可以实现线程范围内的数据共享.
2.一个ThreadLocal只能存放线程的一个变量,可以把这个变量扩展到一个对象.
ThreadLocal类中的方法:
protected T initialValue()
public T get()
从当前线程里取出数据.
public void set(T value)
把数据存入到了当前线程里面.
public void remove()
清空当前线程所存入的键值对.
案例:
使用ThreadLocal类,改写上面的代码.
static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();  //改写点.
public static void main(String[] args)
{
 for(int i=1;i<=3;i++)
 {
  new Thread(new Runnable()
  {
   @Override
   public void run()
   {
    int data = new Random().nextInt();
    threadLocal.set(data);
    System.out.println(Thread.currentThread().getName()+"存入"+data);  //改写点.
    new A().get();
    new B().get();
   }
  }).start();
 }
}
static class A
{
 void get()
 {
  System.out.println(Thread.currentThread().getName()+"取出"+threadLocal.get());  //改写点.
 }
}
static class B
{
 void get()
 {
  System.out.println(Thread.currentThread().getName()+"取出"+threadLocal.get());  //改写点.
 }
}
思路:
ThreadLocal类就相当于一个Map,可把数据存放在ThreadLocal中.
案例:
写一个懒汉式的单例.
class MyThreadScopeData
{
 private MyThreadScopeData(){};  //构造函数.
 private static MyThreadScopeData instance = null;
 public static synchronized MyThreadScopeData getInstance()
 {
  if(instance==null)
  {
   instance = new MyThreadScopeData();  //延迟创建.
  }
  return instance;
 }
}
思路:
设计一个懒汉式,共需要3个步骤.
1.构造函数.
2.静态变量.
3.静态方法.
案例:
根据上面的懒汉式,模拟Struts2的容器对象,实现线程范围内的数据共享.
class MyThreadScopeData
{
 private MyThreadScopeData(){};  //构造函数.
 //private static MyThreadScopeData instance = null;
 private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
 public static /*synchronized*/ MyThreadScopeData getInstance()
 {
  MyThreadScopeData instance = map.get(); //新建语句.
  if(instance==null)
  {
   instance = new MyThreadScopeData();  //延迟创建.
   map.set(instance);
  }
  return instance;
 }
}
思路:
设计一个Struts2的容器,共需要3个步骤.
1.构造函数.
2.静态变量.
3.静态方法.
-----------------------------------------------------------------------------------------------------
多个线程访问共享对象和数据的方式:
1.将共享数据封装和对共享数据的操作都封装在对象中,将这个对象逐一传给(不同的)Runnable对象的构造方法中.
2.将共享数据封装和对共享数据的操作都封装在对象中,让(不同的)Runnable对象,主动访问外部的局部数据对象.
案例:
设计4个线程,2个对j加1,2个对j减1.
思路:
由于对线程的操作方式不一样,需要不同的Runnable对象.
public class TestTest
{
 public static void main(String[] args)
 {
  ShareData share = new ShareData();
  new Thread(new Runnable1(share)).start();
 }
}
class Runnable1 implements Runnable
{
 ShareData share;
 Runnable1(ShareData share)
 {
  this.share = share;
 }
 @Override
 public void run()
 {
  share.increment();
 }
}
class ShareData
{
 int j = 0;
 void increment()
 {
  j++;
 }
 void decrement()
 {
  j--;
 }
}
多线程共享数据的引用所在的位置:
1.可以放在类的成员变量中.
2.可以放在final的局部变量中.
----------------------------------------------------------------------------------
Java 1.3中的特性-----Timer定时器(用的不多):
可以使用开源的工具quartz来做定时器.
---->java.util.Timer类,
1.Timer类的使用方式,Timer-->TimerTask(抽象类)-->run().
2.每一个Timer对象,都是一个新线程,而且这个线程自己不会停止.
3.实现原理,使用的Object.wait(long)方法来安排任务.
Timer类中的方法:
public void schedule(重载)
这个方法的本质,就是在线程上执行了wait.
public void scheduleAtFixedRate(重载)
public void cancel()
终止定时器,并丢弃所有任务.
public int purge()
从任务队列中移除所有已取消的任务.
---->java.util.TimerTask类,
1.一个任务只能够被安排一次,重复安排抛IllegalStateException.
2.这个安排,可以是一次性的调度,也可以是周期性的调度.
TimerTask类中的方法:
定时器任务类,这是一个抽象类,此类实现了Runnable接口,
public abstract void run()
定时器要执行的操作,
public boolean cancel()
取消定时器任务.
public long scheduledExecutionTime()
返回已安排的任务执行时间.
案例:
使用Timer类,完成交替2个时间段的某个任务.
代码:
boolean flag; // flag需要被局部类共享,不需要加final
public static void main(String[] args) throws InterruptedException
{
 new TestTest().exchangedMethod();    //客户端的调用.
}
void exchangedMethod() throws InterruptedException
{
 final Timer timer = new Timer(); // timer需要被局部类共享,需要加上final
 class MyTimerTask extends TimerTask
 {
  @Override
  public void run()
  {
   System.out.println("bombing.....");   //这里是业务逻辑部分的代码.
   if (!flag)
   {
    timer.schedule(new MyTimerTask(), 1000);  //TimerTask对象,不可以被共享.
   } else
   {
    timer.schedule(new MyTimerTask(), 3000);
   }
   flag = !flag;
  }
 }
 timer.schedule(new MyTimerTask(), 2000);
 while (true) // 这里可以辅助显示时间.
 {
  System.out.println(new Date().getSeconds());
  Thread.sleep(1000);
 }
}
看点:
1.timer(局部变量)是共享的,flag(成员变量)也是共享的,
2.MyTimerTask类的内部,new出了自己的实例.
3.辅助显示时间的while循环.
缺点:
1.以上的这种实现,写的既复杂又乱七八糟.
2.完全可以用Object.wait方法替代Timer类的使用,将更直观.
3.本例的实现,就是一个迭代,只是迭代中使用了wait方法.
-------------------------------------------------------------------------------------------
         


0 0