并发作用
将多核CPU的计算能力发挥到极致,性能得到提升。
面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分。
并发缺点
- 线程安全问题。
- 频繁的上下文切换。
概念
并发与并行
Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once。并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。实际上,如果系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。
同步与异步
主要从消息通知角度来看。
同步是说在调用一个函数后,直到执行完成才返回结果。
异步是在调用一个函数之后,立即返回,等待函数执行完成之后,通过状态、通知和回调来通知调用者。
阻塞与非阻塞
主要从等待返回结果时的状态来看。
阻塞就是在等待返回结果时,当前线程会被挂起,让出CPU,不能执行其他业务。
非阻塞就是在等待返回结果时,当前线程不会阻塞,可以去执行其他的业务。
临界区资源
临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。
线程
创建线程
- 继承Thread
- 实现Runnable
- 实现Callable
1 |
|
线程状态
-
新建(new),创建线程。
-
Runnable。包括Running和Ready两个阶段,Running就是占用CPU运行,Ready是线程还处于等待阶段。
-
阻塞(Blocked)。等待获取临界区资源,一旦他获得了锁就会结束这个状态。
-
无限期等待(Waiting)。
等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
进入方法 退出方法 没有设置 Timeout 参数的 Object.wait() 方法 Object.notify() / Object.notifyAll() 没有设置 Timeout 参数的 Thread.join() 方法 被调用的线程执行完毕 LockSupport.park() 方法 LockSupport.unpark(Thread) -
限期等待(Timed-waiting)。
无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
进入方法 退出方法 Thread.sleep() 方法 时间结束 设置了 Timeout 参数的 Object.wait() 方法 时间结束 / Object.notify() / Object.notifyAll() 设置了 Timeout 参数的 Thread.join() 方法 时间结束 / 被调用的线程执行完毕 LockSupport.parkNanos() 方法 LockSupport.unpark(Thread) LockSupport.parkUntil() 方法 LockSupport.unpark(Thread) -
终止。线程任务结束,或者是产生了异常而终止。
线程基本操作
sleep() and wait()
sleep会休眠当前线程,等到了时间,自动苏醒。
调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
两者区别:
-
sleep到点自己醒,wait需要其他线程调用notify来唤醒。
-
wait()方法必须要在同步方法或者同步块中调用,也就是必须已经获得对象锁。
-
sleep不释放锁,wait释放锁。
join()
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。
yield()
一旦执行,当前线程会让出cpu,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。
sleep()和yield()方法,同样都是当前线程会交出处理器资源,而它们不同的是,sleep()交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而yield()方法只允许与当前线程具有相同优先级的线程能够获得释放出来的CPU时间片。
守护线程
守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
main() 属于非守护线程。
使用 setDaemon() 方法将一个线程设置为守护线程
线程中断问题
线程可以调用interrupt()来中断别的线程。但这个操作并不一定会使线程中断,更像是给了线程一个通知,但具体是否中断还是要看线程本身。
InterruptedException
通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。
interrupted()
如果线程不处在等待或者是阻塞状态,那么直接interrupt是不能中断线程的。但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。
这样,线程就可以对相应的中断请求进行处理了。
1 |
|
近期评论