Java之多线程同步

来源:http://tinyurl.com/y4yd4vsk

前言

本章中线程同步问题会涉及到以下关键字及接口:

  • synchronized 关键字;
  • Lock 接口;
  • ReentrantLock 类;
  • wait()/notify() 方法;
  • volatile 关键字。

线程同步问题引入

测试代码:

Java之多线程同步_第1张图片

 

主程序代码:

Java之多线程同步_第2张图片

主程序,创建RunableThread对象同时启动3个线程

测试结果:初始设置100张票,但运行结果是101张。

问题分析:这是由于程序中当线程名字是“Thread-0”时,线程休眠10ms,这时线程是阻塞的,且并没有将ticket减1;这时其他线程正常运行,因此就会导致线程同步问题。

synchronied关键字

  • 同步代码块:
synchronized (对象/同步监视器){
 // 需要被同步的代码;
}

修改RunableThread类,加入线程同步:

Java之多线程同步_第3张图片

RunableThread类加入synchronized关键字

加上synchronized关注字后,线程就没有同步异常的问题了。

synchronized(obj)中的obj相当于是一个同步锁,没有get到锁的线程不能进入同步,在同步中的线程如果没有运行到synchronized的最后,则不会释放锁。

附注】加上了synchronized线程同步之后,程序的运行速度会明显减慢。

  • 同步方法: 此时会存在一个 隐式锁 this
void synchronized show (String name){
 // 方法体
}

Java之多线程同步_第4张图片

RunableThread升级后

附注:

  1. 当同步方法是非静态方法时,obj锁是它自己this,this被系统隐式处理了;
  2. 当同步方法是静态static时,obj锁是本类.class,在这个例子当中就是RunnableTread.class,class是一个属性;
  3. 静态的同步方法,必须对应静态的共享变量,这里的ticket必须要用static修饰,因为静态方法中是没有this和supper;
  • synchronized缺陷

分析一:

如果获取锁的线程由于要等待IO或者其它原因(比如sleep())被阻塞了,但是又没有释放锁,其它线程便只能等待,这样非常影响程序执行的效率。因此就需要一种机制:可以不让线程一直无期限等待下去(比如只等待一定时间或者能够相应中断),通过Lock就可以办到。

分析二:

当多个线程读写文件时,read-write会发生冲突现象,write-write会发生冲突,但是read-read不会发生冲突。如果采用synchronized,就不能让read-read同时进行,只要有一个线程read,其他想read的线程都只能等待,严重影响效率。因此需要一种机制:使得多个线程都只是read时,线程之间不会发生冲突,通过Lock就可以办到。

另外通过Lock可以知道线程有没有成功获取到锁,这个是synchronized无法办到的。

Lock接口方法

Java之多线程同步_第5张图片

Lock接口方法

ReentrantLock 类

  • lock()/unlock()

使用Lock接口使程序中的应用更加灵活,lock()/unlock()效果和synchronized关键字效果一致。

Java之多线程同步_第6张图片

使用lock()/unlock()改造代码

  • trylock()/unlock()

代码略……

  • lockInterruptibly()-unLock()

代码略……

ReadWriteLock接口(了解)

Java之多线程同步_第7张图片

ReadWriteLock接口

使用读写锁,可以实现读写分离锁定,读操作可以并发进行,写操作锁定单个线程。

如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。

如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

ReadWriteLock接口已实现类ReentrantReadWriteLock。

死锁问题

Java之多线程同步_第8张图片

死锁问题示意图

测试代码:

Java之多线程同步_第9张图片

DeadLockA

Java之多线程同步_第10张图片

DeadLockB

DeadLockA、DeadLockB类的说明:

第一次使用private来修饰构造函数,作用是为了防止对象被主程序new对象

使用public static final修饰LOCK_A和LOCK_B是为了让程序能够静态调用DeadLockA中的LOCK_A和DeadLockB中的LOCK_B,并且LOCK_A、LOCK_B不能够被修改。

Java之多线程同步_第11张图片

code

线程通信

当多个线程同时完成某项特定任务时,多个线程之间需要一定的通信,即线程通信。

线程等待与唤醒所用方法:

  • wait(): 等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中;
  • notify(): 唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意一个(唤醒正在排队等待同步资源的线程中优先级最高者结束等待);
  • notifyAll(): 唤醒全部线程,将线程池中的所有等待线程唤醒。

测试代码:

Java之多线程同步_第12张图片

消费者/生产者

Java之多线程同步_第13张图片

售货员

wait() 方法注意事项:

  1. 在当前线程中调用方法: 对象名.wait();
  2. 使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止;
  3. 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁);
  4. 调用此方法后,当前线程将释放对象监控权 ,然后进入等待;
  5. 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。

notify()/notifyAll()方法注意事项:

  1. 在当前线程中调用方法: 对象名.notify();
  2. 功能:唤醒等待该对象监控权的一个线程;
  3. 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)。

面试题

面试题一:如果拿到synchronized的线程异常退出了,那么等待锁的线程是否会一直等待呢?

答案是否定的,当JVM发现有锁的线程异常了之后会将它的锁自动释放,再由其它等待的线程拿到锁。

面试题二:Lock和synchronized的区别?

  1. Lock是一个接口,不是Java语言内置的,synchronized是java语言内置的关键字。
  2. Lock与synchronized有一点非常大的不同,采用synchronized不需要用户区手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户区手动释放锁,如果没有主动释放锁,就有可能导致出现死锁。

 

觉得本文有用,分享到朋友全给更多的人看到吧!

更多技术交流,欢迎关注本人微信
Java之多线程同步_第14张图片

你可能感兴趣的:(语言,JAVA,多线程,锁,Synchronized,lock)