引言
本文主要描述,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(); } } }
创建生产者类
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(); } } }
synchronized和lock的区别
- 都是锁。解决线程安全问题
- synchronized无法判断是否获得锁的状态,而lock可以判断
- synchronized只能通过线程执行完之后释放,而lock可以手动释放
- synchronized是关键字,而lock是接口
- synchronized范围是整个方法或代码块,而lock是调用的方式,灵活性更好
- synchronized会一直等待线程,不能响应中断,而lock可以