`

黑马程序员Java培训和Android培训Java技术六

 
阅读更多
黑马程序员
五十七
定义了一个静态内部类的话,如果在方法主函数中创建了一个这个静态内部类的对象,这个对象要马上调用这个内部静态类的一个方法。


在某个类中用static扩住的代码,在程序运行的首次仅仅加载一次。

五十八
分割图片
要有揣测能力。
两个线程要想同步,不许有相同的监视器。很好理解,就比方说是有同一个长官领导下的。当然有类监视器,也有对象监视器。

五十九
ThreadLocal实现线程范围的共享变量
Thread的作用与目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。
每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值,在线程结束时要记住调用ThreadLocal.clear()方法。
ThreadLocal应用场景:
1订单处理包含一系列操作:减少库存量,增加一条流水台帐,累加公司应收款,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。
2银行转帐包含以系列操作:把转出的帐户余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。
3例如Struct2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管用getContext方法多少次和在哪个模块中getContext方法,拿到的都是同一个。
实验案例:定义一个全局变量的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量的值,就可以看到多个类在同一个线程中共相同一份数据。

实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal变量。
1对基本类型的数据的封装,这种应用相对很少见。
2对对象类型的数据的封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。




















实验步骤:
1先在MyThreadData类中定义一个访问权限为public的ThreadLocal类型的变量x,直接对这个x进行读写操作;
2将变量的x的访问权限定义为private,MyThreadLocalData上定义相应的set和get方法对向变量x中存储和检索数据;
3将MyThreadLocalData类自身变成一个具有业务功能的对象,但每个线程仅有该类的一个实例对象,即对于不同的线程来说,MyThreadLocalData.getMyData静态方法拿到的对象都不同,但对于同一个线程来说,不管调用MyThreadData.getMydata多少次和在哪里调用,拿到的都是同一个MyThreadData对象。先将MyThreadData封装成具有业务功能的对象,然后设计getMyData方法的定义,最后定义getMyData方法要操作的ThreadLocal变量和编写具体的代码。
ThreadLocal类的应用举例:
public class ThreadLocalTest{

   public static void main(String[] args)
   {
     ExecutorService service=Executor.newFixedThreadPool(2);
     final A a=new A();
     final B b=new B();
   }

六十
Java5中的线程并发库
1看java.util.concurrent包及子包的API帮助文档
2线程池的概念与Executors类的应用
---创建固定大小的线程池
---创建缓存线程池
---创建单一线程池
关闭线程池
shutdown与shutdownNow的比较

ScheduleExecutorService


在int变量之前加上volatile关键字后,当操作该变量时则具有互斥性。等效于在synchronized方法中使用该变量。


看包的API帮助文档,可以先找到该包下的某个类的帮助页面,然后在该页面的顶部单击package超链接。了解atomic和lock这两个子包。还有locks子包。

固定大小的线程池&缓存线程池-------------
步骤1:用3个大小的固定线程池去执行10个内部循环10次就结束任务,为了观察固定线程池下的其他任务一直再等待,希望打印出正在执行的线程名,任务号和任务内部的循环次数,刚开始看到只有3个线程在执行,并看到任务前赴后继的效果。注意:这10个任务要用各自独立的runnable对象,才能看到任务的序号。
步骤2:改为缓存线程池,可以看到当前有多个任务,就会分配多少个线程为之服务。

六十一
Callable&Future
Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的。
Callable要采用ExecutorService的submit方法提交,返回的future对象可以取消任务。
CompletionService用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象。

六十二
Lock&Condition实现线程同步通信
两个线程执行的代码片段要实现同步互斥的效果,他们必须用同一个Lock对象。

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

在等待Condition时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为Condition应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。

一个锁内部可以有多个Condition,即有多路等待和通知,可以参看jdk1.5提供的

用finally解锁。

六十三
线程通信
1.5中的condition的引入,解决了以前只能引入一个信号量的问题,1.5以后可以引入多个。

六十四
Semaphhore实现信号灯
Semaphore可以轻松完成信号量控制,即控制同时访问的线程个数,例如,实现一个文件允许的并发访问数。

Semaphore可以维护当前访问自身的线程个数,并提供了同步机制。

与阻塞队列有些相似,但也不同,阻塞队列是一方存放数据,另一方释放数据,Semaphore通常则是由同一放设置和释放信号量。

我的理解:Lock类的设置是为了将代码中的不必要的并发性消除,实现原子性。



其他同步工具类
CyclicBarrier:表示大家彼此等待,大家结合好后才开始出发,分散活动后又在指定的地点集合碰面。

CountDownLatch:表示一个人(也可以是多个人)等待其他所有人来通知他,这犹如一个计划需要多个领导都签字后才能继续向下实施。还可以实现一个人通知多个人的效果,类似裁判一声口令,运动员开始奔跑。

Exchanger
用于实现两个人之间的数据交换,每个人在完成一定的事物 后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。

六十五
可阻塞的队列
ArrayBlockingQueue
---只有put方法和take方法才具有阻塞功能

用了三个空间的队列来演示阻塞队列的功能和效果。

用两个具有1个空间的队列来实现同步通知的功能。


同步集合
传统方式下用Collections工具类提供的方法来获得同步集合。
Java5中提供了如下一些同步集合类:
---ConcurrentHashMap
---CopyOnWriteArrayList
---CopyOnWriteArraySet

同步方法synchronizedCollection



BlockingQueue类 ArrayBlockingQueue类
public class ArrayBlockQueue{

public static void main(){
final BlockingQueue queue=new ArrayBlockQueue(3);

for(int i=0;i<2;i++){
new Thread(){
public void run(){

  while(true){

    try{

      Thread.sleep((long)(Math.random()*1000));
      System.out.println(Thread.currentThread().getName()+"准备放数据!");
      queue.put(1);
      System.out.println(Thread.currentThread().getName()+"已经放好了数据,"+"队列目前有"+queue.size()+"个数据");

       }catch(InterruptedException e){
            e.printStackTrace();
       }

}
}
}.start();
}


new Thread(){

     public void run(){
         while(true){
              try{
                
                 Thread.sleep(100);
                 System.out.println(Thread.currentThread().getName()+"准备取数据!");
                 queue.take();
                 System.out.println(Thread.currentThread().getName()+"已经取走数据"+"队列目前有"+queue.size()+"个数据");  

                 } catch{
                     e.printStackTrace();
                 }
          
}
         }   
}
}
}

深拷贝就是实现java.lang.Cloneable接口,然后重写clone()方法,最简单的方法就是在clone()方法中直接把对象序列化到磁盘上再反序列化回来,这样不用判断就可以得到一个深copy的结果。如果不了解序列化的作法,可以看一下ObjectOutputstream和ObjectInputStream.

序列化就是把一个Java对象转换成二进制进行磁盘上传输或者网络流行的传输,反序列化的意思就是把这个接受到的二进制流重新组装成原来的对象逆过程。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics