Java多线程
Java中,可运行的程序都是有一个或多个进程组成。进程则是由多个线程组成的。
最简单的一个进程,会包括mian线程以及GC线程。
线程的状态
线程状态由以下一张网上图片来说明:
在图中,红框标识的部分方法,可以认为已过时,不再使用。
(1)wait、notify、notifyAll是线程中通信可以使用的方法。线程中调用了wait方法,则进入阻塞状态,只有等另一个线程调用与wait同一个对象的notify方法。这里有个特殊的地方,调用wait或者notify,前提是需要获取锁,也就是说,需要在同步块中做以上操作。
(2)join方法。该方法主要作用是在该线程中的run方法结束后,才往下执行。如以下代码:
[html] view plain copy
1. package com.thread.simple;
2.
3. public class ThreadJoin {
4.
5.
6. public static void main(String[] args) {
7.
8. thread= new Thread(new Runnable() {
9.
10. @Override
11. public void run() {
12. System.err.println("线程"+Thread.currentThread().getId()+" 打印信息");
13. }
14. });
15. thread.start();
16.
17. try {
18. thread.join();
19. } catch (InterruptedException e) {
20. // TODO Auto-generated catch block
21. e.printStackTrace();
22. }
23.
24. System.err.println("主线程打印信息");
25.
26. }
27.
28. }
该方法显示的信息是:
线程8 打印信息
主线程打印信息
如果去掉其中的join方法,则显示如下:
主线程打印信息
线程8 打印信息
(3)yield方法。这个是线程本身的调度方法,使用时你可以在run方法执行完毕时,调用该方法,告知你已可以出让内存资源。
其他的线程方法,基本都会在日常中用到,如start、run、sleep,这里就不再介绍。
Synchronized(同步锁)
在Java中使用多线程,你就不能绕过同步锁这个概念。这在多线程中是十分重要的。
在Java多线程的使用中,你必然会遇到一个问题:多个线程共享一个或者一组资源,这资源包括内存、文件等。
很常见的一个例子是,张三在银行账户存有9999元,经过多次的取100,存100后,账户还有多少钱?
看代码:
以下表示账户信息:
[html] view plain copy
1. package com.thread.simple;
2.
3. import java.sql.Time;
4. import java.util.concurrent.TimeUnit;
5.
6. public class Account {
7.
8. private String name;
9. private float amt;
10. public Account(String name,float amt) {
11. this.name=name;
12. this.amt=amt;
13. }
14.
15. public void increaseAmt(float increaseAmt){
16. try {
17. TimeUnit.SECONDS.sleep(1);
18. } catch (InterruptedException e) {
19. // TODO Auto-generated catch block
20. e.printStackTrace();
21. }
22. amt+=increaseAmt;
23. }
24.
25. public void decreaseAmt(float decreaseAmt){
26. try {
27. TimeUnit.SECONDS.sleep(1);
28. } catch (InterruptedException e) {
29. // TODO Auto-generated catch block
30. e.printStackTrace();
31. }
32. amt-=decreaseAmt;
33. }
34.
35. public void printMsg(){
36. System.out.println(name+"账户现有金额为:"+amt);
37. }
38. }
以下是我们操作账户的方法:
[html] view plain copy
1. <span style="white-space:pre"> </span>final int NUM=100;
2.
3. threads=new Thread[NUM];
4. i=0;i<NUM;i++){
5. if(threads[i]==null){
6. threads[i]=new Thread(new Runnable() {
7.
8. @Override
9. public void run() {
10. account.increaseAmt(100f);
11. account.decreaseAmt(100f);
12. }
13. });
14. threads[i].start();
15. }
16. }
17.
18. i=0;i<NUM;i++){
19. try {
20. threads[i].join();
21. } catch (InterruptedException e) {
22. // TODO Auto-generated catch block
23. e.printStackTrace();
24. }
25. }
26.
27. account.printMsg();
你会发现,每次打印出来的账户余额都不一定是一样的。这就是同步锁的必要性。
java中,提供了多种使用同步锁的方式。
(1)对动态方法的修饰。
作用的是调用该方法的对象(或者说对象引用)。
[html] view plain copy
1. public synchronized void doSomething(){}
作用的是调用该方法的对象(或者说对象引用)。
[html] view plain copy
1. public void increaseAmt(float increaseAmt){
2.
3. try {
4. TimeUnit.SECONDS.sleep(1);
5. } catch (InterruptedException e) {
6. // TODO Auto-generated catch block
7. e.printStackTrace();
8. }
9. synchronized (this) {
10. System.out.println(this);
11. amt+=increaseAmt;
12. }
13. }
(3)对静态方法的修饰。
作用的是静态方法所在类的所有对象(或者说对象引用)。
[html] view plain copy
1. public synchronized static void increaseAmt(float increaseAmt){
2. try {
3. TimeUnit.SECONDS.sleep(1);
4. } catch (InterruptedException e) {
5. // TODO Auto-generated catch block
6. e.printStackTrace();
7. }
8. amt+=increaseAmt;
9. }
(4)对类的修饰。
作用的是静态方法所在类的所有对象(或者说对象引用)。
[html] view plain copy
1. synchronized (AccountSynchronizedClass.class) {
2. amt-=decreaseAmt;
3. }
以修饰代码块的方式为例,我们重新运行以上代码后,得到了正确的结果。代码如下:
[html] view plain copy
1. package com.thread.simple;
2.
3. import java.util.concurrent.TimeUnit;
4. /**
5. * Synchronized 代码块
6. * @author 战国
7. *
8. */
9. public class AccountSynchronizedBlock {
10.
11. private String name;
12. private float amt;
13. public AccountSynchronizedBlock(String name,float amt) {
14. this.name=name;
15. this.amt=amt;
16. }
17.
18. public void increaseAmt(float increaseAmt){
19.
20. try {
21. TimeUnit.SECONDS.sleep(1);
22. } catch (InterruptedException e) {
23. // TODO Auto-generated catch block
24. e.printStackTrace();
25. }
26. synchronized (this) {
27. System.out.println(this);
28. amt+=increaseAmt;
29. }
30. }
31.
32. public void decreaseAmt(float decreaseAmt){
33. try {
34. TimeUnit.SECONDS.sleep(1);
35. } catch (InterruptedException e) {
36. // TODO Auto-generated catch block
37. e.printStackTrace();
38. }
39. synchronized (this) {
40. System.out.println(this);
41. amt-=decreaseAmt;
42. }
43.
44. }
45.
46. public void printMsg(){
47. System.out.println(name+"账户现有金额为:"+amt);
48. }
49. }
[html] view plain copy
1. //多线程synchronized修饰代码块 ,每次计算的值都一样
2.
3. account=new AccountSynchronizedBlock("张三", 9999.0f);
4. NUM=50;
5.
6. threads=new Thread[NUM];
7. i=0;i<NUM;i++){
8. if(threads[i]==null){
9. threads[i]=new Thread(new Runnable() {
10.
11. @Override
12. public void run() {
13. account.increaseAmt(100f);
14. account.decreaseAmt(100f);
15. }
16. });
17. threads[i].start();
18. }
19. }
20.
21. i=0;i<NUM;i++){
22. try {
23. threads[i].join();
24. } catch (InterruptedException e) {
25. // TODO Auto-generated catch block
26. e.printStackTrace();
27. }
28. }
29. account.printMsg();
以上是同步锁的简单说明。
在JDK5中,Java又引入了一个相似的概念Lock,也就是锁。功能与synchronized是类似的。
Lock
Lock对比synchronized有高手总结的差异如下:
总结来说,Lock和synchronized有以下几点不同:
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
(参考http://www.cnblogs.com/dolphin0520/p/3923167.html)。
Lock的操作与synchronized相比,灵活性更高,而且Lock提供多种方式获取锁,有Lock、ReadWriteLock接口,以及实现这两个接口的ReentrantLock类、ReentrantReadWriteLock类。
对Lock的简单操作代码如下:
[html] view plain copy
1. package com.thread.simple;
2.
3. import java.util.ArrayList;
4. import java.util.List;
5. import java.util.concurrent.locks.Lock;
6. import java.util.concurrent.locks.ReadWriteLock;
7. import java.util.concurrent.locks.ReentrantLock;
8. import java.util.concurrent.locks.ReentrantReadWriteLock;
9.
10. public class LockImp {
11.
12.
13. lock=new ReentrantLock();
14. rwLock=new ReentrantReadWriteLock();
15.
16. <Integer> list=new ArrayList<Integer>();
17.
18. public void doReentrantLock(Thread thread){
19. lock.lock();
20. System.out.println(thread.getName()+"获取锁");
21. try {
22. i=0;i<10;i++){
23. list.add(i);
24. }
25. } catch (Exception e) {
26.
27. }finally{
28. lock.unlock();
29. System.out.println(thread.getName()+"释放锁");
30. }
31.
32. }
33. public void doReentrantReadLock(Thread thread){
34. rwLock.readLock().lock();
35. System.out.println(thread.getName()+"获取读锁");
36. try {
37. i=0;i<10;i++){
38. list.add(i);
39. }
40. } catch (Exception e) {
41.
42. }finally{
43. rwLock.readLock().unlock();
44. System.out.println(thread.getName()+"释放读锁");
45. }
46.
47. }
48. public void doReentrantWriteLock(Thread thread){
49. rwLock.writeLock().lock();
50. System.out.println(thread.getName()+"获取写锁");
51. try {
52. i=0;i<10;i++){
53. list.add(i);
54. }
55. } catch (Exception e) {
56.
57. }finally{
58. rwLock.writeLock().unlock();
59. System.out.println(thread.getName()+"释放写锁");
60. }
61.
62. }
63.
64.
65.
66. /**
67. * @param args
68. */
69. public static void main(String[] args) {
70.
71. lockImp=new LockImp();
72.
73. thread1=new Thread();
74. thread2=new Thread();
75. thread3=new Thread();
76.
77. new Thread(new Runnable() {
78.
79. @Override
80. public void run() {
81. lockImp.doReentrantLock(thread1);
82. }
83. }).start();
84.
85. new Thread(new Runnable() {
86.
87. @Override
88. public void run() {
89. lockImp.doReentrantLock(thread2);
90. }
91. }).start();
92.
93. new Thread(new Runnable() {
94.
95. @Override
96. public void run() {
97. lockImp.doReentrantLock(thread3);
98. }
99. }).start();
100.
101.
102. lockImp.doReentrantReadLock(thread1);
103. lockImp.doReentrantReadLock(thread2);
104. lockImp.doReentrantReadLock(thread3);
105.
106. lockImp.doReentrantWriteLock(thread1);
107. lockImp.doReentrantWriteLock(thread2);
108. lockImp.doReentrantWriteLock(thread3);
109. }
110.
111. }
Lock的使用中,务必需要lock、unlock同时使用,避免死锁。
线程池的使用
为什么使用线程池?
因为使用它有好处:(1)在界面上,简化了写法,代码更简洁(2)对程序中的线程可以进行适度的管理(3)有效较低了多个线程的内存占有率等。
这是一篇讲述线程池非常好的文章:http://www.cnblogs.com/dolphin0520/p/3932921.html
如果对线程池有不了解的同学,可以参考链接中的文章,讲的深入浅出。
在这里只是简单的封装一个线程池的工具类,仅供参考:
[html] view plain copy
1. package com.thread.simple;
2.
3. import java.util.concurrent.ExecutorService;
4. import java.util.concurrent.Executors;
5.
6. public class ThreadPoolUtil {
7.
8. private volatile static ThreadPoolUtil instance;
9. private ThreadPoolUtil(){}
10. private static ExecutorService threadPool;
11.
12.
13. public static ThreadPoolUtil getInstance(){
14. instance==null){
15. synchronized (ThreadPoolUtil.class) {
16. instance=new ThreadPoolUtil();
17. threadPool=Executors.newCachedThreadPool();
18. }
19. }
20. return instance;
21. }
22.
23. public void excute(Runnable runnable){
24. threadPool.execute(runnable);
25. }
26.
27. public void shutdown(){
28. threadPool.shutdown();
29. }
30.
31. public boolean isActive(){
32. if(threadPool.isTerminated()){
33. return false;
34. }
35. return true;
36. }
37. }