线程同步:锁的初步认识(三)

本文详细介绍了Java中的同步机制,包括synchronized关键字的多种使用方式及其作用范围,通过具体示例展示了如何实现线程间的同步,避免共享资源访问冲突。

  • 同步机制介绍

    • 一个资源被多个执行线程共享,为了防止这个资源可能出现的错误或者数据不一致,人们引入了临界区(critical section)概念。临界区是一个用于访问共享资源的代码块,在统一时间内该代码块只能有一个线程执行
    • Java语言提供了两种基本同步机制
      1.synchronized关键字机制
      2.Lock接口及其实现机制
  • 使用synchronized实现同步

    1. 修饰一个代码块,作用范围是synchronized(this){…}扩起来的代码;
    2. 修饰一个方法,作用范围是整个方法,作用对象是调用这个方法的对象
    3. 修饰静态方法,其作用范围是整个静态方法,作用对象是这个类的所有对象;
    4. 修饰一个类,其作用范围是synchronized(className.Class){…}扩起来的部分,作用的对象是这个类的所有对象

1.修饰一个代码块


1.修饰对象里的代码

public class SyncThread implements Runnable {

    private int number = 0;

    @Override
    public void run() {
        // TODO Auto-generated method stub
            for (int i = 0; i < 3; i++) {
        synchronized (this) {
                this.number++;
                System.out.printf("Thread name: %s number=%d\n", Thread.currentThread().getName(), this.number);
            }
        }
    }
}

执行:

SyncThread syncThread = new SyncThread();
Thread thread = new Thread(syncThread, “syncThread1”);
Thread thread2 = new Thread(syncThread, “syncThread2”);
thread.start();
thread2.start();

结果如下:

Thread name: syncThread1 number=1
Thread name: syncThread1 number=2
Thread name: syncThread1 number=3
Thread name: syncThread2 number=4
Thread name: syncThread2 number=5
Thread name: syncThread2 number=6

当两个并发线程访问同一个对象的同步代码块时,只有上一个线程执行完之后释放该对象锁下一个线程才能执行该代码块并获得该对象的锁。

对以上执行做如下调整:

执行:
Thread thread = new Thread(new SyncThread(), “syncThread1”);
Thread thread2 = new Thread(new SyncThread(), “syncThread2”);
thread.start();
thread2.start();
结果如下:
Thread name: syncThread2 number=1
Thread name: syncThread1 number=1
Thread name: syncThread1 number=2
Thread name: syncThread2 number=2
Thread name: syncThread2 number=3
Thread name: syncThread1 number=3

发现执行结果并未与上一个执行一致。究其原因,两个线程中的对象并不一致,而synchronized (this) {…}锁定的是对象,thread与thread2拿到的锁并不相同,两个对象的锁是互不干扰,所以线程同时执行。

2.修饰对象


1.创建一个账户信息类

/**
 * @Desciption 创建一个账号类
 * @Author Baozi
 */
public class Account {
    private float balance;

    public Account(float balance) {
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }

    public void addAmount(float amount) {
        try {
            Thread.sleep(10);
        } catch (Exception e) {
            e.printStackTrace();
        }
        this.balance += amount;
    }

    public void subtractAmount(float amount) {
        try {
            Thread.sleep(10);
        } catch (Exception e) {
            e.printStackTrace();
        }
        this.balance -= amount;
    }
}

2.创建一个消费者

public class Bank implements Runnable {
    private Account account;

    public Bank(Account account) {
        super();
        this.account = account;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 10; i++) {
            synchronized (account) {
                this.account.subtractAmount(1000);
            }
        }
    }
}

3.创建一个存款人

public class Company implements Runnable {
    private Account account;

    public Company(Account account) {
        this.account = account;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 10; i++) {
            synchronized (account) {
                this.account.addAmount(1000);
            }
        }
    }
}

执行

    Account account = new Account(1000);

        Bank bank = new Bank(account);
        Thread t = new Thread(bank);
        Company company = new Company(account);
        Thread t1 = new Thread(company);
        System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
        t.start();
        t1.start();

        try {
            t.join();
            t1.join();
            System.out.printf("Account : Final Balance: %f\n", account.getBalance());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

结果:

Account : Initial Balance: 1000.000000
Account : Final Balance: 1000.000000

以上结果可以看出修饰对象相当于将同步代码块的大小扩大到整个实例化对象。


修饰一个方法

public class SyncMethod implements Runnable {
    private int number = 0;
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            String name = Thread.currentThread().getName();
            switch (name) {
            case "1":
                this.addNumber(name);
                break;
            default:
                this.printfNumber(name);
                break;
            }
        }
    }

    private void printfNumber(String name) {
        System.out.printf("Thread name: %s number=%d\n", name, this.number);
    }

    private synchronized void addNumber(String name) {
        this.number++;
        System.out.printf("Thread name: %s number++=%d\n", name, this.number);
    }
}

执行:

SyncMethod method = new SyncMethod();
Thread thread = new Thread(method, “syncThread1”);
Thread thread2 = new Thread(method, “syncThread2”);
thread.start();
thread2.start();

结果如下:

Thread name: 2 number=1
Thread name: 1 number++=1
Thread name: 1 number++=2
Thread name: 2 number=1
Thread name: 2 number=3
Thread name: 1 number++=3

以上结果可以看出一个线程访问同步方法的情况下,另一个线程是可以访问非同步方法而不受阻塞,但是存在获取共享变量时可能存在线程安全问题。


修饰一个静态方法

public class SyncStatic implements Runnable {
    private static int number = 0;
    @Override
    public void run() {
        addNumber();
    }

    public synchronized static void addNumber() {
        for (int i = 0; i < 3; i++) {
            SyncStatic.number++;
            System.out.printf("Thread name: %s number=%d\n", Thread.currentThread().getName(), number);
        }
    }
}

执行:

Thread thread = new Thread(new SyncStatic(), “syncStatic1”);
Thread thread2 = new Thread(new SyncStatic(), “syncStatic2”);
thread.start();
thread2.start();

结果如下:

Thread name: syncStatic1 number=1
Thread name: syncStatic1 number=2
Thread name: syncStatic1 number=3
Thread name: syncStatic2 number=4
Thread name: syncStatic2 number=5
Thread name: syncStatic2 number=6

以上结果结合静态修饰符的特性可以看出,两个线程虽然两个线程的对象不一致,但是引用的方法都是静态方法,而静态修饰符使该方法拥有唯一属性,所以两个线程使用的是同一个锁


修饰一个类

SyncClass implements Runnable {
    private int number;

    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 3; i++) {
            synchronized (SyncThread.class) {
                this.number++;
                System.out.printf("Thread name: %s number=%d\n", Thread.currentThread().getName(), this.number);
            }
        }
    }
}   

执行:

Thread thread = new Thread(new SyncThread(), “syncThread1”);
Thread thread2 = new Thread(new SyncThread(), “syncThread2”);
thread.start();
thread2.start();

结果如下:

Thread name: syncThread1 number=1
Thread name: syncThread1 number=2
Thread name: syncThread1 number=3
Thread name: syncThread2 number=1
Thread name: syncThread2 number=2
Thread name: syncThread2 number=3

以上结果与修饰对象类似,不同在于synchronized作用于一个类C时,所有C对象是哟同一个锁。

总结

  1. synchronized修饰符作用与非静态方法或者类上时,锁的基本单位是对象,不同对象获取到的锁是不一致的;
  2. synchronized修饰的代码块越小阻塞的时间越短,造成死锁的可能性越小,效率越高;
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值