Java实现线程同步的几种方式?

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Michaeles/article/details/86500835

1.为什么要使用同步?

使用多线程时,保证数据的唯一性和准确性。

二、Java实现同步的几种方式

//通过加synchronized关键字实现多个线程同时访问共享资源时出现的问题,当有申请者申请该资源时,如果资源没有被占用,就给这个申请者使用,否则不能使用该资源。

1.synchronized关键字

(1)同步方法

public synchronized void save(){}

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

(2)同步代码块

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

public class SynchronizedThread {

    class Bank {

        private int account = 200;

        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(i + "账户余额为:" + bank.getAccount());
            }
        }

    }

    /**
     * 建立线程,调用内部类
     */
    public void useThread() {
        Bank bank = new Bank();
        NewThread new_thread = new NewThread(bank);
        System.out.println("线程1");
        Thread thread1 = new Thread(new_thread);
        thread1.start();
        System.out.println("线程2");
        Thread thread2 = new Thread(new_thread);
        thread2.start();
    }

    public static void main(String[] args) {
        SynchronizedThread st = new SynchronizedThread();
        st.useThread();
    }

}
//400

2.使用重入锁reentrantLock()

class Bank {

            private int account = 100;
            //需要声明这个锁
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }

            }
        }

3.使用局部变量ThreadLocal实现线程同步

如果使用thread管理变量,那么每一个使用该变量的线程都会获得该变量的副本,这样每一个线程可以随意修改自己的变副本,不会对其他线程产生影响。

//只改Bank类,其余代码与上同
        public class Bank{
            // 创建一个线程本地变量 ThreadLocal
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                //返回当前线程的"初始值" 
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                //设置线程副本中的值
                account.set(account.get()+money);
            }
            public int getAccount(){
                //返回线程副本中的值 
                return account.get();
            }
        }

4.使用阻塞队列实现线程同步

(1) LinkedBlockingQueue 类常用方法 
    LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue 
    put(E e) : 在队尾添加一个元素,如果队列满则阻塞 
    size() : 返回队列中的元素个数 
    take() : 移除并返回队头元素,如果队列空则阻塞 

(2)代码:实现商家生产商品和买卖商品的同步?

import java.util.Random;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 用阻塞队列实现线程同步 LinkedBlockingQueue的使用
 * 
 * @author XIEHEJUN
 * 
 */
public class BlockingSynchronizedThread {
    /**
     * 定义一个阻塞队列用来存储生产出来的商品
     */
    private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
    /**
     * 定义生产商品个数
     */
    private static final int size = 10;
    /**
     * 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程
     */
    private int flag = 0;

    private class LinkBlockThread implements Runnable {
        @Override
        public void run() {
            int new_flag = flag++;
            System.out.println("启动线程 " + new_flag);
            if (new_flag == 0) {
                for (int i = 0; i < size; i++) {
                    int b = new Random().nextInt(255);
                    System.out.println("生产商品:" + b + "号");
                    try {
                        queue.put(b);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("仓库中还有商品:" + queue.size() + "个");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            } else {
                for (int i = 0; i < size / 2; i++) {
                    try {
                        int n = queue.take();
                        System.out.println("消费者买去了" + n + "号商品");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("仓库中还有商品:" + queue.size() + "个");
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        // TODO: handle exception
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        BlockingSynchronizedThread bst = new BlockingSynchronizedThread();
        LinkBlockThread lbt = bst.new LinkBlockThread();
        Thread thread1 = new Thread(lbt);
        Thread thread2 = new Thread(lbt);
        thread1.start();
        thread2.start();

    }

}

注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:

  add()方法会抛出异常

  offer()方法返回false

  put()方法会阻塞

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

(1)什么是原子操作?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作。即-这几种行为要么同时完成,要么都不完成。

(2)AtomicInteger类常用方法:
   AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
   addAddGet(int dalta) : 以原子方式将给定值与当前值相加
    get() : 获取当前值

(3)原子操作主要有:
 对于引用变量和大多数原始变量(long和double除外)的读写操作;
 对于所有使用volatile修饰的变量(包括long和double)的读写操作。

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

        public AtomicInteger getAccount() {
            return account;
        }

        public void save(int money) {
            account.addAndGet(money);
        }
    }

参考:https://www.cnblogs.com/XHJT/p/3897440.html

6.生产环境少用volatile

猜你喜欢

转载自blog.csdn.net/Michaeles/article/details/86500835