您的位置:首页 > 编程语言 > Java开发

Java线程同步方式

2019-01-22 18:57 92 查看

本文部分内容来自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管理变量,每一个使用该变量的线程都能获得该变量的副本,每个副本之间相互独立,这样每一个线程都能随意修改自己的变量副本,不会对其他线程产生影响。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: