实现同步的几种方式

1.同步方法
即有synchronized关键字修饰的方法;
由于Java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法之前,需要获取内置锁,否则就处于阻塞状态。

2.同步代码块
即有synchronized关键字修饰的语句块;
代码如:
synchronized(object) {

}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

3.使用volatile实现线程同步
(1)volatile关键字为域变量的访问提供了一种免锁机制;
(2)使用volatile修饰域,相当于告诉JVM,该域可能会被其它线程更新,因此每次使用该域时就要重新计算,而不是使用寄存器中的值;
(3)volatile不会提供任何原子操作,也不能用来修饰final类型的变量。

实例:如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?
代码示例:
public class Test {

    private volatile static int count = 0; //账户余额
    public static void main(String[] args) throws InterruptedException {
        new TAdd().start();
        new TSub().start();
    }
    
    public static void addMoney(int money) { //存钱
        synchronized(Test.class) {
            count+=money;
        }
        System.out.println(System.currentTimeMillis() + "存进" + money);
    }
    public static void subMoney(int money) { //取钱
        synchronized(Test.class) {
            if(count < money) {
                System.out.println("余额不足");
                return;
            }
            count -= money;
        }
        System.out.println(System.currentTimeMillis() + "取出" + money);
    }
    public static void lookMoney() {
        System.out.println("账户余额:"+count);
    }
    static class TAdd extends Thread {
        
        public void run() {
            while(true) {
                try {
                    Thread.sleep(1000);
                    addMoney(100);
                    lookMoney();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    static class TSub extends Thread {
        public void run() {
            while(true) {
                try {
                    Thread.sleep(1000);
                    subMoney(100);
                    lookMoney();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
运行结果:
1545704383398存进100
1545704383398取出100
账户余额:0
账户余额:0
余额不足
账户余额:100
1545704384399存进100
账户余额:100(出错了)
。。。。。。。。
出错原因是输出语句并没有被synchronized同步。另外,volatile不能保证原子性,因此volatile并不能代替synchronized,而在这个例子中,通过synchronized将count包裹起来解决了原子性的问题。volatile的原理是在每次线程要访问volatile修饰的变量时都是从内存中读取,而不是从缓存中读取,因此每个线程访问到的变量值都是一样的。

4.使用重入锁实现线程同步
在Java5中新增了java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和块具有相同的基本行为和语义,并且扩展了其能力。
ReentrantLock类的常用方法有:
ReentrantLock():创建一个ReentrantLock实例;
lock():获得锁;
unlock():释放锁;
注:ReentrantLock还有一个可以创建公平锁的构造方法,但由于会大幅降低程序运行效率,不推荐使用。

代码示例:
public class Test {

    private volatile static int count = 0; //账户余额
    
    private static Lock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        new TAdd().start();
        new TSub().start();
    }
    
    public static void addMoney(int money) { //存钱
        lock.lock();
        try {
            count+=money;
            System.out.println(System.currentTimeMillis() + "存进" + money);
        } finally {
            lock.unlock();
        }
    }
    public static void subMoney(int money) { //取钱
        lock.lock();
        try {
            if(count < money) {
                System.out.println("余额不足");
                return;
            }
            count -= money;
            System.out.println(System.currentTimeMillis() + "取出" + money);
        } finally {
            lock.unlock();
        }
        
    }
    public static void lookMoney() {
        System.out.println("账户余额:"+count);
    }
    static class TAdd extends Thread {
        
        public void run() {
            while(true) {
                try {
                    Thread.sleep(1000);
                    addMoney(100);
                    lookMoney();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    static class TSub extends Thread {
        public void run() {
            while(true) {
                try {
                    Thread.sleep(1000);
                    subMoney(100);
                    lookMoney();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
运行结果正常。
注:关于Lock对象和synchronized关键字的选择
(1)如果synchronized能满足用户需求,就用synchronized,因为它能简化代码;
(2)如果需要更高级的功能,就用ReentrantLock,此时需要注意及时释放锁,否则会出现死锁,通常在finally中释放锁。

5.使用ThreadLocal实现线程同步
public class Test {
    
    private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
        
        @Override
        protected Integer initialValue() {

            return 0;
        }
    };
    public static void main(String[] args) throws InterruptedException {
        new TAdd().start();
        new TSub().start();
    }
    
    public static void addMoney(int money) { //存钱
            count.set(count.get() + money);
            System.out.println(System.currentTimeMillis() + "存进" + money);
        
    }
    public static void subMoney(int money) { //取钱
            if(count.get() < money) {
                System.out.println("余额不足");
                return;
            }
            count.set(count.get() - money);
            System.out.println(System.currentTimeMillis() + "取出" + money);
        
        
    }
    public static void lookMoney() {
        System.out.println("账户余额:"+count.get());
    }
    static class TAdd extends Thread {
        
        public void run() {
            while(true) {
                try {
                    Thread.sleep(1000);
                    addMoney(100);
                    lookMoney();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    static class TSub extends Thread {
        public void run() {
            while(true) {
                try {
                    Thread.sleep(1000);
                    subMoney(100);
                    lookMoney();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
运行结果:
余额不足
账户余额:0
1545709620876存进100
账户余额:100
余额不足
账户余额:0
1545709621876存进100
账户余额:200
余额不足
账户余额:0
1545709622876存进100
账户余额:300
余额不足
账户余额:0
1545709623878存进100
账户余额:400

如果使用ThreadLocal管理变量,则每个使用该变量的线程都获得该变量的一个副本,副本之间相互独立,这样每个线程都可以随意更改自己的变量副本,而不会对其它线程产生影响。
ThreadLocal类的常用方法:
ThreadLocal():创建一个线程本地变量;
get():返回此线程局部变量的当前线程副本中的值;
initialValue():返回此线程局部变量的当前线程的初始值;
set(T value):将此线程局部变量的当前线程副本中的值设置为value。

转载于:https://www.cnblogs.com/yuanfei1110111/p/10173133.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值