java 几种同步方式


java的同步方式

在java主要有以下几种方式

1.给变量/代码块/方法/类添加悲观锁避免一个变量更改值的时候对这个变量进行更改。

用 sychronized 来进行同步
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
代码如:
public synchronized void save(){}
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
优缺点:
优点:加上synchronized锁是为了保证服务器选择的顺序性,使得并发操作的同一时刻有且仅有一个线程能够读取/写入数据表记录,防止多线程并发造成的脏数据。
缺点:悲观锁的引入,将会导致并发吞吐量发生明显下降,付出相当大的性能代价。且不能获得锁是否是已经加上去了。

2.使用特殊域变量(volatile)实现线程同步

它的原理是每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步
a.volatile关键字为域变量的访问提供了一种免锁机制,
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
因为volatile使用了复合操作的原子性,所以下面的代码结果一定<=正确值


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

public class VolatileKeywordSynchronization {

    class Bank {
        private volatile int accout = 10;


        public int getAccout() {
            return accout;
        }

        public void saveAccount(int money) {
            accout += money;
            System.out.println("account:" + accout);

        }

    }

    class VolatileThread implements Runnable {

        private Bank bank;

        public VolatileThread(Bank bank) {
            this.bank = bank;
        }

        @Override
        public void run() {

            for (int i = 0; i < 10; i++) {
                bank.saveAccount(10);
                System.out.println(Thread.currentThread().getName() + "-->第" + i + "次当前账户余额:" + bank.getAccout() + "元。");
            }
        }
    }


    public void userVolatileThread() {
        Bank bank = new Bank();

        VolatileThread volatileThread = new VolatileThread(bank);

        Thread thread1 = new Thread(volatileThread);

        Thread thread2 = new Thread(volatileThread);

        System.out.println("线程1:");
        thread1.start();

        System.out.println("线程2:");
        thread2.start();


    }

    public static void main(String[] args) {

        VolatileKeywordSynchronization volatileKeywordSynchronization = new VolatileKeywordSynchronization();

        volatileKeywordSynchronization.userVolatileThread();
    }


}

volatile的优缺点
优点:Volatile修饰的成员变量在每次被线程访问是,都强迫从共享内存中重读成员变量的值;当成员变量发生变化是,强迫线程将变化值回写到共享内存;这样可以让多个线程总是看到某个成员变量的同一个值,Volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应该直接语共享成员变量交互;
缺点: 关键字能保证数据的可见性,但不能保证数据的原子性,且volatile只适用变量。
3.AtomicInteger(类)

public class VolatileKeywordSynchronization {

    class Bank {
        private AtomicInteger accout = new AtomicInteger(10);


        public int getAccout() {
            return accout.get();
        }

        public void saveAccount(int money) {
            accout.getAndAdd(money);
            System.out.println("account:" + accout);

        }

    }

    class VolatileThread implements Runnable {

        private Bank bank;

        public VolatileThread(Bank bank) {
            this.bank = bank;
        }

        @Override
        public void run() {

            for (int i = 0; i < 10; i++) {
                bank.saveAccount(10);
                System.out.println(Thread.currentThread().getName() + "-->第" + i + "次当前账户余额:" + bank.getAccout() + "元。");
            }
        }
    }


    public void userVolatileThread() {
        Bank bank = new Bank();

        VolatileThread volatileThread = new VolatileThread(bank);

        Thread thread1 = new Thread(volatileThread);

        Thread thread2 = new Thread(volatileThread);

        System.out.println("线程1:");
        thread1.start();

        System.out.println("线程2:");
        thread2.start();


    }

    public static void main(String[] args) {

        VolatileKeywordSynchronization volatileKeywordSynchronization = new VolatileKeywordSynchronization();

        volatileKeywordSynchronization.userVolatileThread();
    }
}

AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 CAS 的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个 volatile 变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
优缺点:
优点:不需要加锁,就可以保证原子性,减少大量的开销。
缺点:不能保证代码块的原子性

ReentrantLock

ReentrantLock 意思为可重入锁,指的是一个线程能够对一个临界资源重复加锁。
其原理是使用AQS(AbstractQueuedSynchornized)机制,是采用双向队列来进行加锁。

AQS机制

AQS 是一个设计模板,具体的实现由子类完成,AQS中维护了一个state(共享资源,代表是否可以被加锁)和一个CLH(先入先出)双向队列。把需要等待的线程放进CLH队列。

ReentrantLock 实现原理

ReentrantLock支持公平锁和非公平锁,其中的一些原理是稍微由不同的。

公平锁

ReentrantLock(true)
reentrantLock.lock(); reentrantLock.unlock();
线程加锁,先把线程加入队列中,如果该线程是头节点,就加锁执行,如果不是调用本地方法park等待。线程解锁,更新状态,并且从尾节点向上追溯到head的下一个节点,并且更新head节点。

非公平锁

ReentrantLock() ReentrantLock(false)
reentrantLock.lock(); reentrantLock.unlock();
AQS中维护了一个volatile int state(共享资源)和一个CLH队列。当state=1时代表当前对象锁已经被占用,其他线程来加锁时则会失败,失败的线程被放入一个FIFO的等待队列中,然后会被UNSAFE.park()操作挂起,等待已经获得锁的线程释放锁才能被唤醒。

public class VolatileKeywordSynchronization {
    ReentrantLock reentrantLock = new ReentrantLock();
    class Bank {
        private int accout = 10;

        public int getAccout() {
            return accout;
        }

        public void saveAccount(int money) {
            reentrantLock.lock();
            try {
                accout+=money;
            }finally {
                reentrantLock.unlock();
            }

            System.out.println("account:" + accout);

        }

    }

    class VolatileThread implements Runnable {

        private Bank bank;

        public VolatileThread(Bank bank) {
            this.bank = bank;
        }

        @Override
        public void run() {

            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                bank.saveAccount(10);

                System.out.println(Thread.currentThread().getName() + "-->第" + i + "次当前账户余额:" + bank.getAccout() + "元。");
            }
        }
    }


    public void userVolatileThread() {
        Bank bank = new Bank();

        VolatileThread volatileThread = new VolatileThread(bank);

        Thread thread1 = new Thread(volatileThread);

        Thread thread2 = new Thread(volatileThread);

        System.out.println("线程1:");
        thread1.start();

        System.out.println("线程2:");
        thread2.start();


    }

    public static void main(String[] args) {
        VolatileKeywordSynchronization volatileKeywordSynchronization = new VolatileKeywordSynchronization();

        volatileKeywordSynchronization.userVolatileThread();
    }
}


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM