Java中多线程同步的6种方式和ThreadLocal

        网上有些资料将使用ThreadLocal也定义为多线程同步的一种方式,但是多线程同步的前提是变量共享。

        ThreadLocal并不是为了解决多线程间共享变量的问题。举个例子,在一个电商系统中,用一个Long型变量表示某个商品的库存量,多个线程需要访问库存量进行销售,并减去销售数量,以更新库存量。在这个场景中,是不能使用ThreadLocal类的。

         ThreadLocal适用的场景是,多个线程都需要使用一个变量,但这个变量的值不需要在各个线程间共享,各个线程都只使用自己的这个变量的值。这样的场景下,可以使用ThreadLocal。此外,我们使用ThreadLocal还能解决一个参数过多的问题。例如一个线程内的某个方法f1有10个参数,而f1调用f2时,f2又有10个参数,这么多的参数传递十分繁琐。那么,我们可以使用ThreadLocal来减少参数的传递,用ThreadLocal定义全局变量,各个线程需要参数时,去全局变量去取就可以了。

    ThreadLocal内部维护了一个静态内部类ThreadLocalMap,每个线程都有一个ThreadLocalMap,用来保存该线程关联的所有ThreadLocal类型的变量,因为一个线程中可以存在多个ThreadLocal实例,ThreadLocalMap中key为当前ThreadLocal实例,value

为当前ThreadLocal实例对应的值。

    如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,

    副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。变量局部化

package com.xb.test;

public class ThreadLocalTest {
	class Bank {
        private ThreadLocal<Integer> account = new ThreadLocal<Integer>() {
        	protected Integer initialValue() {
        		return 100;
        	};
        };
        public int getAccount() {

            return account.get();

        }

        /**

         * 用同步方法实现

         *

         * @param money

         */

        public void save(int money) {

        	account.set(account.get()+money);

        }


    }
    class NewThread implements Runnable {
        private Bank bank;
        public NewThread(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {

            for (int i = 0; i < 10; i++) {

                // bank.save1(10);

                bank.save(10);

                System.out.println(Thread.currentThread().getName() + ":" + i + "账户余额为:" + bank.getAccount());

            }

        }
    }



    /**

     * 建立线程,调用内部类

     */

    public void useThread() {

        Bank bank = new Bank();

        NewThread new_thread = new NewThread(bank);

        Thread thread1 = new Thread(new_thread , "线程1");

        thread1.start();

        Thread thread2 = new Thread(new_thread , "线程2");

        thread2.start();

    }

    public static void main(String[] args) {

        ThreadLocalTest st = new ThreadLocalTest();

        st.useThread();

    }
}

   

关于线程同步(6种方式) 

