Java 多线程同步

时间:2021-07-14 23:25:13

引言

  本文主要描述,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 用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

生产者与消费者应用案例

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

创建共享资源类

Java 多线程同步Java 多线程同步
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方法。

Java 多线程同步Java 多线程同步
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