JAVA多线程之线程同步

为什么需要同步

  JVM有一个main memory,而每个线程都有自己的working memory,一个线程对一个变量进行操作时,都要在自己的 working memory里面建立一个copy,操作完之后再写入main memory.当多个线程同时操作一个变量时,就可能产生不可预知的结果,这就是线程安全问题.
   当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用。达到此目的的过程叫做同步(synchronization), 解决线程安全问题的方法就是利用同步机制.看一段简单的代码,银行账户取钱的例子:
package code;

public class UseBank {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
			Account account = new Account();
			Thread user_1 = new User(account);
			Thread user_2 = new User(account);
			user_1.start();
			user_2.start();
	}
}

class User extends Thread{
	private Account account;
	public User(Account account){
		this.account = account;
	}
	@Override
	public void run(){
       System.out.println("I get:" + this.account.Withdraw(800));
	}
}

class Account{
	private int money = 1000;
	public Account(){
	}
	//取钱
	public int Withdraw(int number){
		if(number < 0){
			return -1;
		}else if(number > money){
			return -2;
		}else if(money < 0){
			return -3;
		}else{
            money -= number;
            try{
                Thread.sleep(2000);
            }
            catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("Withdraw:" + number + "  " + "Remains: " + money);
            return number;
		}
	}
}
  代码中定义了一个银行账户有1000元,两个线程代表两个用户去共享这个账户去取钱,每个取800,正常来说一个用户取完800之后另一个用户是无法再取出800的,但是代码执行的结果会出现以下情况:
 情况一:
Withdraw:800  Remains: -600
I get:800
Withdraw:800  Remains: -600
I get:800

情况二:
Withdraw:800  Remains: 200
I get:800
Withdraw:800  Remains: -600
I get:800
   也就是说两个用户都成功取出了800.这是因为,在执行钱数减少之前,线程休眠了一个简短的时间,在一个用户进入else语句后休眠过程中,还未取走钱,所以另一个用户也能进入,于是就都能取走800.
  同时也能看到,余额输出的顺序可以是-600 -600或者200 -600.这是因为在输出钱另一个账户可能没把钱取走,也可能把钱去走了.

解决办法

  在Withdraw方法前加上关键字synchronized
package code;

public class UseBank {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
			Account account = new Account();
			Thread user_1 = new User(account);
			Thread user_2 = new User(account);
			user_1.start();
			user_2.start();
	}
}

class User extends Thread{
	private Account account;
	public User(Account account){
		this.account = account;
	}
	@Override
	public void run(){
       System.out.println("I get:" + this.account.Withdraw(800));
	}
}

class Account{
	private int money = 1000;
	public Account(){
	}
	//取钱
	public  synchronized int Withdraw(int number){
		if(number < 0){
			return -1;
		}else if(number > money){
			return -2;
		}else if(money < 0){
			return -3;
		}else{
            try{
                Thread.sleep(2000);
            }
            catch (InterruptedException e){
                e.printStackTrace();
            }
            money -= number;
            System.out.println("Withdraw:" + number + "  " + "Remains: " + money);
            return number;
		}
	}
}
 之后再执行代码就只有一个用户能够取出800了.

同步的实现方法

同步方法

  被 synchronized修饰的方法叫做同步方法.
  当 synchronized关键字修饰一个方法时,该方法叫做同步方法.在JAVA中每个对象都有一个锁,当一个进程访问某个对象的 synchronized方法时,将该对象上锁,其他任何进程都无法再访问对象的synchronized方法,直到之前那个对象执行完毕或是抛出异常,才将锁释放,其他进程才能够访问该对象的 synchronized方法.在这里需要注意的是被上锁的是对象.如果被 synchronized修饰的方法是静态方法,将会锁住整个类.
  上面例子的解决方法使用的就是同步方法.

同步代码块

  可能一个方法中只有某段代码访问共享资源,而同步是一种高开销的操作,因此应该减少同步的内容,同步代码块就是用synchronized修饰代码块,被修饰的代码块会自动加上内置锁,实现同步.
  通常同步代码块是需要锁定的对象,一般是需要并发访问的共享资源,任何线程在修改指定资源之前都首先对该资源加锁,在加锁期间其它线程无法修改该资源。从而保证了线程的安全性,而且线程在调用sleep也不会让出资源锁。
  例如上例可以这样修改,在用户线程代码中对Account对象进行加锁,把调用Withdraw代码块变成同步块:
package code;

public class UseBank {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
			Account account = new Account();
			Thread user_1 = new User(account);
			Thread user_2 = new User(account);
			user_1.start();
			user_2.start();
	}
}

class User extends Thread{
	private Account account;
	public User(Account account){
		this.account = account;
	}
	@Override
	public void run(){
		synchronized(account){
			 System.out.println("I get:" + this.account.Withdraw(800));
		}
	}
}

class Account{
	private int money = 1000;
	public Account(){
	}
	//取钱
	public  synchronized int Withdraw(int number){
		if(number < 0){
			return -1;
		}
		else if(number > money){
			return -2;
		}else if(money < 0){
			return -3;
		}else{
            try{
                Thread.sleep(2000);
            }
            catch (InterruptedException e){
                e.printStackTrace();
            }
            money -= number;
            System.out.println("Withdraw:" + number + "  " + "Remains: " + money);
            return number;

同步锁

  在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。

  ReenreantLock类的常用方法有:
  ReentrantLock() : 创建一个ReentrantLock实例
  lock() : 获得锁
  unlock() : 释放锁

  上述例子使用同步锁:

package code;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseBank {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
			Account account = new Account();
			Thread user_1 = new User(account);
			Thread user_2 = new User(account);
			user_1.start();
			user_2.start();
	}
}

class User extends Thread{
	private Account account;

	public User(Account account){
		this.account = account;
	}
	@Override
	public void run(){
			 System.out.println("I get:" + this.account.Withdraw(800));
	}
}

class Account{
	private int money = 1000;
	Lock lock = new ReentrantLock();
	public Account(){
	}
	//取钱
	public  int Withdraw(int number){
		lock.lock();
		if(number < 0){
            lock.unlock();
			return -1;
		}
		else if(number > money){
            lock.unlock();
			return -2;
		}else if(money < 0){
            lock.unlock();
			return -3;
		}else{
            try{
                Thread.sleep(2000);
            }
            catch (InterruptedException e){
                e.printStackTrace();
            }
            money -= number;
            System.out.println("Withdraw:" + number + "  " + "Remains: " + money);
            lock.unlock();
            return number;
		}
	}
}
  在对共享资源进行操作的前加锁,操作完毕释放.


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值