Java线程同步方式
本文部分内容来自https://www.geek-share.com/detail/2618399427.html
为什么要使用同步?
Java与允许多线程并发控制,当多个线程同时操作共享变量时,将会导致数据的不准确,相互之间产生冲突,使用同步可以保证共享变量的准确性和唯一性。
1、同步代码块
使用synchronized修饰的语句块。
被修饰的语句块会自动加上内置锁,从而实现同步。
synchronized(obj){ }
其中obj是同步监视器,它的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时刻只能有一个进程可以获得同步监视器的锁定,当同步代码被执行完毕后会自动释放同步监视器的锁定。
一般将可能被访问的共享资源作为同步监视器。
代码:
Account类
public class Account{ private String accountNo; private double balance; public Account(){} public Account(String accountNo,double balance){ this.accountNo = accounNo; this.balance = balance; } //getter和setter方法 public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } public Double getBalance() { return balance; } public void setBalance(Double balance) { this.balance = balance; } //重写equals方法 public boolean equals(Object o){ if(this==o){ return true; } if(o==null||getClass()!=o.getClass()){ return false; } Account account = (Account)o; return accountNo.equals(account.accountNo); } }
DrawThread类
public class DrawThread extends Thread{ private Account account; private double drawAmount; public DrawThread(){} public DrawThread(String name,Account account,double drawAmount){ super(name); this.account = account; this.drawAmount = drawAmount; } public void run(){ //用account作为同步监视器 synchronized(account){ //如果余额大于等于去的钱数 if(account.getBalance>=drawAmount){ System.out.println(getName()+"取钱成功,吐出钞票:"+drawAccount); try { Thread.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.getStackTrace(); }; //更改余额(实际上余额不应该随意更改) account.setBanlance(account.getBanlance()-drawAmount); System.out.println("余额为:"+account.getBanlance()); }else{ System.out.println(getName()+"取钱失败,余额不足"); } } } }
测试类
public class test{ public static void main(Stringp[] args){ Account acct = new Account("1234567",(double)1000); new DrawThread("甲",acct,800).start(); new DrawThread("乙",acct,800).start(); } }
2、同步方法
使用synchronized关键字修饰的方法
由于Java对象有内置锁,因此使用此关键字修饰方法时内置锁会保护整个方法。在调用该方法时需取得内置锁,否则就处于阻塞状态。
同步方法无须显式的指定同步监视器,可以很方便的实现线程安全的类。
注:静态方法也可以用synchronized修饰,此时整个类会被锁住。
代码:
Account类
public class Account{ String accountNo; double balance; public Account(){} public Account(String accountNo,double banlance){ this.accountNo = accountNo; this.balance = balance; } //getter和setter方法 public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } //账户余额不能随便更改,因此只需有get方法 public Double getBalance() { return balance; } public boolean equals(Object o){ if(this==o){ return true; } if(o==null||getClass()!=o.getClass()){ return false; } Account account = (Account)o; return accountNo.equals(account.accountNo); } //定义一个线程安全的类执行取钱操作 public synchronized 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()+"取钱失败,余额不足"); } } }
DrawThread类
public class DrawThread extends Thread{ private Account account; private double drawAmount; public DrawThread(){} public DrawThread(String name,Account account,double drawAmount){ super(name); this.account = account; this.drawAmount = drawAmount; } public void run(){ account.draw(drawAmount); } }
测试类
public class test(){ public static void main(String[] args){ Account acct = new Account("1234556",(double)1000) ; new DrawThread("甲",acct,800).start(); new DrawThread("乙",acct,800).start(); } }
注:synchronized关键字可以修饰方法、代码块,但是不可以修饰构造器、成员变量等。
在Account中定义draw()方法而不是在run()方法中实现取钱逻辑这样更符合面向对象规则。
3、使用volatile关键字
volatile保证了多线程下的可见性。
禁止指令重排序。
将Account类中余额balance用volatile修饰即可不用synchronized。
private volatile double balance;
4、使用重入锁实现线程同步
JavaSE1.5引入java.util.concurrent包来支持同步。
ReentrantLock是可重入,互斥,实现了Lock接口的锁,扩展了synchronized的功能。
代码:
Account类,其他类与之前类似。
public class Account{ private final ReentrantLock lock = new ReentrantLock(); private String accountNo; private double balance; public Account(){} public Account(String accountNo,double banlance){ this.accountNo = accountNo; this.balance = balance; } //getter和setter方法 public String getAccountNo() { return accountNo; } public void setAccountNo(String accountNo) { this.accountNo = accountNo; } //账户余额不能随便更改,因此只需有get方法 public Double getBalance() { return balance; } public boolean equals(Object o){ if(this==o){ return true; } if(o==null||getClass()!=o.getClass()){ return false; } Account account = (Account)o; return accountNo.equals(account.accountNo); } //提供一个线程安全的方法实现取钱操作 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("\t余额为:" + balance); } else { System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足"); } }finally{ //必须显示调用unlock()方法释放锁,一般放在finally里面 lock.unlock(); } } }
synchronized和lock
如果使用synchronized能满足用户需求则不用Lock,因为synchronized能简化代码。
如果需要使用更高级的功能则使用Lock。线程在每次lock()加锁后,必须显示调用unlock()来释放锁。
5、使用局部变量(共享变量副本)实现线程同步
ThreadLocal threadLocal = new ThreadLocal();
使用ThreadLocal管理变量,每一个使用该变量的线程都能获得该变量的副本,每个副本之间相互独立,这样每一个线程都能随意修改自己的变量副本,不会对其他线程产生影响。
- Java线程总结(二):线程的同步方式synchronized
- Java同步Socket通信例子(线程方式) ,完整版
- Java线程同步的几种方式
- Java线程(八):锁对象Lock-同步问题更完美的处理方式
- Java线程(八):锁对象Lock-同步问题更完美的处理方式
- Java线程(七):锁对象Lock-同步问题更完美的处理方式
- Java线程:锁对象Lock-同步问题更完美的处理方式
- Java线程(七):锁对象Lock-同步问题更完美的处理方式
- Java线程同步、锁机制精解(5中同步方式)
- Java线程(八):锁对象Lock-同步问题更完美的处理方式
- Java线程(七):锁对象Lock-同步问题更完美的处理方式
- Java之线程安全中的三种同步方式
- java线程同步7种同步方式
- Java线程(八):锁对象Lock-同步问题更完美的处理方式(转)
- java线程的同步方式
- Java线程(6)锁对象Lock-同步问题更完美的处理方式
- Java线程(八):锁对象Lock-同步问题更完美的处理方式
- Java线程(八):锁对象Lock-同步问题更完美的处理方式
- Java线程之锁对象Lock-同步问题更完美的处理方式代码实例