Java基础加强之 Java5的线程并发库

 <a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流!

 

01. 传统线程技术回顾

创建线程的两种传统方式:

1、在Thread子类覆盖的run方法中编写运行代码;

 涉及一个以往知识点,能否在run方法声明上抛出InterruptedException异常?以便省略run方法内部对Thread.sleep()语句的try……catch处理?

  答:不能,因为Thread类的run方法没有抛异常,子类覆盖run方法时也不能抛异常。

2、在传递给Thread对象的Runnable对象的run方法中编写代码;

                  Thread thread1 = new Thread(){

                     @Override

                     publicvoid run()

                     {

                            while(true)

                            {

                                   try {

                                         Thread.sleep(500);

                                   //不能让run方法throws异常,因为父类run没有抛异常

                                   catch (InterruptedException e) {

                                         e.printStackTrace();

                                   }

                            System.out.println(Thread.currentThread().getName());//this.getName()也行

                            }

                     }

              };

              thread1.start();

              

              //创建Thread对象,并传入一个Runnble对象到构造方法

              Thread thread2 = new Thread(new Runnable(){

                     @Override

                     publicvoid run()

                     {

                            while(true)

                            {

                                   try {

                                         Thread.sleep(500);

                                   catch (InterruptedException e) {

                                         e.printStackTrace();

                                   }

                                  //此处就不能用this.getName,因为this代表Runnable子类对象

                                   System.out.println(Thread.currentThread().getName());  

                            }

                     }

                     

              });

 这两种方式有什么区别?为什么通常人们采用第二种(实现Runnable接口)?

      都是创建一个线程,只是第二种方式更加体现面向对象的思想;创建一个线程,线程要运行的代码封装到Runnable对象中(run方法)。

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

         Public void run()
{

if(target != null)

{//Thread构造方法中传递了Runnable对象就执行Runnablerun方法。

    target.run();    

}

 

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

答:(Thread子类的run),因为子类覆盖了父类的run方法,子类对象调用start方法时,会到自己的类中找run方法,如果没有找到就会执行父类的run方法,而父类(Thread)的run方法中会去调用Runnable对象的run方法。只要覆盖了Thread中的run方法,就不会执行Runnable对象的run方法。

   涉及到一个以往的知识点:匿名内部类对象的构造方法如何调用父类的非默认构造方法。

5、多线程机制会提高程序的运行效率吗?为什么会有多线程下载呢?

答:多线程不会提高程序的运行效率甚至更慢,因为cpu同一时间只能执行一个线程,如果要在几个线程之间切换需要时间。

        计算机并没有快,只是抢了服务器的带宽。因为多(10)个线程下载,服务器以为多(10)个客户端在下载,所以给每个客服端提供20k,合起来就200k

02. 传统定时器技术回顾

java.util 
 Timer 

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

void

schedule(TimerTask task, long delay)
安排在指定延迟后执行指定的任务。

void

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

java.util
 TimerTask   Timer 安排为一次执行或重复执行的任务。实现了Runnable接口。

boolean

cancel() 
取消此计时器任务。

abstract void

run() 
此计时器任务要执行的操作。

 

定时器的应用:

   //两个时间间隔发生,方式一:定义计数器

    class MyTimerTaskextends TimerTask

              {

                     @Override

                     publicvoid run()

                     {

                            count= (count+1)%2;

                            System.out.println("bombing!");

                            new Timer().schedule(new MyTimerTask(), 2000+2000*count);

                     }                   

              }

              new Timer().schedule(new MyTimerTask(), 2000); //启动炸弹,2秒后

 

03. 传统线程互斥技术

线程的互斥与同步通信:

1线程安全问题可以用银行转账来解释;两个业务同时拿到余额,一个存一个取。

2使用synchronized代码块及其原理;  任意对象都可作为锁

3使用synchronized方法;   this

4分析静态方法所使用的同步监视器对象是什么?

    方法所在类文件字节码

 

总结:如果多个线程执行的代码要求同步,需要加上synchronized;即一个线程没有将synchronized修饰的代码执行完时,其他线程不能进来执行。这里要注意,需要同步的代码(同一份)可以定义在不同的块中,但是为了同步,必须保证使用同一把锁。

 

线程的同步互斥的图文解说:

      线程1        线程2         synchronizedobj)的作用主要是其中obj的作用

        |               |

      -------------------------------         ab这两段代码被两个线程执行时要互斥;

       a………       b……         要互斥的代码必须用synchronized代码块包围。

 

                                 第二组代码要互斥,即cd互斥,但ac不互斥

     线程3        线程4        cpu可同时进入ac中进行替换

        |               |          

      -------------------------------       用什么进行互斥分组?就是synchronized中的obj       

       c………         d……       值不同,而分为不同的互斥组。

 

 

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

5waitnotify实现线程间的通信;

用面试宝典中的子线程循环10次和主线程循环5次,两者交替运行50的例子讲解;

为了观察程序的运行结果,可以在eclipse中设置运行对话框,让结果重定向到一个文本文件,然后用UE去查看该文件中的特殊的行号处是否正好对应了线程的切换点;

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

 

package cn.itcast.heima;

/**

 面试题:子线程循环10次,接着主线程循环10次;

 接着又回到子线程循环10次,然后再让主线程循环10此。

 交替循环50

 @author Administrator

 *

 */

publicclass TraditionalThreadCommunication

{

       

       publicstaticvoid main(String[] args)

       {

               final Business business =new Business();    //为什么要加final

              //子线程

               new Thread(new Runnable(){

                     publicvoid run()

                     {

                            for(int i=1;i<=50;i++)

                            {

                                   business.sub(i);       //每次循环,执行十次,然后等待唤醒main

                            }

                     }

                     

              }).start();

              

              for(int i=1;i<=50;i++)

              {

                     business.main(i);

              }

       }

 

        //此处不加static,在main方法中new内部类对象会出错,

       //是为什么?因为静态只能访问静态?

       staticclass Business  

       {

              privatebooleanbShouldSub =true;

              publicsynchronizedvoid sub(int i)

              {

                     while(!bShouldSub)  //不该子线程运行了就让子线程等

                     {

                            try {

                                   this.wait();

                            catch (InterruptedException e) {

                                   e.printStackTrace();

                            }

                     }

                     for(int j=1;j<=10;j++)

                     {

                            System.out.println("sub Thread sequece of"+j+"loop of "+i);

                     }

                     bShouldSub=false;

                     this.notify();  //唤醒主线程

              }

              publicsynchronizedvoid main(int i)

              {

                     while(bShouldSub)

                     {

                            try {

                                   this.wait();

                            catch (InterruptedException e) {

                                   e.printStackTrace();

                            }

                     }

                     for(int j=1;j<=20;j++)

                     {

                            System.out.println("main Thread "+j+"loop of "+i);

                     }

                     bShouldSub=true;

                     this.notify();  //唤醒子线程

              }

       }

}

 

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

publicclass ThreadScopeShareData

{

       //存放线程和数据的映射关系

       privatestatic Map<Thread, Integer>threadData =new HashMap<Thread, Integer>();

       publicstaticvoid main(String[] args)

       {             

              for(int i=0;i<2;i++)  //产生两个线程

              {

                     new Thread(new Runnable(){

                            publicvoid run()

                            {                          

                                   int data =new Random().nextInt();

                          System.out.println(Thread.currentThread().getName()+get data  "+data);

                                   

                                   threadData.put(Thread.currentThread(), data);

                                   new A().get();

                                   new B().get();

                            }

                     }).start();

              }

       }

 

 

06. ThreadLocal类及应用技巧

ThreadLocal实现线程范围的共享变量:

1、每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更块释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。

2ThreadLocal的应用场景:

订单处理包一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中;

 银行转账包含一系列操作:把转出账户的余额减少,把转入账户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库对象,转入和转出操作的代码分别是两个不同的账户对象的方法。

例如Strut2ActionContext,同一段代码被不同的线程调用运行的,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在那个模块中getContext方法,拿到的都是同一个。

实例

publicclass ThreadLocalTest

{

       //定义一个全局变量的ThreaadLocal变量,只能存放一个数据

       privatestatic ThreadLocal<Integer> x =new ThreadLocal<Integer>();

       //定义一个全局变量的ThreaadLocal变量,存放一个对象,线程的所有数据封装在这个对象中

       privatestatic ThreadLocal<MyThreadScopeData> myThreadScopeData =

                                            new ThreadLocal<MyThreadScopeData>();

       publicstaticvoid main(String[] args)

       {             

              for(int i=0;i<2;i++)  //产生两个线程

              {

                     new Thread(new Runnable(){

                            publicvoid run()

                            {                          

                                   int data =new Random().nextInt();

                            System.out.println(Thread.currentThread().getName()+get data  "+data);

                                   x.set(data); //ThreadLocal变量x中存储一个随机值

                                   

                                   MyThreadScopeData.getThreadInstance().setName("name"+data);

                                   MyThreadScopeData.getThreadInstance().setAge(data);

                                   

                                   //调用其他类的方法,获取x的值,多个类在同一线程中获取到的值是相同的

                                   new A().get();  

                                   new B().get();

                            }

                     }).start();

              }

       }

       

       staticclass A

       {

              publicvoid get()

              {

                     //读取这个ThreadLocal变量的值

                     int data =x.get();

              System.out.println("A from "+Thread.currentThread().getName()+" get data: "+data);

                     MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();

                     System.out.println("A from "+Thread.currentThread().getName()

                                      +" getMyData: "+ myData.getName()+""+myData.getAge());

              }

       }

       staticclass B

       {

              publicvoid get()

              {

                     int data =x.get();

              System.out.println("B from "+Thread.currentThread().getName()+" get data: "+data);

                     MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();

                     System.out.println("B from "+Thread.currentThread().getName()

                                      +" getMyData: "+ myData.getName()+""+myData.getAge());

              }

       }

 

       //将线程的局部变量封装成对象,这样ThreadLocal中存放一个对象就等于存了几个数据

       staticclass MyThreadScopeData

       {

              //单例设计模式

              private MyThreadScopeData(){}

              publicstatic/*synchronized*/ MyThreadScopeData getThreadInstance()

              {

                     MyThreadScopeData instance =map.get();

                     if(instance ==null)

                     {

                            instance =new MyThreadScopeData();

                            map.set(instance);

                     }

                     return instance;

              }

              //private static MyThreadScopeData instance = null;

              privatestatic ThreadLocal<MyThreadScopeData>map =

new ThreadLocal<MyThreadScopeData>();

              

              private Stringname;

              privateintage;

              public String getName() {

                     returnname;

              }

              publicvoid setName(String name) {

                     this.name = name;

              }

              publicint getAge() {

                     returnage;

              }

              publicvoid setAge(int age) {

                     this.age = age;

              }

       }

}

 

07. 多个线程之间共享数据的方式探讨

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

2、如果每个线程执行的代码不同,这时就需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:

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

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

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

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

 

08. java5原子性操作类的应用

1、了解java.util.concurrent.atomic包;

     atomic包文档页下面的介绍,可以对基本数据AtomicInteger,对数组中的基本数据AtomicIntegerArray,对类中的基本数据AtomicIntegerFieldUpdater等进行操作。

 

09. java5线程并发库的应用

Java5中的线程并发库:
2、看java.util.concurrent包及子包的API帮助文档;

concurrent包的帮助文档,对并发库中涉及的内容有一个总体上的介绍。
Executors

此包中所定义的ExecutorExecutorServiceScheduledExecutorServiceThreadFactoryCallable类的工厂和实用方法

3、了解java.util.concurrent.lock

 

线程池:

   线程池的概念与Executors类的应用

      A、理解:在线程池编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池拿到任务后,它就在内部招有无空闲的线程,再把任务交给内部某个空闲线程,这就是封装。记住,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池中提交多个任务。

     B、创建固定大小的线程池:

ExecutorService threadPool = Executors.newFixedThreadPool(3);

          创建缓存线程池:

              ExecutorService threadPool = Executors.newCachedThreadPool();

           创建单一线程池:(如何实现线程死掉后重新启动一个线程)

ExecutorService threadPool = Executors.newSingleThreadExecutor();

       C、关闭线程池:

void

shutdown() 
启动一次顺序关闭,执行以前提交的任务,但不接受新任务。

List<Runnable>

shutdownNow() 
试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

    

 D、用线程池启动定时器:

    调用ScheduleExecutorServiceschedule方法,返回的SchedulFuture对象可以取消任务。

     支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式。

  实例:

Executors.newScheduledThreadPool(3).scheduleAtFixedRate(

                            new Runnable(){@Override

                            publicvoid run() {

                                   System.out.println(Thread.currentThread()+bombing!");

                                   //Thread[pool-2-thread-1,5,main]  bombing!

                            }}, 

                            1,    //10秒后炸

                            2,    //以后每隔2秒炸一次

                        TimeUnit.SECONDS);

 

10. CallableFuture的应用

java.util.concurrent 

接口 Callable<V>

返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做call 的方法。Callable 接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是Runnable 不会返回结果,并且无法抛出经过检查的异常。
接口 Future<V>表示异步计算的结果

//创建线程池

            ExecutorService threadPool2 = Executors.newSingleThreadExecutor();   CompletionService<Integer> completionService =

new ExecutorCompletionService(threadPool2);

              for(int i=1;i<=10;i++) //提交10个任务,任务会返回结果

              {

                     finalint seq = i;

                     completionService.submit(new Callable<Integer>() {

                            public Integer call()throws Exception {

                                   Thread.sleep(new Random().nextInt(5000));//等待5秒以内随机

                                   return seq;

                            }

                     });

              }

              //获取任务执行结果

              for(int i=0;i<10;i++)

              {

                     try {

                     //获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。

                            System.out.println(completionService.take().get());

                     catch (Exception e) {

                            e.printStackTrace();

                     }

                            }

 

 

11. java5的线程锁技术

java.util.concurrent.locks
接口 LockLock 

实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

锁本身也应该是一个对象,两个线程执行的代码片段要实现同步互斥,他们必须使用用一个Lock对象。锁是上在代表要操作的资源的类的内部方法中,而不是线程代码中。

接口 Condition

Condition 将 Object 监视器方法(waitnotify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。 

 

12. java5读写锁技术的妙用

   读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,就上写锁。总之,读的时候上读锁,写的时候上写锁。

 

一个面试题:写一个缓存类。

import java.util.*;

import java.util.concurrent.locks.*;

import java.util.concurrent.locks.ReentrantReadWriteLock;

 

/**

 写一个缓存系统,

 获取数据,当系统中有这个数据时,直接返回这个数据,、

 当没有这个数据时,去数据库中找,找到后缓存起来,

 下次获取这个数据时就直接返回。

 @author Administrator

 *

 */

publicclass CacheDemo

{

       private Map<String, Object>cache = new HashMap<String, Object>();

       publicstaticvoid main(String[] args)

       {

       }

 

       private ReadWriteLockrw1 = new ReentrantReadWriteLock();

       public Object getData(String key)

       {

              rw1.readLock().lock();    //多个线程可同时读

              Object value = null;

              try{

                     value = cache.get(key);

                     if(value ==null//缓存中没有这个对象

                     {

                            rw1.readLock().unlock();

                            rw1.writeLock().lock();   //只有一个线程去从数据库拿

                            try{

                                   //如果3个线程同时来读,到rw1.writeLock().lock();

                                   //有两个线程等待了,当第一个线程已经从数据库中拿数据

                                   //避免另外两个线程醒来时又去拿,还要判断一下

                                   if(value ==null

                                   {

                                         value = "aaaa";  //实际是去queryDB

                                   }

                            }finally{

                                   rw1.writeLock().unlock();

                            }

                            rw1.readLock().lock();

                     }

              }finally{

                     rw1.readLock().unlock();

              }

              return value;  

       }

       

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值