实现同步的几种方式

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
    评论
### 回答1: 一些实现同步机制的方式包括:使用锁、信号量、条件变量、屏障等。锁可以用于保护共享资源,信号量可以用于限制并发访问,条件变量可以用于线程之间的通信,屏障可以用于同步多个线程的执行。具体实现方式取决于具体的应用场景和需求。 ### 回答2: 实现同步机制的几种方式包括:锁机制、信号量机制、条件变量机制和管程机制。 锁机制是最常见的同步机制,通过对关键资源加锁来实现同一时间只能有一个线程访问该资源。常见的锁包括互斥锁和读写锁。互斥锁用于实现互斥访问,即同一时间只能有一个线程访问该资源;读写锁则允许多个线程同时读取资源,但只允许一个线程写入资源。 信号量机制使用信号量来实现对资源的访问控制。它通过一个计数器来控制同一时间内允许访问资源的线程数量,并提供了P操作(申请访问资源)和V操作(释放资源)来实现对资源的加锁和解锁。 条件变量机制用于实现线程之间的通信和协作。它通过一个条件变量和一个互斥锁来实现。线程可以在某个条件成立时等待条件变量,直到条件满足后被唤醒;同时,其他线程也可以通过发送信号来通知条件变量的等待线程条件已经满足。 管程机制是一种高级的同步机制,它将共享资源和对资源的操作封装在一个对象中,通过对象的方法实现资源的访问控制。管程提供了条件变量和互斥锁来实现线程之间的通信和同步操作。 这些同步机制都可以用来解决多线程并发访问共享资源时可能引发的问题,例如数据竞争和死锁。不同的机制适用于不同的场景和需求,开发者需要根据实际情况选择合适的同步机制来确保程序的正确性和性能。 ### 回答3: 实现同步机制的几种方式有: 1. 互斥量(Mutex):使用互斥量来保证同一时间只有一个线程执行临界区代码,通过对互斥量的加锁和解锁来控制线程的访问顺序。 2. 信号量(Semaphore):使用信号量来控制同时可访问某一资源的线程数量,通过信号量的P(wait)和V(signal)操作来进行进程间的同步。 3. 条件变量(Condition):条件变量用于在某个线程等待特定条件的发生,当条件满足时,其他线程可以唤醒等待的线程继续执行。 4. 临界区(Critical Section):临界区是指一段代码,一次只允许一个线程进入执行,通过使用互斥量或信号量来保证临界区的互斥性。 5. 事件(Event):事件用于线程间的通信和同步,一个线程等待某个事件的发生,另一个线程发生该事件后,等待的线程被唤醒继续执行。 6. 互斥量+条件变量(Mutex+Condition):通过互斥量的加锁和解锁保证临界区的互斥性,通过条件变量的等待和唤醒来控制线程的执行顺序和同步。 以上是实现同步机制的几种常见方式,不同的应用场景可以选择适合的方式实现线程间的同步和互斥操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值