• Java 多线程同步


    引言

      本文主要描述,Java中多线程共享数据(同步)/线程死锁/生产者与消费者应用案例。

      多线程的优势:资源利用率好、程序设计在某种情况下更简单、程序响应更快。

      进程和线程之间如何通信:进程间通讯依靠 IPC 资源,例如管道(pipes)、套接字(sockets)等;线程间通讯依靠 JVM 提供的 API,例如 wait()、notify()、notifyAll() 等方法,线程间还可以通过共享的主内存来进行值的传递。

    多线程共享数据(同步)

      线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行。

      多线程共享数据的安全问题,使用同步解决。

      创建一个多线程:同步方式按照如下所示

    public static void main(String[] args) {
            MyThread s0 = new MyThread();
            Thread t1 = new Thread(s0,"one");
            Thread t2 = new Thread(s0,"two");
            t1.start();
            t2.start();
        }

    同步代码块

      使用方式:synchronized(要同步的对象){ 要同步的操作 }

    class MyThread implements Runnable{
        Object obj = new Object(); //同步的标记对象
        @Override
        public void run() {
            //同步代码块
            synchronized(obj){
                System.out.println(Thread.currentThread().getName()+" is doing...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" finished.");
            }
        }
    }
    

    同步方法

      使用方式:public synchronized void method(){ 要同步的操作 },同步的是当前对象(this)

    public synchronized void doMethod(){
            System.out.println(Thread.currentThread().getName()+" is doing...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" finished.");
        }
    

    线程死锁

      当一个对象内,同步过多,会造成斯死锁。

      定义一个服务员类:Waiter,其下有两个同步方法

    public class Waiter {
    
        public synchronized void say(Customer c) {
            System.out.println("Waiter: Pay --> Do");
            c.doService();
        }
    
        public synchronized void doService() {
            System.out.println("Waiter: OK");
        }
    }
    

      定义一个消费者类:Customer,其下有两个同步方法

    public class Customer {
        public synchronized void say(Waiter w) {
            System.out.println("Customer: Do --> Pay");
            w.doService();
        }
    
        public synchronized void doService() {
            System.out.println("Customer: OK");
        }
    }
    

      当同时访问的时候,就会造成数据死锁。

    public class Main {
        public static void main(String[] args) {
            new MyThred();
        }
    }
    
    class MyThred implements Runnable {
        Customer c = new Customer();
        Waiter w = new Waiter();
    
        public MyThred() {
            new Thread(this).start();
            w.say(c);
        }
    
        @Override
        public void run() {
            c.say(w);
        }
    }
    

      总结:当 synchronized 用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

    生产者与消费者应用案例

      案例需求:生产者不断生成产品,消费者不断取走产品。

    创建共享资源类

    package lib;
    
    /**
     * 共享资源类
     */
    public class Storage {
        private String type;
        private String date;
    
        private boolean isEmpty = true;
    
        /**
         * 生产者向共享资源存储数据
         * @param type
         * @param date
         */
        public synchronized void push(String type, String date) {
            try {
                while (!isEmpty) {
                    // 当前共享资源状态不为空,等待消费者消费
                    this.wait();
                }
                // 生产开始
                this.type = type;
                Thread.sleep(10);
                this.date = date;
                // 生产结束
                isEmpty = false;
                this.notifyAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 消费者从共享资源中取出数据,并打印
         */
        public synchronized void popup() {
            try {
                while (isEmpty) {
                    //当资源为空,释放同步锁,进入等待
                    this.wait();
                }
                Thread.sleep(10);
                //消费开始
                System.out.println(this.type + "-->" + this.date);
                //消费结束
                isEmpty = true;
                this.notifyAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    }
    View Code

    创建生产者类

    package lib;
    
    /**
     * 生产者
     */
    public class Producer implements Runnable {
        private Storage storage;
    
        public Producer(Storage storage) {
            this.storage = storage;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                if (i%2==0) {
                    storage.push("apple", "20190625001");
                }
                else {
                    storage.push("banana", "20190625002");
                }
            }
        }
    }

    创建消费者类

    package lib;
    
    public class Consumer implements Runnable {
        private Storage storage;
    
        public Consumer(Storage storage) {
            this.storage = storage;
        }
    
        @Override
        public void run() {
            for (int i=0;i<20;i++) {
                storage.popup();
            }
        }
    }
    

    执行测试:两个生产者,两个消费者

    package lib;
    
    public class Test {
    
        public static void main(String[] args) {
            Storage storage = new Storage();
    
            Producer p1 = new Producer(storage);
            Producer p2 = new Producer(storage);
    
            Consumer c1 = new Consumer(storage);
            Consumer c2 = new Consumer(storage);
    
            Thread t1 = new Thread(p1, "P1");
            Thread t2 = new Thread(p2, "P2");
            Thread t3 = new Thread(c1, "C1");
            Thread t4 = new Thread(c2, "C2");
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
    
        }
    }
    

      总结说明: 

        wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他的线程唤醒该线程。
        notify:执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待。
        notifyAll():执行该方法的线程唤醒在等待池中等待的所有的线程,把线程转到锁池中等待。

    使用lock

      wait和notify方法,只能被同步监听锁对象来调用,否则报错IllegalMonitorStateException。那么现在问题来了,Lock机制根本就没有同步锁了,也就没有自动获取锁和自动释放锁的概念。因为没有同步锁,所以Lock机制不能调用wait和notify方法。解决方案:Java5中提供了Lock机制的同时提供了处理Lock机制的通信控制的Condition接口。
    从Java5开始,可以:
          1):使用Lock机制取代synchronized 代码块和synchronized 方法。
          2):使用Condition接口对象的await,signal,signalAll方法取代Object类中的wait,notify,notifyAll方法。

    package lib;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 共享资源类
     */
    public class Storage {
        private String type;
        private String date;
    
        private boolean isEmpty = true;
    
        private final Lock lock = new ReentrantLock();
        private Condition condition = lock.newCondition();
    
        /**
         * 生产者向共享资源存储数据
         * @param type
         * @param date
         */
        public void push(String type, String date) {
            lock.lock(); //获取对象锁
            try {
                while (!isEmpty) {
                    // 当前共享资源状态不为空,等待消费者消费
                    condition.await();
                }
                // 生产开始
                this.type = type;
                Thread.sleep(10);
                this.date = date;
                // 生产结束
                isEmpty = false;
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                lock.unlock(); //释放对象锁
            }
        }
    
        /**
         * 消费者从共享资源中取出数据,并打印
         */
        public void popup() {
            lock.lock();
            try {
                while (isEmpty) {
                    //当资源为空,释放同步锁,进入等待
                    condition.await();
                }
                Thread.sleep(10);
                //消费开始
                System.out.println(this.type + "-->" + this.date);
                //消费结束
                isEmpty = true;
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                lock.unlock();
            }
        }
    
    }
    View Code

     synchronized和lock的区别

    • 都是锁。解决线程安全问题
    • synchronized无法判断是否获得锁的状态,而lock可以判断
    • synchronized只能通过线程执行完之后释放,而lock可以手动释放
    • synchronized是关键字,而lock是接口
    • synchronized范围是整个方法或代码块,而lock是调用的方式,灵活性更好
    • synchronized会一直等待线程,不能响应中断,而lock可以
  • 相关阅读:
    effective c++ 条款10 让operator= 返回*this的引用
    基于vs2008的opengl开发环境的配置
    effective c++ 条款13 以对象管理资源
    effective C++ 条款06如果你不想让编译器为你生成函数就应该明确拒绝
    effective c++条款07为多态基类声明为virtual析构函数
    effective c++条款08别让异常逃离析构函数
    effective c++条款11 在operator=中处理“自我赋值”
    ubuntu 下的截图工具
    effective c++ 条款12 赋值对象时勿忘其每一个成员
    [linux 安装]修改centos的yum源
  • 原文地址:https://www.cnblogs.com/huanghzm/p/11078717.html
Copyright © 2020-2023  润新知