基本的线程机制
1、 定义任务
Runnable的子类必须实现run()方法,但是该方法不会产生任何线程,除非显式的将任务附着到线程上面;
2、 使用Executor
JDK1.5之后,关于线程要优先使用Executor
3、 从任务中产生返回值
使用Callable接口,实现call方法,这里又牵扯到ExecutorService的submit方法,而submit方法又返回Future对象,调用该对象的get方法来获取返回结果,get方法将堵塞直至call方法返回结果
4、 休眠
子线程中异常不能跨线程传播至start该线程的地方,所以你必须在本地处理所有在任务内部产生的异常;
不同操作系统的底层线程机制不同,因而不能依赖休眠来控制任务执行的顺序;
JDK1.5以后新增TimeUnit来提供更加强大的休眠方法;
5、 优先级
这里,有线程优先级、线程所属线程组的概念,参考Android
6、 让步
自己的工作已经做得差不多了,可以让别的线程使用CPU了;
Thread.yield();
7、 后台线程
当所有的非后台线程结束时,程序也就终止了且同时杀死进程中所有后台线程;
Thread实例.setDaemon(true)来设置为后台线程;
如果是一个后台线程,那么它创建的任何线程将被自动设置为后台线程;
后台线程在不执行finally语句的情况下就会终止其run()方法;
8、 捕获异常
Thread中的异常不可能向外抛出,一旦发生异常且run()方法没有try-catch,异常会向外传播到控制台,JDK1.5新增Thread.UncaughtExceptionHandler接口,它允许你在每个Thread对象上都附着一个异常处理器,
Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获的异常而临近死亡时被调用;
Thread实例.setUncaughtExceptionHandler为单个线程设置;
Thread.setDefaultUncaughtExcetionHandler为所有线程设置;
系统会检查线程是否有专有异常处理器,如果没有,则检查线程组是否有专有异常处理器,如果也没有,再调用默认异常处理器即defaultUncaughtExceptionHandler;
共享受限资源
1、 解决共享资源竞争
①共享资源一般是以对象形式存在内存片段中;所有对象都自动含有单一的锁,当调用对象的synchronized方法的时候,此对象被加锁,这时该对象上的其他synchronized方法只能等到前一个方法调用完毕并释放对象锁之后才能被调用,所以,对于某对象来说,所有的synchronized方法共享同一个锁,这可以被用来防止多个任务同时访问被对象所处的内存;
②针对每个类也有一个锁,所以可以用synchronized static方法在类的范围内防止对static数据的并发访问(即解决对static方法的并发访问)
③每一个访问临界共享资源的方法都必须被同步,否则就会不正确的工作;
④使用显式的Lock对象,java.util.concurrent.locks包,Lock对象必须显式创建、锁定和释放;return必须放在try中,lock释放必须写在finally中;
⑤Lock对象可以尝试着来获取锁且最终获取失败,或尝试获取锁一段时间后放弃,而synchronized则不能;Lock对象的2个tryLock方法赋予了Lock对锁更加精细的操作;
2、 原子性与易变性
①原子性:一旦操作开始,那么它一定可以在可能切换到其他线程执行之前执行完毕;
②原子性可以应用于除了long和double之外的所有基本类型上的简单操作,对于读取和写入除了long和double之外的基本类型变量这样的操作,可以保证它们会被当做不可分的操作来操作内存,但是JVM可以将64位的long和double的读取和写入会当做两个分离的32位来执行,这样就可能在读取和写入的时候切换到其他线程
③可视性:一个任务对某变量做出的修改,其他任务不知道,因此造成不同线程状态不同;
④volatile修饰符可以保证原子性和可视性,因为修改volatile修饰的变量,volatile域会立即被写入主存中而读取操作就发生在主存中;
⑤如果多个任务同时访问某个域,则这个域应该是volatile的;
⑥第一选择应该是synchronized,这是最安全的方式;
⑦对域中的值做赋值和返回操作,通常是原子性的;
3、 线程本地存储
防止任务在共享资源上产生冲突的第二种方式就是根除对变量的共享,线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储,创建和管理线程本地存储可以由java.lang.ThreadLocal类来实现
终结任务
1、 在堵塞时终止
①进入堵塞状态:调用sleep方法、调用wait方法、等待获取锁、等待某个输入输出完成
2、 中断
之前通过Thread.interrupt来终止任务,但是会抛出异常且需要你来持有Thread实例,JDK1.5之后通过Executor.submit来获取Futrue实例,然后调用Future实例的cancel(true)来终止任务;
被互斥所阻塞:一个任务应该能够调用同一个对象上的其他synchronized方法,这是因为该任务已经获取了该对象锁;JDK1.5之后新增ReentrantLock上阻塞的任务具备可以被中断的能力这与在synchronized方法或临界区上的阻塞的任务完全不同;
线程之间的协作
1、 wait与notifyAll
①wait使你可以等待某个条件发生变化而改变这个条件超出了当前方法的控制能力
当满足执行条件后调用notify或notifyAll后这个任务才会被唤醒并检查所产生的变化;
②调用sleep/yield的时候锁并没有被释放,调用wait的时候释放锁
③在wait期间对象锁是释放的;可以通过notify/notifyAll/时间到期则从wait中恢复执行;
④由于wait会释放对象锁,因此,在调用wait之前先获取对象锁,因此,wait/notify/notifyAll必须在synchronized方法内调用;
⑤while+wait:检查所感兴趣的特定条件并在条件不满足的情况下再次调用wait,惯用的方法就是使用while来编写这种代码
⑥注意synchronized使用方式
2、 notify与notifyAll
在技术上可能会有多个任务为了争夺同一个对象锁而处于wait状态,因此调用notifyAll比notify更安全,但是如果只有一个任务处于wait状态则可以用notify来代替notifyAll,使用notify时只会从众多等待同一个锁的任务中只有一个会被唤醒,因此如果使用notify则必须保证被唤醒的是恰当的任务;
为了使用notify,所有的任务必须等待相同的条件,如果有多个任务在等待不同的条件,那么你就不会知道是否唤醒了恰当的任务
notifyAll因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒;
5万+

被折叠的 条评论
为什么被折叠?



