对自己这段时间学习的多线程技术做个记录:
1、为什么要使用多线程?
我认为使用多线程的初衷就是为了使程序更合理的利用硬件资源,记得曾经在一本书上看到过,目前的硬件技术至少超前了软件技术10年。
2、Thread类的API有哪些?
1)、start()和run()
当调用start()方法后会通知“线程规划器”此线程已准备就绪,等待执行run()方法,执行顺序并不会按照调用start()方法的顺序执行,具有随机性。而真正处理任务逻辑的是run()方法,由当前线程直接调用,必须等run()结束之后(同步执行)才能执行后续的代码。
2)、wait()和sleep()
这两个方法来源不一样,wait()来源于Object类,sleep()方法来源于Thread类。在调用wait()方法之前,需要获取对象级别的锁,也就是说wait()方法必须在synchronized修饰的方法或代码块或使用Lock加锁的代码里使用,调用wait()方法之后,会释放对象锁,让出CPU让其它线程执行,直到被notify()或notifyAll()唤醒,而sleep()方法不会出让CPU资源,只是暂时的休眠,需要指定休眠时间,时间一到自动被唤醒。
3)、wait()、notify()和notifyAll()
wait()在第二点中已经说明了,notify()方法同样也是来源于Object类,它的作用是随机唤醒一个执行了wait()方法的线程,因为不确定有多少线程处于等待状态和唤醒的随机性,所以一般不推荐使用此方法,而是使用notifyAll()方法,通知所有处于等待状态的线程。
4)、stop()、suspend()和resume()
这三个方法虽然已经被弃用了,但觉得还是有必要了解一下。
调用stop()方法后会将锁释放,破坏线程安全,所以不适用,替代方案是使用interrupt()来终止线程。
调用suspend()方法获取的是Object对象锁(独占锁),这将导致其它的线程都无法获取锁,全部处于阻塞状态,并且其它线程无法使其恢复,只有当当前线程调用resume()后其它线程才能去竞争获取锁。
5)、其它API
join():使两个线程变成同步执行。
isAlive():判断线程是否处于活动状态。
yield():让出CPU,让其它线程去执行,让出时间具有随机性。
interrupted()和isInterrupt():判断线程是否终止。
3、停止线程的方法有哪些?
1)、正常的退出:等待run()方法结束
2)、暴力的停止:调用stop()停止线程,不建议使用,原因在第2点4)中已经说明了。
3)、优雅的停止:通过调用interrupt()方法,这个时候线程并不会真正的停止,还是处于RUNNABLE状态,需要调用interrupted()或isInterrupt()方法判断线程是否真正停止了,然后再结束当前线程,结束方式主要有以下两种:
a)、return方式:当线程真正停止后,直接return结束掉,这种方式的缺点就是让人感觉线程戛然而止,不知道什么时候结束了。
b)、异常法(推荐):当线程真正停止后,抛出throw new interruptedException()异常,这样就很清晰的知道线程在哪个节点结束了。
4、线程状态之间的切换图(state枚举类:NEW,RUNNABLE,BLOCK,WATING,TIMED_WATING,TERMINATED)
5、synchronized、Lock、ReadWriteLock的区别?
synchronized是Java关键字,用于修饰方法和代码块而达到同步的效果,保证线程安全,一般用于处理任务比较短的业务。
Lock是个接口,其实现类是ReentrantLock,用法要比synchronized灵活,可以设置锁超时时间,但不会自动释放锁(synchronized可以),需要在finally里手动释放,一般适用于处理任务比较长的场景。
ReadWriteLock读写锁是对前面两种锁处理任务同步效率低的一种补充吧,读的操作可以共享一把锁,异步执行读操作提升效率(乐观锁原理),而写锁与前面两种类似(读读共享,写写互斥,读写互斥,写读互斥),一般用于读多写少的情况。