1、使用线程主要有以下原因:1)解决生成进程导致的性能问题;2)用于同时处理;3)合理利用CPU资源。
2、Java 线程的运行:构造一个Thread类的实例(主要有两种方法),调用其start()方法,如:
- 1
- 2
- 1
- 2
这是一个空壳线程,不做任何事,创建之后就退出。
构造一个Thread类的实例的两种方法:1)派生Thread的子类,覆盖run()方法;2)实现一个Runnable接口,将Runnable对象传递给Thread构造函数。采用方法2)更能区分线程和线程执行的任务。
3、(派生,extends)Thread类有多个方法,采用派生Thread并覆盖run()方法时,不能覆盖其他方法,如start(),stop(),interrupt(),join(),sleep()等等。简单示例:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
当然这个示例也没有实际应用,因为没有利用多线程完成I/O和CPU之间的平衡问题。仅作为示例
4、(实现接口,implements)为避免覆盖标准Thread中的其他方法,可以将任务编写为Runnable的一个实例。将上述代码修改一下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
这样就能创建线程了。
那线程之间如何交互?
5、Thread类中run()和start()方法既不接收参数也不返回结果。线程接收参数容易实现,不管是派生子类还是实现Runnable接口,都可以在构造函数中将参数传递进去;那线程运行的结构如何返回?
不好的方法——1):派生子类中实现一个public type[] getResult()的方法,这样在线程运行结束的时候得到结果。问题在于不知线程结束时间,当调用getResult()的时候,也许run()还没有运行完。
不太好的方法——2):轮询,既当run()方法调用结束后,修改子类某字段或者是有个标志。时间效率太差。
比较好的方法——3):回调。要想获得线程运行之后的结果,不一定去请求线程的方法,也可以用线程去通知主程序。回调也有两种方法,一是静态方法,二是回调实例方法。静态方法虽然简单,但是不够灵活。以下假如主程序通过调用其它线程分析字符串中的计算式子,得到结果,并打印。
调用静态方法的伪代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
而调用实例方法的伪代码:仅列出改变的地方
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
这里没有将calculate()方法放在构造方法里,主要是担心构造方法未完成calculate里的线程便执行结束。
回调实例函数虽然有点复杂,但是多个类都关心线程运行结果时,回调静态函数就无能为力了,而如果多个类都实现了一个统一的接口用于接收结果,则可以向多个对象发送运行结果。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
调用者类先构造一个ListCallBack,并将自己添加到listenerList中,再运行线程。
6、同步问题。由synchronized关键词修饰,可修饰对象或者方法。
而同步容易导致死锁,所以同步尽量少使用。同步使用的情景一般有全局变量,所以减少全局变量可以使同步减少。
7、线程调度。
线程优先级有1~10,数字大优先级高,与一般情况不同。Thread中有getPriority()和setPriority()两个方法。
线程调度的时机:1)阻塞;2)显示放弃(Thread的yield()方法);3)休眠(Thread 的sleep()方法,精度依赖平台);4)连接线程,Thread的join()方法,某线程a在其代码中调用线程t的join()方法,其后的代码要在线程t运行结束再运行;5)等待一个对象,调用Object的wait()方法,每个对象都有,当此对象的notify()方法调用之后,运行wait()之后的代码,另外wait()方法也可以传入时间参数,即等待一段时间就运行其后代码;6)当线程结束也进行线程调度。而调用sleep,join,wait方法都有被中断(interrupt)的可能,一旦捕获到中断异常,则执行catch(InterruptException ex)里的代码。
8、线程池。
尽管线程与进程相比开销小很多,但是频繁创建与删除依然影响性能。虽然线程停止之后不能重启,但是可以进行改造。首先保存一个全局的任务池并创建固定数量的线程。当池为空时,线程等待;当向池中添加一个任务时,所有等待线程得到通知。当一个线程结束其分配的任务时,再去从池中获取新的任务。
需要注意的是,如何告知程序已经结束。一般需要再设置两个全局变量,一个是总任务数,一个是已经完成的任务数。