JAVA 多线程(读书笔记)


目录

生成线程的两种方法  
线程的中断
线程状态
线程属性
同步
阻塞队列
线程安全集合
callable,Future
执行器
同步器
线程和Swing

 

 


 

生成线程的两种方法
  • 实现Runnable,并作为参数传给Thread

 

  • 重写Tread的run方法

 

中断线程

强制终止线程的stop方法已经被弃用,但还可以用interrupt方法来请求终止线程。

 

如果一个线程被阻塞了,那就无法检查中断状态了,如果在阻塞的线程上调用interrupt,那么就会产生InterruptedException异常,阻塞调用(如sleep或wait)就会被这个异常所终止。

 

如果线程的中断状态被置位后调用sleep,那么sleep抛出一个InterruptedException,同时清除中断状态。

 

interrupted,islnterrupted区别。

线程状态

4个状态:新生,可运行,被阻塞,死亡(new,runnable,blocked,dead)

 

线程属性

设置线程属性setPriority(int newPriority),默认优先级为5

 

调用t.setDaemon(true)设置一个线程为守护线程,唯一作用为其他现成提供服务,比如计时器线程,定时发出"时间滴答"信号给其他线程。

 

线程组概念 TreadGroup g = new ThreadGroup(groupName); Thread t= new Thread(g, threadName);

 

线程的run方法不抛出异常也没有catch语句捕捉异常,但是没有检查的异常可以导致线程死亡,这个异常最后被传递给未捕捉异常处理器处理。处理器实现了接口Thread.UncaughtExceptionHandler。 setUncaughtExceptionHandler可以为每个线程安装处理器,如果没有安装那默认处理器将是null,这样的话处理器就是线程的threadGroup对象,因为threadGroup对象实现了处理器接口,它的uncaughtException方法如下操作:

  • 如果线程有父线程组,则调用父线程组的uncaughtException方法
  • 否则,如果thread.getDefualtExceptionHandler方法返回非空处理器,则调用该处理器。
  • 否则,如果Throwable是个ThreadDeath事例,则什么都不做
  • 否则,线程名字和throwable得堆栈踪迹被输出到System.err上。
同步

大体两种方式实现同步,一种是通过java专门提供的锁类,一种是利用Object自身的锁。

  1. ReentrantLock

 

 

myLock可以产生很多Condition,每个condition代表一个条件,当一个线程拥有这个锁后,判断条件是否满足,如果满足那么继续运行,知道释放锁,再次中间别的线程都不能运行程序断,如果条件不满足,那么此线成被阻塞,释放锁让别的线程得到锁并且运行程序,当别的线程运行完后,可以给因为相关condiition阻塞的线程发送信号,让这些线程重新试图获得锁,运行程序。一旦,被阻塞的线程获得锁并且满足了条件,那么将从被阻塞的地方继续运行代码。

 

也可以使用tryLock方法获取锁(锁测试),如果无法获得那么继续别的工作。

if(myLock.tryLock()){

   //now the thread owns the lock

   try{....}

   finally{myLock.unlock();}

}

else

  //do something else

 

 

    2. synchronized关键字

 

class myObject{

       public void synchronized method1(){}

       public void synchronized method2(){}

}

 

 

每个object都有一个隐性锁,一个object可以有很多被标为synchronized的方法,但是只能同时最多一个方法被另外的多线程运行,因为只有一个锁,当方法1被运行的时候,这个锁已经被一个线程占有,而另外的线程得不到锁,也无法访问方法2。这个隐性锁也只拥有一个condition, wait, notifyAll.

 

锁是可以被重入的,一个获得锁的线程可以再次多次获得这个锁,锁有个hold count持有计数追踪lock方法的嵌套使用.

 

同步块其实跟同步方法一样,先获得obj的隐性锁

synchronized(obj){

critical section

}

 

如果只是定义同步存取的域,那么用volatile 定义域

 

读写锁

ReentrantReadWriteLock rwl= new ReentrantReadWriteLock();

private Lock readLock = rwl.readLock();

private Lock writeLock = rwl.writeLock();

 

public double getTotalBalance(){

   readLock.lock();

   try{...}

   finally{readLock.unLock();}

}

 

public double getTotalBalance(){

   writeLock.lock();

   try{...}

   finally{writeLock.unLock();}

}

 

stop,suspend方法已经被弃用,因为stop会引起对象状态不一致,比如在线程修改对象后存入之前被stop。suspend可能引起死锁,比如挂起了一个拥有锁的线程,这样就不能释放锁,而另一个调用挂起第一个线程的线程要等待这个锁才能继续运行,这样就行成了死锁。

 

阻塞队列

利用特定的队列数据结构实现一种同步机制,但是却不需要显示的线程同步,比如不用锁,和synchronized.例子比如: 把一批任务放进队列里面,多个线程从队列里面取任务来完成或者往队列填任务,如果队列满了,那么线程就得等待不能填充,如果队列空了,提取任务的线程也的等待。

几个类实现了这种数据结构: ArrayBlockingQueue,LinkedBlockingQueue,DelayQueue,PriorityBlockingQueue,BlockingQueue.以及各种放入元素,提取元素的方法。

 

线程安全集合

java.util.concurrent包提供了印象,有序集和队列的高效实现,比如ConcurrentHashMap,ConcurrentSkipListMap,ConcurrentSkipListSet和ConcrrentLinkedQueue这些集合实现了复杂的算法,允许并发访问数据结构的不同部分来是竞争极小化。这些集合返回弱一致性的迭代器,在数据结构发生改变后,不会发出异常,(一般情况下,如果迭代器被构造后,数据发生改变,迭代器就抛出异常)。比如并发HashMap可以同时允许很多写线程。有些还提供原子性的插入关联,和删除关联操作。

 