  1. 同步方法
  2. 同步代码块
  3. 使用重入锁实现线程同步(ReentrantLock)
  4. 使用特殊域变量(volatile)实现同步(每次重新计算,安全但并非一致)
  5. 使用原子变量实现线程同步(AtomicInteger(乐观锁))
  6. 使用阻塞队列实现线程同步(BlockingQueue (常用)add(),offer(),put()

1.同步方法

    即有synchronized关键字修饰的方法。

    由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,

    内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

   

package com.xb.test;

public class SynchronousTest {

	class Bank {
        private int account = 100;
        
        public int getAccount() {

            return account;

        }

        /**

         * 用同步方法实现

         *

         * @param money

         */

        public synchronized void save(int money) {

        	account += money;

        }

        /**

                         *用同步代码块实现

         *

         * @param money

         */

        public void save1(int money) {

            synchronized (this) {

                account += money;

            }

        }

    }
    class NewThread implements Runnable {
        private Bank bank;
        public NewThread(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {

            for (int i = 0; i < 10; i++) {

//                 bank.save1(10);

                bank.save(10);

                System.out.println(Thread.currentThread().getName() + ":" + i + "账户余额为:" + bank.getAccount());

            }

        }
    }



    /**

     * 建立线程,调用内部类

     */

    public void useThread() {

        Bank bank = new Bank();

        NewThread new_thread = new NewThread(bank);


        Thread thread1 = new Thread(new_thread , "线程1");

        thread1.start();


        Thread thread2 = new Thread(new_thread , "线程2");

        thread2.start();

    }

    public static void main(String[] args) {

        SynchronousTest st = new SynchronousTest();

        st.useThread();

    }
}

 

    注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

2.同步代码块

    即有synchronized关键字修饰的语句块。

    被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

    代码如:

    synchronized(object){

    }

    代码示例如一所示 

    注:同步是一种高开销的操作,因此应该尽量减少同步的内容。

    通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

3.使用重入锁实现线程同步

    在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。

    ReentrantLock类是可重入、互斥、实现了Lock接口的锁,

    它与使用synchronized方法和块具有相同的基本行为和语义,并且扩展了其能力

   ReentrantLock类的常用方法有:

        ReentrantLock() : 创建一个ReentrantLock实例

        lock() : 获得锁

        unlock() : 释放锁

package com.xb.test;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

import com.xb.test.AtomicTest.Bank;
import com.xb.test.AtomicTest.NewThread;

public class ReentrantLockTest {



	class Bank {
        private int account = 100;
        private ReentrantLock lock = new ReentrantLock();
        public int getAccount() {

            return account;

        }

        /**

         * 用同步方法实现

         *

         * @param money

         */

        public void save(int money) {
        	lock.lock();
        	try {
        		account += money;
			} finally {
				lock.unlock();
			}
        	

        }

    }
    class NewThread implements Runnable {
        private Bank bank;
        public NewThread(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {

            for (int i = 0; i < 10; i++) {

                bank.save(10);

                System.out.println(Thread.currentThread().getName() + ":" + i + "账户余额为:" + bank.getAccount());

            }

        }
    }



    /**

     * 建立线程,调用内部类

     */

    public void useThread() {

        Bank bank = new Bank();

        NewThread new_thread = new NewThread(bank);


        Thread thread1 = new Thread(new_thread , "线程1");

        thread1.start();


        Thread thread2 = new Thread(new_thread , "线程2");

        thread2.start();

    }

    public static void main(String[] args) {

        ReentrantLockTest rlt = new ReentrantLockTest();

        rlt.useThread();

    }


}

 

    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

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

    常用于保持内存可见性和防止指令重排序。

    内存可见性(Memory Visibility)是指所有线程都能看到共享内存的最新状态。

    volatile关键字通过“内存屏障”来防止指令被重排序。

    参考链接:volatile关键字的作用、原理

package com.xb.test;

public class VolatileTest {

	class Bank {
        private volatile int account = 0;
        
        public int getAccount() {

            return account;

        }

        /**

         * 用同步方法实现

         *

         * @param money

         */

        public void save(int money) {

        	account += money;

        }

    }
    class NewThread implements Runnable {
        private Bank bank;
        public NewThread(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {

                bank.save(10);

                System.out.println(Thread.currentThread().getName() + ":" + i + "账户余额为:" + bank.getAccount());

            }

        }
    }



    /**

     * 建立线程,调用内部类

     */

    public void useThread() {

        Bank bank = new Bank();

        NewThread new_thread = new NewThread(bank);


        Thread thread1 = new Thread(new_thread , "线程1");

        thread1.start();


        Thread thread2 = new Thread(new_thread , "线程2");

        thread2.start();

    }

    public static void main(String[] args) {

        VolatileTest st = new VolatileTest();

        st.useThread();

    }
}

6.使用原子变量实现线程同步   

需要使用线程同步的根本原因在于对普通变量的操作不是原子的。

那么什么是原子操作呢?

原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作

即-这几种行为要么同时完成,要么都不完成,跟数据库的事务的原子性有些类似。

在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类

使用该类可以简化线程同步。小工具包,支持在单个变量上解除锁的线程安全编程。

类摘要

AtomicBoolean

可以用原子方式更新的 boolean 值。

AtomicInteger

可以用原子方式更新的 int 值。

AtomicIntegerArray

可以用原子方式更新其元素的 int 数组。

AtomicIntegerFieldUpdater<T>

基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。

AtomicLong

可以用原子方式更新的 long 值。

AtomicLongArray

可以用原子方式更新其元素的 long 数组。

AtomicLongFieldUpdater<T>

基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。

AtomicMarkableReference<V>

AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。

AtomicReference<V>

可以用原子方式更新的对象引用。

AtomicReferenceArray<E>

可以用原子方式更新其元素的对象引用数组。

AtomicReferenceFieldUpdater<T,V>

基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。

AtomicStampedReference<V>

AtomicStampedReference 维护带有整数"标志"的对象引用,可以用原子方式对其进行更新。

其中AtomicInteger(乐观锁)为例 :

表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

AtomicInteger类常用方法:

AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger

addAndGet(int dalta) : 以原子方式将给定值与当前值相加

int

getAndAdd(int delta)

以原子方式将给定值与当前值相加。

int

getAndDecrement()

以原子方式将当前值减 1。

int

getAndIncrement()

以原子方式将当前值加 1。

int get() : 获取当前值

set():设置给定初始值

package com.xb.test;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicTest {


	class Bank {
        private AtomicInteger account = new AtomicInteger(100);
        
        public int getAccount() {

            return account.get();

        }

        /**

         * 用同步方法实现

         *

         * @param money

         */

        public void save(int money) {

        	account.addAndGet(money);

        }

    }
    class NewThread implements Runnable {
        private Bank bank;
        public NewThread(Bank bank) {
            this.bank = bank;
        }
        @Override
        public void run() {

            for (int i = 0; i < 10; i++) {

                bank.save(10);

                System.out.println(Thread.currentThread().getName() + ":" + i + "账户余额为:" + bank.getAccount());

            }

        }
    }



    /**

     * 建立线程,调用内部类

     */

    public void useThread() {

        Bank bank = new Bank();

        NewThread new_thread = new NewThread(bank);


        Thread thread1 = new Thread(new_thread , "线程1");

        thread1.start();


        Thread thread2 = new Thread(new_thread , "线程2");

        thread2.start();

    }

    public static void main(String[] args) {

        AtomicTest st = new AtomicTest();

        st.useThread();

    }

}

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值