Java 多线程(四)——线程同步(synchronized、ReentrantLock)
2016-12-26 00:00
786 查看
同步,是指协同步调,按预定的先后次序进行运行。而不是从字面上理解的“一起工作”。
#1 一个线程安全问题
银行取钱问题:使用两个线程来模拟两个人对同一账户取钱操作。
运行上面程序,有可能出现如下结果,这是不希望出现的错误:
#2 synchronized关键字实现同步
使用***对象互斥锁***来保证共享数据操作的完整性。每个对象都对应一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只有一个线程访问该对象。使用***synchronized***关键字与对象互斥锁联系。
##2.1 同步代码块
Java多线程引入同步监视器来解决多线程并发访问问题:
其中,obj就是同步监视器。线程开始执行同步代码之前,必须先获得对同步监视器的锁定,即获得对象的互斥锁。
同步监视器的目的是阻止多个线程对统一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。
改写上面的DrawThread类的run()方法为同步代码块:
##2.2 同步方法
synchronized关键字还可以放到方法声明中,表示整个方法为同步方法,例如:
运行结果与synchronized修饰代码块一样。
对于synchronized修饰的实例方法(非static方法),无须显示指定同步监视器,同步方法的同步监视器是this,即调用该方法的对象。
##2.3 释放监视器的锁定
线程进入同步代码块(方法)之前,会对同步监视器加锁。程序无法显示释放对同步监视器的锁定,当出现如下情况时会释放:
同步方法(代码块)执行结束时
在同步方法(代码块)中遇到break、return等终止语句
在同步方法(代码块)中出现了未处理的Error或Exception,导致异常结束
在同步方法(代码块)中执行了同步监视器对象的wait()方法,当前线程暂停,释放同步监视器
线程不会释放同步监视器的情况:
线程执行同步方法(代码块)时,程序调用了Thread.sleep()、Thread.yield()方法来暂停当前线程的执行
线程执行同步方法(代码块)时,其他线程调用了该线程的suspend()方法将该线程挂起
#3 同步锁(Lock对象)实现同步
同步锁由Lock对象充当,通过显示定义同步锁对象来实现同步。
Lock是控制多个线程对共享资源进行访问的工具,比较常用的是ReentrantLock(可重入锁),该Lock对象可以显式地加锁、释放锁。
ReentrantLock锁具有可重入性,即一个线程可以对已被加锁的ReentrantLock锁再次加锁。
改写上面Account类:
使用Lock与使用synchronized同步代码块相似,同样符合“加锁、修改、解锁”的逻辑。只是Lock是显式使用Lock对象作为同步锁,而synchronized同步代码块是使用当前对象作为同步监视器。
#4 死锁
当两(多)个线程互相等待对方释放资源(同步监视器)时就会发生死锁。一旦发生死锁,整个程序既不会发生任何异常,也不会出现任务提示,只是所有线程都处于阻塞状态,无法继续。
例如:
Java中Synchronized的用法
#1 一个线程安全问题
银行取钱问题:使用两个线程来模拟两个人对同一账户取钱操作。
package thread; /** * Created by Zen9 on 2016/3/9. */ public class DrawTest { public static void main(String[] args) { //创建一个账户 Account account = new Account(1000); //模拟两个线程对同一个账户取钱 new DrawThread("A",account,800).start(); new DrawThread("B",account,800).start(); } } //账户 class Account{ //余额 private double balance; public Account(double balance){ this.balance = balance; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } public void draw(double drawAmount){ if (balance >= drawAmount){ System.out.println(Thread.currentThread().getName() + "取钱成功!取出金额为:" + drawAmount); try{ Thread.sleep(1); }catch (InterruptedException ex){ ex.printStackTrace(); } balance -= drawAmount; System.out.println("余额为:" + balance); }else { System.out.println(Thread.currentThread().getName() + "余额不足!"); } } } //取钱线程 class DrawThread extends Thread{ private Account account; private double drawAmount; public DrawThread(String name,Account account,double drawAmount){ super(name); this.account = account; this.drawAmount = drawAmount; } @Override public void run() { account.draw(drawAmount); } }
运行上面程序,有可能出现如下结果,这是不希望出现的错误:
#2 synchronized关键字实现同步
使用***对象互斥锁***来保证共享数据操作的完整性。每个对象都对应一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只有一个线程访问该对象。使用***synchronized***关键字与对象互斥锁联系。
##2.1 同步代码块
Java多线程引入同步监视器来解决多线程并发访问问题:
synchronized(obj) { //同步代码块 ... }
其中,obj就是同步监视器。线程开始执行同步代码之前,必须先获得对同步监视器的锁定,即获得对象的互斥锁。
同步监视器的目的是阻止多个线程对统一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。
改写上面的DrawThread类的run()方法为同步代码块:
//取钱线程 class DrawThread extends Thread{ private Account account; private double drawAmount; public DrawThread(String name,Account account,double drawAmount){ super(name); this.account = account; this.drawAmount = drawAmount; } @Override public void run() { //使用account作为同步监 3ff0 视器,任何线程进入同步代码之前 //必须先获得account对象的锁定,其他线程无法获得锁 // 逻辑:“加锁、修改、释放锁” synchronized (account) { //////修改之处/////// account.draw(drawAmount); } } }
##2.2 同步方法
synchronized关键字还可以放到方法声明中,表示整个方法为同步方法,例如:
//账户 class Account{ //余额 private double balance; public Account(double balance){ this.balance = balance; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } //synchronized修饰方法 synchronized public void draw(double drawAmount){ //////修改之处/////// if (balance >= drawAmount){ System.out.println(Thread.currentThread().getName() + "取钱成功!取出金额为:" + drawAmount); try{ Thread.sleep(1); }catch (InterruptedException ex){ ex.printStackTrace(); } balance -= drawAmount; System.out.println("余额为:" + balance); }else { System.out.println(Thread.currentThread().getName() + "余额不足!"); } } } //取钱线程 class DrawThread extends Thread{ private Account account; private double drawAmount; public DrawThread(String name,Account account,double drawAmount){ super(name); this.account = account; this.drawAmount = drawAmount; } @Override public void run() { account.draw(drawAmount); } }
运行结果与synchronized修饰代码块一样。
对于synchronized修饰的实例方法(非static方法),无须显示指定同步监视器,同步方法的同步监视器是this,即调用该方法的对象。
##2.3 释放监视器的锁定
线程进入同步代码块(方法)之前,会对同步监视器加锁。程序无法显示释放对同步监视器的锁定,当出现如下情况时会释放:
同步方法(代码块)执行结束时
在同步方法(代码块)中遇到break、return等终止语句
在同步方法(代码块)中出现了未处理的Error或Exception,导致异常结束
在同步方法(代码块)中执行了同步监视器对象的wait()方法,当前线程暂停,释放同步监视器
线程不会释放同步监视器的情况:
线程执行同步方法(代码块)时,程序调用了Thread.sleep()、Thread.yield()方法来暂停当前线程的执行
线程执行同步方法(代码块)时,其他线程调用了该线程的suspend()方法将该线程挂起
#3 同步锁(Lock对象)实现同步
同步锁由Lock对象充当,通过显示定义同步锁对象来实现同步。
Lock是控制多个线程对共享资源进行访问的工具,比较常用的是ReentrantLock(可重入锁),该Lock对象可以显式地加锁、释放锁。
ReentrantLock锁具有可重入性,即一个线程可以对已被加锁的ReentrantLock锁再次加锁。
改写上面Account类:
//账户 class Account{ private double balance; public Account(double balance){ this.balance = balance; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } //定义锁对象 private final ReentrantLock lock = new ReentrantLock(); //////修改之处/////// public void draw(double drawAmount){ //加锁 lock.lock(); //////修改之处//////// try{ if (balance >= drawAmount){ System.out.println(Thread.currentThread().getName() + "取钱成功!取出金额为:" + drawAmount); try{ Thread.sleep(1); }catch (InterruptedException ex){ ex.printStackTrace(); } balance -= drawAmount; System.out.println("余额为:" + balance); }else { System.out.println(Thread.currentThread().getName() + "余额不足!"); } }finally { //释放锁 lock.unlock(); //////修改之处/////// } } }
使用Lock与使用synchronized同步代码块相似,同样符合“加锁、修改、解锁”的逻辑。只是Lock是显式使用Lock对象作为同步锁,而synchronized同步代码块是使用当前对象作为同步监视器。
#4 死锁
当两(多)个线程互相等待对方释放资源(同步监视器)时就会发生死锁。一旦发生死锁,整个程序既不会发生任何异常,也不会出现任务提示,只是所有线程都处于阻塞状态,无法继续。
例如:
package thread; /** * Created by Zen9 on 2016/3/9. */ public class DeadLockTest { public static void main(String[] args) { DeadLock deadLock = new DeadLock(); new Thread(deadLock).start(); deadLock.init(); } } class DeadLock implements Runnable{ A a = new A(); B b = new B(); public void init(){ Thread.currentThread().setName("主线程"); a.Amethod(b); System.out.println("进入主线程之后"); } @Override public void run() { Thread.currentThread().setName("副线程"); b.Bmethod(a); System.out.println("进入副线程后"); } } class A{ //进入该方法前,对A实例加锁 synchronized public void Amethod(B b){ System.out.println(Thread.currentThread().getName() + " 进入A实例的Amethod方法"); try { Thread.sleep(200); }catch (InterruptedException ex){ ex.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 企图调用B的B_another_method方法"); //视图调用B实例的方法,但B实例已被锁定 b.B_another_method(); } synchronized public void A_another_method(){ System.out.println("进入A类的A_another_method方法"); } } class B{ //进入该方法时,对B实例加锁 synchronized public void Bmethod(A a){ System.out.println(Thread.currentThread().getName() + " 进入B实例的Bmethod方法"); try { Thread.sleep(200); }catch (InterruptedException ex){ ex.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 企图调用A的A_another_method方法"); //视图调用A实例的方法,但A实例已被锁定 a.A_another_method(); } synchronized public void B_another_method(){ System.out.println("进入B类的B_another_method方法"); } }
5 其他
关于Synchronized关键字的其他文章Java中Synchronized的用法
相关文章推荐
- Java 多线程(四)——线程同步(synchronized、ReentrantLock)
- Java 多线程(四)——线程同步(synchronized、ReentrantLock)
- Java:多线程,线程同步,同步锁(Lock)的使用(ReentrantLock、ReentrantReadWriteLock)
- 【Java基础之线程同步(二)】java线程同步:synchronized关键字,Lock接口以及可重入锁ReentrantLock
- Java多线程synchronized、ReentrantLock、ReentrantReadWriteLock 和StampedLock 的对比
- 高级java必会系列二:多线程经常使用的3个关键字:synchronized、ReentrantLock、volatile
- java多线程 21 : ReentrantReadWriteLock ,synchronized和ReentrantLock的对比
- 多线程拨号Java版(探讨synchronized和Lock线程同步)
- java多线程基础---synchronized与ReentrantReadWriteLock的介绍与比较
- java多线程之:Java中的ReentrantLock和synchronized两种锁定机制的对比 (转载)
- Java:多线程,线程同步,同步锁(Lock)的使用(ReentrantLock、ReentrantReadWriteLock)
- java多线程基础---synchronized与ReentrantReadWriteLock的介绍和比较
- Java多线程——锁(Synchronized、Lock、ReentrantLock、ReadWriteLock、ReentrantReadWriteLock)
- Java中的ReentrantLock和synchronized两种锁定机制的对比
- Java中的ReentrantLock和synchronized两种锁定机制的对比
- Java中的ReentrantLock和synchronized两种锁定机制的对比 .
- Java 并发编程中使用 ReentrantLock 替代 synchronized 关键字原语
- Java中的ReentrantLock和synchronized两种锁定机制的对比
- Java多线程(九)之ReentrantLock与Condition (2013-02-19 16:43:54)
- JAVA可重入锁ReentrantLock和synchronized关键字