CopyOnWriteArrayList 和 CopyOnWriteArraySets是线程安全的集合,如果在集合上的迭代器数目多于修改器数据,这种情况下就和适合用。当构建一个迭代器的时候,它包含了一个对当前数组的引用,就算数组后来被修改了,迭代器仍然引用旧数组。所以迭代器拥有一致的视图,减少了很多开销。

 

任何集合类通过同步包装器(synchronizationwrapper)变成线程安全,

List<E> synArrayList=Collections.synchronizedList(new ArrayList<E>());

Map<K,V> synchHashMap=Collections.synchronizedMap(new HashMap<K,V>());

这样下来集合的方法都被锁保护了,提供了线程的安全访问。而且确保没有任何方法通过原始的集合访问数据。如果一个线程修改的时候要进行对集合迭代,那么仍然需要使用封锁:

synchronozed(synchHashMap){

Iterator<K> iter=synchHachMap.keySet().iterator();

}

因为for each循环也使用了迭代器,所以也的被封锁。迭代过程中,如果别的线程进行了修改,那么迭代器失效,并且抛出异常。

 

callable,Future

callable如同runnable同是一个接口,但是不同的是callable的call的方法是返回一定数据类型的结果,而runnable的run方法是不返回的。

 

接口Future是保存异步结果的。get方法是只有在结果已经存在的时候才能显示,否则get方法就会被阻塞,直到结果存在的时候才给出结果。

FutureTask包装器可以把Callable转换成Future和Runnable,同时实现二者的接口。

执行器

多线程虽然可以加快工作的速度,但是如果当线程很多的时候,那么管理线程也是个问题,那么意味着也会消耗更多的资源。那么就用执行器Executors就可以生成多种类型的线程池,有线程池管理维持一定数量的线程运行,有任务的时候给线程池,那么线程池就找一个线程运行,如果所有的线程都在忙,那么就只能让这个任务等待,当没有任务提交的时候那就shudown线程池。

Future<?> submit(Runnable task)

Future<?> submit(Runnable,T result)

Future<?> submit(Callable<T> task)

 

Executors类的静态方法如newScheduledThreadPool和newSingleThreadScheduledExecutor范围的对象为预订执行或者周期性重复执行任务提供了方法。

 

invokeAny是提交全部callable对象,但是只得到一个结果,其他的任务结构都不接收了。invokeAll方法是把所有的callable对象提交,然后得到所有结果组成的列表,缺点是如果第一个任务就需要很多时间,那么更好的方法是,先存储先得到的结果,不按照任务的顺序存储结果,那样可能耽误很多时间。这样就用到了ExecutorCompletionService.他执行了一个blockingQueue的结构,只有在结果可以得到的时候才能take已经完成的任务出来,然后再get得到结果

同步器

信号量: 一个信号量管理一定数量的许可证,线程要想运行就的先申请许可,如果没有许可了,那就不能被通过。其他线程又可以释放线程,任何线程都可以释放许可,不必由获取许可的线程释放。每个线程还可以释放任意多的许可,不过释放的总许可最多只能等于最大许可量。

 

倒计时门栓(CountDownLatch)让一个线程集等待直到计数变为0,是一次性的,一旦为0,就不能复用了。举例,假定一个线程集需要一些初始数据来完成工作,工作线程被启动并且在门外等候,另一个线程准备数据,当数据准备好时,调用countDown,,那么等待的线程就能继续运行了。然后用第二个门栓检查什么时候所有工作器完成工作,用线程集的数量初始化门栓,完成一个线程就减1,一个获取工作结果的线程在门外等待,等所有线程完成后,门栓减为0后,该线程就继续运行。

 

CyclicBarrier类实现了一个栅栏。举例一个大任务分成很多块,由很多线程分别运行一小块,等全部运行完了,然后再组合起来,每个线程运行完后就让他等在栅栏处,如果全都运行完了,栅栏就消失了,所有线程可以继续运行。在所有线程被释放后可以重复用。

 

当两个线程在同一数据缓冲区中的两个实例上工作的时候(来自同一个数据结构)就可以用交换器(Exchanger).经典的情况是,一个线程往缓冲区填入数据,一个线程消费数据,当他们都完成后交换缓冲区。

 

同步队列是一种把生产者与消费者线程皮对的机制,当一个线程调用synchronousQueue的put方法时,他会阻塞直到另一个线程调用take方法为止,反之也是。Exchanger的交换方向只是一个,而他是两个方向都行。

 

线程和Swing

SwingWorker可以让程序在后台运行,等运行完后提交结果刷新界面,而在SwingWorker后台运行的时候,界面不会因为等到长时间的任务运行,而不能响应别的时间。

 

一般情况下,刷新界面等操作界面的任务都放在事件任务分配线程里工作,这样保证所有的界面操作都在一个线程里工作,就不会出现多个线程共同操作界面而出现同步方面的问题。通过EventQueue的两个方法invokeLater,invokeAndWait把放入这两个方法的代码插入到事件分配线程的任务队列里面进行执行。注意这两个方法的区别,invokeLater是启动方法里面的代码后立即返回运行方法后面的代码。而invokeAndWait是启动方法里面的代码,然后并不返回,而是等待方法里面的代码执行完毕后再返回继续运行方法后面的代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值