[Java]线程的同步

一、线程同步


线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。
例如:创建并启动100个线程,每个线程都向同一个账户里添加一便士。当所有数据源同时访问同一数据源时,就会出现数据破坏问题。

import java.util.concurrent.*;

public class AccountWithoutSync{
private static Account account=new Account();

public static void main(String[] args){
ExecutorService executor=Executors.newCachedThreadPool();
for(int i=0;i<100;i++){
executor.execute(new AddAPennyTask());
}
executor.shutdown();

while(!executor.isTerminated()){
}

System.out.println("What is balance? "+account.getBalance());
}

//任务类
private static class AddAPennyTask implements Runnable{
public void run(){
account.deposit(1);
}
}

//账户 内部类
private static class Account{
private int balance=0;
public int getBalance(){
return balance;
}
public void deposit(int amount){
int newBalance=balance+amount;

try{
Thread.sleep(5);//放大数据破坏问题
}
catch(InterruptedException ex){
}
balance=newBalance;
}
}
}

运行结果:
What is balance? 3

从结果发现,这样的输出值明显是不合理的。原因是多个线程不加控制的访问Account对象并修改其数据所致。
举例说明:
example
这个情景的效果就是任务1什么都没做,因为在步骤4任务2覆盖了任务1的结果。

如果要保持结果的合理性,只需要达到一个目的,就是将对Account的访问加以限制,每次只能有一个线程在访问。这样就能保证Account对象中数据的合理性了。

在具体的Java代码中需要完成一下两个操作:
把竞争访问的资源类Account变量balance标识为private;
同步那些修改变量的代码,使用synchronized关键字同步方法或代码。

二、同步和锁定


不同任务以一种会引起冲突的方式访问一个公共资源,称为竞争状态(race condition)。

若一个类的对象在多线程程序中未导致竞争状态,则称该类为线程安全的(thread-safe)。上面的例子中Account类就不是线程安全的。

为避免竞争状态,应该防止多个线程同时进入程序的某一特定部分,程序中的这部分称为临界区(critical region)。

1、锁的原理

当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。也称为获取锁、锁定对象、在对象上锁定或在对象上同步。

如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放该对象的锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

释放锁是指持锁线程退出了synchronized同步方法或代码块。

2、synchronized关键字 (隐式加锁)

1)同步方法:

public synchronized void xMethod(){
//method body
}

一个同步方法在执行之前需要加锁。
对于实例方法,要给调用该方法的对象加锁;
对于静态方法,要给这个类加锁。
执行过程:
a.加锁(对象/类)
b.执行(其他调用对象/类的同步方法的线程阻塞)
c.解锁(可访问)

针对例子,可改为:

public synchronized void deposit(int amount){
//method body
}

2)同步语句(可用于对任何对象加锁):

synchronized (expr){//expr必须求出对象的引用
ststements;
}

针对例子,可改为:

synchronized(account){
account.deposit(1);
}
或
public void deposit(int amount){
synchronized(this){
//method body
}
}

注:任何同步方法都可转换为同步语句
例如上面的同步方法语句就等价于:

public void xMethod(){
synchronized(this){
//method body
}
}

3、Lock加锁同步(显式加锁)
一个锁是一个Lock接口的实例,定义了加锁和释放锁的方法。
锁也可以使用newCondition()方法来创建任意个数的Condition对象,用来进行线程通信。

+lock():void 
+unlock():void
+newCondition():Condition

ReentrantLock是为创建相互排斥的锁的Lock的具体实现。可以创建具有特定的公平策略的锁。
使用Lock可将示例代码改为:

import java.util.concurrent.*;
import java.util.concurrent.locks.*;

public class AccountWithSyncUsingLock{
private static Account account=new Account();

public static void main(String[] args){
ExecutorService executor=Executors.newCachedThreadPool();
for(int i=0;i<100;i++){
executor.execute(new AddAPennyTask());
}
executor.shutdown();

while(!executor.isTerminated()){
}

System.out.println("What is balance? "+account.getBalance());
}

private static class AddAPennyTask implements Runnable{
public void run(){
account.deposit(1);
}
}

private static class Account{
private static Lock lock=new ReentrantLock();//创建锁
private int balance=0;
public int getBalance(){
return balance;
}
public void deposit(int amount){
lock.lock();//请求锁
try{
int newBalance=balance+amount;
Thread.sleep(5);
balance=newBalance;
}
catch(InterruptedException ex){
}
finally{
lock.unlock();//释放锁
}
}
}
}

运行结果:
What is balance? 100

通常,使用synchronized方法或语句比使用相互排斥的显式锁简单些。然而,使用显式锁对同步具有状态的线程更加直观和灵活。

三、何时需要同步


在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改它。

四、线程同步小结


1、线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁造成的,在实际中发生的概率非常的小。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值