Print service provided by iDogiCat: http://www.idogicat.com/
home logo





Home > IT > Programming > Java > 线程的新特性

线程的新特性

java实现多线程三种方式

  • 继承Thread类实现多线程
  • 实现Runnable接口实现多线程
  • 通过线程池创建线程

等待时wait会释放锁,而slee 一直持有锁。wait后需等待其他线程调用notify或notifyAll,来重新获得控制。wait通常被用于线程间交互,sleep通常被用于暂停执行。

线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用创建线程而是直接去池中拿既存的线程。可节省了创建线程的时间,提高的代码执行效率。

常用的线程池有哪些?

  • newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。The single thread executor is created with 1 core thread and zero non-core threads; as such, there are no threads which can be terminated after the keep alive time: its one core thread remains active until you shut down the entire ThreadPoolExecutor (soon).
  • newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
  • newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。The cached thread pool is created with zero core threads, and an (effectively) unlimited number of non-core threads; as such, any of its threads can be terminated after the keep alive time (60s).
  • newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

合理利用线程池能够带来三个好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的启动策略

  • 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
  • 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
    • 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
    • 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
    • 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
    • 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“不能再接受任务了”。
  • 当一个线程完成任务时,它会从队列中取下一个任务来执行。
  • 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

If we want to use an ExecutorService and submit a Runnable to it, using ThreadLocal will yield non-deterministic results – because we do not have a guarantee that every Runnable action for a given userId will be handled by the same thread every time it is executed.

Because of that, our ThreadLocal will be shared among different userIds. That’s why we should not use a TheadLocal together with ExecutorService. It should only be used when we have full control over which thread will pick which runnable action to execute.

死锁的定义:所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

死锁产生的必要条件:

  • 互斥条件:线程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。
  • 不剥夺条件:线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。
  • 请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
  • 循环等待条件:存在一种线程资源的循环等待链,链中每一个线程已获得的资源同时被链中下一个线程所请求。即存在一个处于等待状态的线程集合{Pl, P2, ..., pn},其中 Pi 等待的资源被 P(i+1)占有(i=0, 1, ..., n-1),Pn 等待的资源被 P0 占有

如何避免死锁

在有些情况下死锁是可以避免的。两种用于避免死锁的技术:

  • 加锁顺序(线程按照一定的顺序加锁)
  • 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

线程通信的方式:

1.共享变量

线程间通信可以通过发送信号,发送信号的一个简单方式是在共享对象的变量里设置信号值。线程A在一个同步块里设置boolean型成员变量hasDataToProcess为true,线程B也在同步块里读取hasDataToProcess这个成员变量。

2.wait/notify 机制

以资源为例,生产者生产一个资源,通知消费者就消费掉一个资源,生产者继续生产资源,消费者消费资源,以此循环。

同步线程及线程调度相关的方法

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException (soon) 异常;
  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
  • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

注意: java 5 通过Lock接口提供了显示的锁机制,Lock接口中定义了加锁(lock()方法)和解锁(unLock()方法),增强了多线程编程的灵活性及对线程的协调

本类的初始化顺序是:静态变量、静态初始化块、变量、初始化块、构造函数

继承类的初始化顺序是:父类静态变量、父类静态初始化块、子类静态变量、子类静态初始块、父类变量、父类初始化块、父类构造函数、子类变量、子类初始化块、子类构造函数。

STOP()和SUSPEND()不推荐使用的原因

stop()是因为它不安全。它会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:synchronized void { x = 3; y = 4;}由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记线程的stop方法,以后我们再也不要说“停止线程”了。而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。

suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。