Java多线程简析——Synchronized(同步锁)、Lock以及线程池

Java多线程简析——Synchronized(同步锁)、Lock以及线程池

 

Java多线程

Java中,可运行的程序都是有一个或多个进程组成。进程则是由多个线程组成的。

最简单的一个进程,会包括mian线程以及GC线程。

线程的状态

线程状态由以下一张网上图片来说明:

在图中,红框标识的部分方法,可以认为已过时,不再使用。

(1)wait、notify、notifyAll是线程中通信可以使用的方法。线程中调用了wait方法,则进入阻塞状态,只有等另一个线程调用与wait同一个对象的notify方法。这里有个特殊的地方,调用wait或者notify,前提是需要获取锁,也就是说,需要在同步块中做以上操作。

(2)join方法。该方法主要作用是在该线程中的run方法结束后,才往下执行。如以下代码:

 

 
  1. package com.thread.simple;

  2.  
  3. public class ThreadJoin {

  4.  
  5.  
  6. public static void main(String[] args) {

  7.  
  8. Thread 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后,账户还有多少钱?

看代码:

以下表示账户信息:

 

 
  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. }


以下是我们操作账户的方法:

 

 

 
  1. <span style="white-space:pre"> </span>final int NUM=100;

  2.  
  3. Thread[] threads=new Thread[NUM];

  4. for(int 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. for(int 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)对动态方法的修饰。

作用的是调用该方法的对象(或者说对象引用)。

 

public synchronized void doSomething(){}

 

  ( 2)  对代码块的修饰。

作用的是调用该方法的对象(或者说对象引用)。

 

 
  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)对静态方法的修饰。

 

作用的是静态方法所在类的所有对象(或者说对象引用)。

 

 
  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)对类的修饰。

 

作用的是静态方法所在类的所有对象(或者说对象引用)。

 

 
  1. synchronized (AccountSynchronizedClass.class) {

  2. amt-=decreaseAmt;

  3. }


以修饰代码块的方式为例,我们重新运行以上代码后,得到了正确的结果。代码如下:

 

 

 
  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. }

 

 

 
  1. //多线程synchronized修饰代码块 ,每次计算的值都一样

  2.  
  3. final AccountSynchronizedBlock account=new AccountSynchronizedBlock("张三", 9999.0f);

  4. final int NUM=50;

  5.  
  6. Thread[] threads=new Thread[NUM];

  7. for(int 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. for(int 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的简单操作代码如下:

 

 
  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. private Lock lock=new ReentrantLock();

  14. private ReadWriteLock rwLock=new ReentrantReadWriteLock();

  15.  
  16. private List<Integer> list=new ArrayList<Integer>();

  17.  
  18. public void doReentrantLock(Thread thread){

  19. lock.lock();

  20. System.out.println(thread.getName()+"获取锁");

  21. try {

  22. for(int 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. for(int 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. for(int 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. final LockImp lockImp=new LockImp();

  72.  
  73. final Thread thread1=new Thread();

  74. final Thread thread2=new Thread();

  75. final Thread 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. }

  112.  


Lock的使用中,务必需要lock、unlock同时使用,避免死锁。

 

 

线程池的使用

为什么使用线程池?

 

因为使用它有好处:(1)在界面上,简化了写法,代码更简洁(2)对程序中的线程可以进行适度的管理(3)有效较低了多个线程的内存占有率等。

这是一篇讲述线程池非常好的文章:http://www.cnblogs.com/dolphin0520/p/3932921.html

如果对线程池有不了解的同学,可以参考链接中的文章,讲的深入浅出。

在这里只是简单的封装一个线程池的工具类,仅供参考:

 

 
  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. if(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. }

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值