同步代码块:
文件并发被访问时容易造成异常。同步代码块语法格式:
synchronized(obj) { ... //此处的代码就是同步代码块 }obj是同步监视器 线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。
注:任何时刻只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。 通常推荐使用可能被并发访问的共享资源充当同步监视器。例:
- /*
- 模拟银行用户取钱问题
- */
- public class DrawThread extends Thread
- {
- //模拟用户帐户
- private Account account;
- //当前取钱线程所希望取的钱数
- private double drawAmount;
- public DrawThread(String name,Account account,double drawAmout)
- {
- super(name);
- this.account = account;
- this.drawAmount = drawAmount;
- }
- //当多条线程修改同一个共享数据时,涉及安全问题
- public void run()
- {
- //account作为同步监视器
- synchronized(account)
- {
- //帐户余额大于取钱钱数
- if(account.getBalance() >= drawAmount)
- {
- System.out.println(getName() +"取钱成功!吐出钞票:" + drawAmount);
- try
- {
- Thread.sleep(1);
- }
- catch(InterruptedException ex)
- {
- ex.printStackTrace();
- }
- //修改余额
- account.setBalance(account.getBalance() - drawAmount);
- System.out.println("\t余额为:"+ account.getBalance());
- }
- else
- {
- System.out.println(getName()+"取钱失败!余额不足!");
- }
- }
- //同步代码块结束,该线程释放同步锁
- }
- }
同步方法:
同步方法就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。 同步方法无需显示指出同步监视器,同步方法的同步监视器就是this,也就是该方法本身。 例:- public class Account
- {
- private String accountNo;
- private double balance;
- public Account(){}
- public Account(String accountNo,double balance)
- {
- this.accountNo = accountNo;
- this.balance = balance;
- }
- //此处省略了accountNo的setter和getter方法
- //因此余额帐户不允许随便修改,所以取消balance属性的setter方法
- public double getBalance()
- {
- return this.balance;
- }
- //提供一个线程安全draw方法来完成取钱操作
- 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("\t 余额为:"+ balance);
- }
- else
- {
- System.out.println(Thread.currentThread().getName() +"取钱失败!余额不足!");
- }
- }
- //此处省略了hashCode和equals两个重写的方法。
- ...
- }
注:synchronized关键字可以修饰方法,可以修饰代码块,但不能修饰构造器,属性等。
释放同步监视器的锁定:
线程会在如下几种情况下释放对同步监视器的锁定: 1.当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器。 2.当线程在同步代码块、同步方法中遇到break,return终止了该代码块、该方法的继续执行, 当前线程将会释放同步监视器。 3.当线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、该方法异常结束时将会释放同步监视器。 4.当线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。在下面情况下,线程不会释放同步监视器:
1、线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器。 2、线程执行同步代码块时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器。(应尽量避免)同步锁(Lock):使用方法:
- 使用Lock对象的代码格式如下:
- //ReentrantLock 可重入锁
- class X
- {
- //定义锁对象
- private final ReentrantLock lock = new ReentrantLock();
- //..
- //定义需要保证线程安全的方法
- public void m()
- {
- //加锁
- lock.locl();
- try
- {
- //需要保证线程安全的代码
- //...method body
- }
- //使用finally块来保证释放锁
- finally
- {
- lock.unlock();
- }
- }
- }
例:
- public class Account
- {
- //定义锁对象
- private final ReentrantLock lock = new ReentrantLock();
- private String accountNo;
- private double balance;
- public Account(){};
- public Account(String accountNo,double balance)
- {
- this.accountNo = accountNo;
- this.balance = balance;
- }
- //此处省略了accountNo的setter和getter方法
- //因此帐户余额不允许随便修改,所以取消balance属性的setter方法,
- public double getBalance()
- {
- return this.balance;
- }
- //提供一个线程安全draw方法来完成取钱操作
- 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块开确保释放锁
- finally
- {
- lock.unlock();
- }
- }
- //此处省略了hashCode和equals两个重写的方法。
- ...
- }
注:使用Lock与使用同步方法有点相似,只是使用Lock时显示使用Lock对象作为同步锁,而使用同步方式时系统饮式使用当前对象作为监视器,同样符合“加锁->访问->释放锁”的操作模式,而且使用Lock对象时每个Lock对象对应一个Account对象,一样可以保证对于同一个Account对象,同一时刻只能有一条线程能进入临界区。
ReentrantLock锁具有重入性,也就是说线程可以对他已经加锁的ReentrantLock锁再次加锁,线程在每次调用lock()加锁后,必须显示调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。