JUC学习笔记

JUC的学习笔记

这篇博文是通过学习B站尚硅谷李老师的JUC课程总结的笔记,在此非常感谢!
JUC:即java.util.concurrent包里的相关知识;

内存不可见性与内存可见性的概念

先来可一个测试结果不可思议的例子,如下代码:

package edu.hebeu.volatile_stu;

/**
 * @author 13651
 */
public class Demo1 {

	public static void main(String[] args) {
		MyRunnable myRunnable = new MyRunnable();
		
		new Thread(myRunnable).start(); // 启动线程
		
		while(true) {
			if(myRunnable.isFlag()) {
				System.out.println("收到flag为" + myRunnable.isFlag() + ";准备结束循环");
				break;
			}
			
//			System.out.println("flag为:" + myRunnable.isFlag() + ";继续循环..."); // 不要加任何输出语句,否则会影响while的执行效率,导致本例演示不成功
		}
	}
}

class MyRunnable implements Runnable {

	private boolean flag = false;

	@Override
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		flag = true;
		System.out.println("通知:flag已经变成:" + isFlag());
	}

	public boolean isFlag() {
		return flag;
	}

	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	
}

我们运行测试一下,发现会出现如下问题:
在这里插入图片描述
可以发现程序此时仿佛死锁一样,不在向下执行了,但是造成该现象的并非是死锁,而是因为:

  • JVM为了提高效率,会在每个线程中开辟独立的缓存区域,将读取到的数据放到此,当修改该数据时,会先将缓存区域的数据修改,再将该数据更新至主存;但是main线程由于在while(true)中没有其他的操作因而执行非常快,导致没有延迟的时间去主存中重新读取新的数据(主存内的数据是true,而main线程缓存的却是false),因此就会出现上面的情况;

如果还有疑惑看看如下的图解:
对于如下的一个main线程线程1,如下图解并引出内存不可见性的概念:
在这里插入图片描述
如何解决上述的问题?

  • 方式一:在 if(myRunnable.isFlag()) 外用synchronized(td)包裹,使同步化,就能解决,但是只有牵扯到锁,就会导致程序的效率非常低!代码如下所示:
    		synchronized(myRunnable) { // 方式一的解决办法
    			if(myRunnable.isFlag()) {
    				System.out.println("收到flag为" + myRunnable.isFlag() + ";准备结束循环");
    				break;
    			}
    		}
    
  • 方式二:在共享的数据上添加volatile关键字,如下所示:
    private volatile boolean flag = false; // 使用方式二的解决办法
    

Volatile关键字

volatile关键字的作用:

  • 当多个线程操作共享数据时,保证内存可见性!
  • 该关键字底层调用的是计算机底层代码,“内存栅栏”,以实时的刷新数据;
  • 使用volatile关键字JVM就不能对被其修饰的变量重排序,因此效率会变低!
  • 但是相较于方式一synchronized锁,效率高!

volatile关键字的注意:

  • 相较于synchronized不具有"互斥性";
  • 不能保证变量的"原子性";
原子性的概念

先来看一个面试题,如下代码,问输出的结果为多少?

int j = 20;
j = j++;
System.out.println("j = " +j);

答案:j输出20,因为i++的底层实际是:

int tmp = j;
j = j + 1;
j = tmp;

所以产生了如上的结果,是因为 i++就是"读->改->写",没有原子性

再来分析下面的例子,代码如下:

package edu.hebeu.volatile_stu;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author 13651
 *
 */
public class AtomicDemo {
	public static void main(String[] args) {
		MyRunnable2 myRunnable2 = new MyRunnable2();
		
		for (int i = 0; i < 20; i++) {
			new Thread(myRunnable2).start(); // 启动20个线程会发现出现问题,有可能产生重复数据!!!
		}
	}
}

class MyRunnable2 implements Runnable {
	
	private int i = 0;

	@Override
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "; i = " + getAdd());
	}
	
	public int getAdd() {
		return i++;
	}
}

测试:
在这里插入图片描述
产生上面的原因(一个值输出了多次,如0、1)就是因为i++操作不是一步的而是多步的(读->改->写),此时就算使用了volatile关键字保证内存可见,仍会出现上述的问题!
如何解决?
使用原子变量代替原先的变量和计算,就不会出现上面的问题了;原子变量:jdk1.5之后java.util.concurrent.atomic包下提供的常用原子变量:

  • 1、该包下的所有类,类似于包装类,如对Integer的、String的、甚至数组的、…;这些类内部的变量都使用了volatile关键字来保证内存的可见性;
  • 底层使用CAS(Compare-And-Swap)算法保证数据的原子性;

对进行改写,如下代码:

class MyRunnable2 implements Runnable {
	
	private AtomicInteger i = new AtomicInteger(); // 使用该对象代替原先的变量

	@Override
	public void run() {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + "; i = " + getAdd());
	}
	
	public int getAdd() {
		return i.getAndIncrement(); // 使用内部的实例方法代替原先的++操作,同样的也有其他的数学计算操作,具体查看API可知
	}
}

测试
在这里插入图片描述
发现已经没有重复数据了;

CAS算法

上述的原子性介绍中引出了CAS算法,它具体是什么?
CAS算法是硬件对于并发操作共享数据的支持;CAS算法包括了三个操作数:有:内存值 V、预估值 A、更新值 B;当且仅当 V == A时,V = B,否则将不做任何操作;
我们通过一段代码来模拟CAS算法,如下所示:

package edu.hebeu.volatile_stu;

/**
 * @author 13651
 *
 */
public class CASDemo {
	public static void main(String[] args) {
		
		final CompareAndSwap compareAndSwap = new CompareAndSwap();
		
		for(int i = 0; i < 20; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					int expectedValue = compareAndSwap.get(); // 通过内存值赋给当前的预估值
					boolean isTrue = compareAndSwap.compareAndSet(expectedValue, (int) (Math.random() * 100));
					System.out.println(isTrue);
				}
			}).start();
		}
	}
}

class CompareAndSwap {
	
	private int value;
	
	/**
	 * 获取内存值
	 * @return
	 */
	public synchronized int get() {
		return value;
	}
	
	/**
	 * 比较旧值与预估值
	 * @param expectedValue
	 * @param newValue
	 * @return
	 */
	public synchronized int compareAndSwap(int expectedValue, int newValue) {
		int oldValue = value;
		// 如果旧值等于预估值
		if(oldValue == expectedValue) {
			this.value = newValue;
		}
		return oldValue;
	}
	
	/**
	 * 比较并设置
	 * @param expectedValue
	 * @param newValue
	 * @return
	 */
	public synchronized boolean compareAndSet(int expectedValue, int newValue) {
		return expectedValue == compareAndSwap(expectedValue, newValue);
	}
	
}

测试结果:
在这里插入图片描述

线程池

线程池:提供一个线程队列,队列中保存着所有等待状态的线程,避免了创建/销毁线程的一些额外开销,提高了响应的速度;
线程池的体系结构:

  • java.util.concurrent.Executor:负责线程使用与调度的根接口
    • ExecutorService:子接口,负责线程主要接口
      • ThreadPoolExecutor:线程池的实现类
      • ScheduledExecutorService:子接口,负责线程的调度
        • ScheduleThreadPoolExecutor:实现类,继承了ThreadPoolExecutor,又实现了ScheduleExecutorService

*线程池的工具类Executors*的常用方法:

  • ExecutorService newFixedThreadPool(int nThreads); // 创建固定容量大小的线程池
  • ExecutorService newCachedThreadPool(); // 创建缓存线程池,容量不固定,可以根据需求自动的更改数量
  • ExecutorService newSingleThreadExecutor(); // 创建容量为单个的线程池,即线程池中只有一个线程
  • ScheduledExecutorService new ScheduledThreadPool(); // 创建固定容量的线程池,可以延迟或定时的执行任务

使用如下代码对上述的类和方法进行测试:

package edu.hebeu.thread_pool;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * @author 13651
 */
public class ThreadPoolStu {
	public static void main(String[] args) {
//		runnableType(); // 调用Runnable方式
//		callableType(); // 调用Callable方式
		newScheduledThreadPoolMethod(); // 使用newScheduledThreadPool()创建的线程池来学习线程调度

	}
	
	/**
	 * Runnable的方式
	 */
	private static void runnableType() {
		MyRunnable myRunnable = new MyRunnable();
		
		// 1、创建线程池对象
		ExecutorService pool = Executors.newFixedThreadPool(5); // 创建容量为5的线程池对象,即最多5个线程
		// 2、为线程池中的线程分配任务
		for(int i = 0; i < 10; i++) { // 分配10个任务
			pool.submit(myRunnable);
		}
		// 3、关闭线程池,注意:shutdown()是比较平和的关闭线程池对象;shutdownNow()是立即关闭线程池对象
		pool.shutdown();
	}
	
	/**
	 * Callable的方式
	 */
	private static void callableType() {
		MyCallable callable = new MyCallable();
		List<Future<Integer>> futureList = new ArrayList<>();
		
		// 1、创建线程池对象
		ExecutorService pool = Executors.newFixedThreadPool(3); // 创建容量为3的线程池对象,即最多3个线程
		// 2、为线程池中的线程分配任务
		for(int i = 0; i < 10; i++) { // 分配10个任务
			Future<Integer> future = pool.submit(callable);
			
			futureList.add(future);
		}
		// 3、关闭线程池
		pool.shutdown();
		
		for(Future<Integer> f : futureList) {
			try {
				System.out.println(f.get());
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ExecutionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 使用newScheduledThreadPool()方法创建线程池对象
	 */
	private static void newScheduledThreadPoolMethod() {
		// 1、创建线程池对象
		ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
		// 2、分配任务
		for(int i = 0; i <= 5; i++) { // 分配5个任务
			Future<Integer> res = pool.schedule(new Callable<Integer>() {
				@Override
				public Integer call() throws Exception {
					int i = new Random().nextInt(100);
					System.out.println(Thread.currentThread().getName() + ": " + i);
					return i;
				}
			}, 3, TimeUnit.SECONDS); // 该任务会在3秒之后执行
			
			try {
				System.out.println(res.get()); // 输出线程执行的结果
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ExecutionException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		// 3、关闭线程池
		pool.shutdown();
	}
}

class MyRunnable implements Runnable {

	private int i = 0;
	
	@Override
	public void run() {
		while(i <= 100) {
			System.out.println(Thread.currentThread().getName() + " : " + ++i);
		}
	}
}

class MyCallable implements Callable<Integer> {
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for(int i = 0; i <= 100; i++) {
			sum += i;
		}
		return sum;
	}
}

测试
只对上述的非注释代码测试,注释的代码读者可以自己尝试测试一下:
在这里插入图片描述

Lock类

我们之前为了解决线程安全问题使用的方法:使用synchronized关键字:解决,该关键字主要有两种作用:

  • 同步代码块
  • 同步方法

但是可以发现,以上的都是使用JVM提供的锁,是隐式的锁!自JDK1.5后,可以使用Lock类解决线程安全问题,该锁是显示的锁,需要通过lock()方法上锁,且最后必须通过unlock()方法释放锁!先来通过如下的代码看下Lock类的简单使用方法:

package edu.hebeu.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 13651
 * 
 */
public class LockStu {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		
		new Thread(ticket, "1号窗口").start();
		new Thread(ticket, "2号窗口").start();
		new Thread(ticket, "3号窗口").start();
	}
}

class Ticket implements Runnable {

	private int tickCount = 30; // 30张票
	
	private Lock myLock = new ReentrantLock(); // 创建Lock对象实例
	
	@Override
	public void run() {
		while(true) {
			try {
				Thread.sleep(200); // 睡200ms
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			myLock.lock(); // 锁住
			try {
				if(tickCount == 0) {
					System.out.println("票已售空");
					break;
				}
				System.out.println(Thread.currentThread().getName() + ":已售出票,剩余票数:" + --tickCount);
			} finally {
				myLock.unlock(); // 在最后释放锁
			}
		}
	}
}

测试:
在这里插入图片描述
可以发现实现了线程的安全执行,没有出现线程安全问题;

线程交替执行

我们通过如下的一个题目引出线程交替执行是如何实现的:
编写一个程序,开启3个线程,这三个线程的名字分别是A、B、C,每个线程将自己的名字在控制台打印10遍,要求输出的结果必须按顺序显示为:AAABBBCCCAAABBBCCCAAABBBCCC…六次(即六次AAABBBCCC轮询)

package edu.hebeu.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author 13651
 */
public class ThreadAlternate {
	public static void main(String[] args) {
		
		MyAlternate alternate = new MyAlternate();
		
		new Thread(() -> {
			for(int i = 0; i < 6; i++) { // 打印六次A,即轮询6次
				alternate.loopA();
			}
		}, "A").start(); // 启动A线程,执行loopA()方法打印A
		
		new Thread(() -> {
			for(int i = 0; i < 6; i++) { // 打印六次C,即轮询6次
				alternate.loopB();
			}
		}, "B").start(); // 启动B线程,执行loopB()方法打印B
		
		new Thread(() -> {
			for(int i = 0; i < 6; i++) { // 打印六次C,即轮询6次
				alternate.loopC();
			}
		}, "C").start(); // 启动C线程,执行loopC()方法打印C
		
	}
}

class MyAlternate {
	private int number = 1; // 用来表示当前执行的线程,1就是A、2就是B,3就是C
	private Lock lock = new ReentrantLock();
	
	private Condition conditionA = lock.newCondition(); // A的通信对象
	private Condition conditionB = lock.newCondition(); // B的通信对象
	private Condition conditionC = lock.newCondition(); // C的通信对象
	
	public void loopA() {
		lock.lock(); // 上锁
		try {
			/*判断*/
			if(number != 1) { // 如果标记不是1,即不是A线程
				try {
					conditionA.await(); // 导致当前线程等到发信号或 interrupted
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			/*程序执行到此,说明是A线程,则打印*/
			for(int i = 0; i < 3; i++) {
				System.out.print(Thread.currentThread().getName());
			}
			/*唤醒B线程*/
			number = 2; // 修改标记为2
			conditionB.signal(); // 唤醒一个等待线程
		} finally {
			lock.unlock(); // 释放锁
		}
	}
	
	public void loopB() {
		lock.lock(); // 上锁
		try {
			/*判断*/
			if(number != 2) { // 如果标记不是2,即不是B线程
				try {
					conditionB.await(); // 导致当前线程等到发信号或 interrupted
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			/*程序执行到此,说明是B线程,则打印*/
			for(int i = 0; i < 3; i++) {
				System.out.print(Thread.currentThread().getName());
			}
			/*唤醒C线程*/
			number = 3; // 修改标记为3
			conditionC.signal(); // 唤醒一个等待线程
		} finally {
			lock.unlock(); // 释放锁
		}
	}
	
	public void loopC() {
		lock.lock(); // 上锁
		try {
			/*判断*/
			if(number != 3) { // 如果标记不是3,即不是C线程
				try {
					conditionC.await(); // 导致当前线程等到发信号或 interrupted
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			/*程序执行到此,说明是C线程,则打印*/
			for(int i = 0; i < 3; i++) {
				System.out.print(Thread.currentThread().getName());
			}
			/*唤醒A线程*/
			number = 1; // 修改标记为1
			conditionA.signal(); // 唤醒一个等待线程
		} finally {
			lock.unlock(); // 释放锁
		}
	}
}

测试
在这里插入图片描述

读写锁

通过一个例子来认识读写锁,举例:如果有多个线程操作一个共享数据,如下情况:

  1. 读和写操作:此时为了保证数据的安全,需要互斥(读和写不能同时进行)
  2. 写和写操作:此时为了保证数据的安全,也需要互斥(写和写之间不能同时进行)
  3. 读和读操作:此时数据没有安全性的问题,不需要互斥(读和读之间可以同时进行)

通过如下的例子来实现读写锁:

package edu.hebeu.lock;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author 13651
 *
 */
public class ReadWriteLockStu {
	public static void main(String[] args) {
		ReadWrite readWrite = new ReadWrite();
		
		/*读的线程*/
		for(int i = 1; i <= 4; i++) { // 4个读线程
			new Thread(() -> {
				while(true) { // 一直读
					readWrite.read();
					try {
						Thread.sleep(50); // 睡50ms
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}, "Read" + i).start();
		}
		
		/*一个写的线程*/
		new Thread(() -> {
			for(int i = 0; i < 2; i++) { // 两次写操作
				int writeData = (int) (Math.random() * 101);
				readWrite.write(writeData);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}, "Write1").start();
		
		/*一个写的线程*/
		new Thread(() -> {
			for(int i = 0; i < 2; i++) { // 两次写操作
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				int writeData = (int) (Math.random() * 101);
				readWrite.write(writeData);
			}
		}, "Write2").start();
	}
}

class ReadWrite {
	
	private int data; // 操作的共享数据
	private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读写锁对象
	
	/**
	 * 读操作,只有读操作时可以被多个线程并发的读,不需要考虑线程安全问题
	 */
	public void read() {
		readWriteLock.readLock().lock(); // 读上锁
		try {
			System.out.println(Thread.currentThread().getName() + ":data=" + data);
		} finally {
			readWriteLock.readLock().unlock(); // 读释放锁
		}
	}
	
	/**
	 * 写操作,有写操作时就需要保证数据的安全,需要考虑线程安全问题
	 * @param writeData 写入的数据
	 */
	public void write(int writeData) {
		readWriteLock.writeLock().lock(); // 写上锁
		try {
			data = writeData;
			System.out.println(Thread.currentThread().getName() + ":data -> " + data);
		} finally {
			readWriteLock.writeLock().unlock(); // 写释放锁
		}
	}
	
}

测试
在这里插入图片描述

线程八锁

线程八锁,通过如下的一个题目来引出,先看代码:

package edu.hebeu.lock;

/**
 * @author 13651
 *
 */
public class ThreadLockEight {
	
	public static void main(String[] args) {
		ThreadLockEight eight = new ThreadLockEight();
		
//		eight.lock1(); // 第一种锁
//		eight.lock2(); // 第二种锁
//		eight.lock3(); // 第三种锁
//		eight.lock4(); // 第四种锁
//		eight.lock5(); // 第五种锁
//		eight.lock6(); // 第六种锁
//		eight.lock7(); // 第七种锁
		eight.lock8(); // 第八种锁
		
	}
	
	/**
	 * 第一种锁
	 */
	private void lock1() {
		Myprint myprint = new Myprint();
		
		new Thread(() -> {
			myprint.loopONE();
		}).start();
		
		new Thread(() -> {
			myprint.loopTWO();
		}).start();
	}
	
	private void lock2() {
		Myprint myprint = new Myprint();
		
		new Thread(() -> {
			myprint.loopONESL();
		}).start();
		
		new Thread(() -> {
			myprint.loopTWO();
		}).start();
	}
	
	private void lock3() {
		Myprint myprint = new Myprint();
		
		new Thread(() -> {
			myprint.loopONESL();
		}).start();
		
		new Thread(() -> {
			myprint.loopTWO();
		}).start();
		
		new Thread(() -> {
			myprint.loop_three();
		}).start();
	}
	
	private void lock4() {
		Myprint myprint1 = new Myprint();
		Myprint myprint2= new Myprint();
		
		new Thread(() -> {
			myprint1.loopONESL();
		}).start();
		
		new Thread(() -> {
			myprint2.loopTWO();
		}).start();
	}
	
	private void lock5() {
		Myprint myprint1 = new Myprint();
		Myprint myprint2= new Myprint();
		
		new Thread(() -> {
			myprint1.loopONESS();
		}).start();
		
		new Thread(() -> {
			myprint2.loopTWO();
		}).start();
	}
	
	private void lock6() {
		Myprint myprint = new Myprint();
		
		new Thread(() -> {
			myprint.loopONESS();
		}).start();
		
		new Thread(() -> {
			myprint.loopTWOS();
		}).start();
	}
	
	private void lock7() {
		Myprint myprint1 = new Myprint();
		Myprint myprint2 = new Myprint();
		
		new Thread(() -> {
			myprint1.loopONESS();
		}).start();
		
		new Thread(() -> {
			myprint2.loopTWO();
		}).start();
	}
	
	private void lock8() {
		Myprint myprint1 = new Myprint();
		Myprint myprint2 = new Myprint();
		
		new Thread(() -> {
			myprint1.loopONESS();
		}).start();
		
		new Thread(() -> {
			myprint2.loopTWOS();
		}).start();
	}
}

class Myprint {
	
	public static synchronized void loopONES() {
		System.out.println("one");
	}
	
	public static synchronized void loopTWOS() {
		System.out.println("two");
	}
	
	public static synchronized void loopThreeS() {
		System.out.println("three");
	}
	
	public static synchronized void loopONESS() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("one");
	}
	
	public static synchronized void loopTWOSS() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("two");
	}
	
	public static synchronized void loopThreeSS() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("three");
	}
	
	public synchronized void loopONE() {
		System.out.println("one");
	}
	
	public synchronized void loopTWO() {
		System.out.println("two");
	}
	
	public synchronized void loopThree() {
		System.out.println("three");
	}
	
	public synchronized void loopONESL() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("one");
	}
	
	public synchronized void loopTWOSL() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("two");
	}
	
	public synchronized void loopThreeSL() {
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("three");
	}
	
	public void loop_one() {
		System.out.println("one");
	}
	
	public void loop_two() {
		System.out.println("two");
	}
	
	public void loop_three() {
		System.out.println("three");
	}
}

对上述的代码中main()方法内的8个方法(每个方法对应着一种锁,线程八锁),依次打开注释并允许测试,结果如下:

  • 第一锁:创建一个Myprint对象,调用loopONE()、调用loopTwo(),打印结果为:one、two
  • 第二锁:创建一个Myprint对象,调用loopONESL()、调用loopTWO(),打印结果为:(延迟3秒) one two
  • 第三锁:创建一个Myprint对象,调用loopONESL()、调用loopTWO()、调用loop_three(),打印结果:three (延迟3秒) one two
  • 第四锁:创建两个Myprint对象,分别这两个一个调用loopONESL()、一个调用loopTWO(),打印结果:two (延迟3秒) one
  • 第五锁:创建两个Myprint对象,分别这两个一个调用loopONESS()、一个调用loopTWO(),打印结果:two (延迟3秒) one
  • 第六锁:创建一个Myprint对象,调用loopONESS()、调用loopTWOS(),打印结果:(延迟3秒) one two第七锁:创建两个Myprint对象,分别这两个一个调用loopONESS()、一个调用loopTWO(),打印结果:two (延迟3秒) one
  • 第八锁:创建两个MyPrint对象,分别这两个一个调用loopONESS()、一个调用loopTWOS(),打印结果为:(延迟3秒) one two

使用传统方式和使用Lock方式实现生产者消费者案例

代码演示:

package edu.hebeu.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 这个例子展示传统的生产者消费者案例
 * @author 13651
 *
 */
public class ProducerAndConsumer {
	public static void main(String[] args) {
//		Store store1 = new Store(); // 传统方式控制
		Store2 store2 = new Store2(); // Lock方式控制
		
//		Producer producer = new Producer(store1);
//		Consumer consumer = new Consumer(store1);
		Producer producer = new Producer(store2);
		Consumer consumer = new Consumer(store2);
		
		new Thread(producer, "生产者A").start();
		new Thread(producer, "生产者B").start();
		new Thread(producer, "生产者C").start();
		new Thread(consumer, "消费者1").start();	
		new Thread(consumer, "消费者2").start();	
		new Thread(consumer, "消费者3").start();	
	}
}

/**
 * 仓库
 * 使用传统的方式实现
 * @author 13651
 *
 */
class Store {
	
	private int productCount = 0; // 仓库的产品数量
	
	/**
	 * 进货
	 */
	public synchronized void put() {
		// 如果产品的数量大于等于10,即货满;注意要使用while包裹wait()方法,否则当有多个生产者和消费者线程启动时会出现虚假等待!
		while(productCount >= 1) {
			System.out.println("货满,生产者等待!");
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		/*
		 * 这里建议不使用else,如果加上else,可以分析在最后某个线程是没有停止的,
		 * 因为有else语句,此时另一个线程如果如果是最后一次执行,导致其在执行if的语句后,else的语句无
		 * 法执行(else内的notfiyAll()无法执行,则其他的wait进程无法唤醒,且该线程此时已经执行完了),
		 * 当某个线程在if内的wait()就无法被另一个线程唤醒,该线程会陷入一直wait()的状态,导致程序无法
		 * 终止!
		 */
		System.out.println(Thread.currentThread().getName() + ":已进货,产品:" + ++productCount);
		this.notifyAll();
	}
	
	/**
	 * 出货
	 */
	public synchronized void out() {
		// 如果产品的数量小于等于0,即缺货;注意要使用while包裹wait()方法,否则当有多个生产者和消费者线程启动时会出现虚假等待!
		while(productCount <= 0) {
			System.out.println("缺货,消费者等待!");
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		/*
		 * 这里建议不使用else,如果加上else,可以分析在最后某个线程是没有停止的,
		 * 因为有else语句,此时另一个线程如果如果是最后一次执行,导致其在执行if的语句后,else的语句无
		 * 法执行(else内的notfiyAll()无法执行,则其他的wait进程无法唤醒,且该线程此时已经执行完了),
		 * 当某个线程在if内的wait()就无法被另一个线程唤醒,该线程会陷入一直wait()的状态,导致程序无法
		 * 终止!
		 */
		System.out.println(Thread.currentThread().getName() + ":已出货,产品:" + --productCount);
		this.notifyAll();
	}
	
}

/**
 * 仓库
 * 使用Lock的方式实现
 * @author 13651
 *
 */
class Store2 {
	
	private int productCount = 0; // 仓库的产品数量
	private Lock lock = new ReentrantLock(); // 锁
	private Condition condition = lock.newCondition(); // 通过锁对象获取这个对象,该对象用来实现类似于wait、notify的操作
	
	
	/**
	 * 进货
	 */
	public void put() {
		lock.lock(); // 上锁
		try {
			// 如果产品的数量大于等于10,即货满;注意要使用while包裹wait()方法,否则当有多个生产者和消费者线程启动时会出现虚假等待!
			while(productCount >= 1) {
				System.out.println("货满,生产者等待!");
				try {
					condition.await(); // 相当于wait
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + ":已进货,产品:" + ++productCount);
			condition.signalAll(); // 相当于notifyAll
		} finally {
			lock.unlock(); // 释放锁
		}
	}
	
	/**
	 * 出货
	 */
	public void out() {
		lock.lock(); // 上锁
		try {
			// 如果产品的数量小于等于0,即缺货;注意要使用while包裹wait()方法,否则当有多个生产者和消费者线程启动时会出现虚假等待!
			while(productCount <= 0) {
				System.out.println("缺货,消费者等待!");
				try {
					condition.await(); // 相当于wait
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + ":已出货,产品:" + --productCount);
			condition.signalAll(); // 相当于notifyAll
		} finally {
			lock.unlock(); // 释放锁
		}
	}
}

/**
 * 生产者
 * @author 13651
 *
 */
class Producer implements Runnable {
	private Store store;
	
	private Store2 store2;
	
	public Producer(Store store) {
		this.store = store;
	}
	
	public Producer(Store2 store2) {
		this.store2 = store2;
	}

	@Override
	public void run() {
		for(int i = 0; i < 30; i++) { // 生产30次
			try {
				Thread.sleep(200); // 模拟延迟200ms
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
//			store.put(); // 进货,进行生产
			store2.put(); // 进货,进行生产
		}
	}
}

/**
 * 消费者
 * @author 13651
 *
 */
class Consumer implements Runnable {
	private Store store;
	
	private Store2 store2;
	
	public Consumer(Store store) {
		this.store = store;
	}
	
	public Consumer(Store2 store2) {
		this.store2 = store2;
	}

	@Override
	public void run() {
		for(int i = 0; i < 30; i++) { // 消费30次
//			store.out(); // 出货,进行消费
			store2.out(); // 进货,进行生产
		}
	}
}

测试
在这里插入图片描述

CountDownLatch类

闭锁(CountDownLatch):指在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行;
通过如下的需求和代码实现展示CountDownLatch类的使用,需求:计算所有线程全部执行完所需的时间,代码如下:

package edu.hebeu.count_down_latch;

import java.util.concurrent.CountDownLatch;

/**
 * 这个例子展示CountDownLatch类的使用
 * 
 * 闭锁(CountDownLatch):指在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才继续执行;
 * 
 * 如本类的计算所有线程全部执行完所需的时间
 * 
 * @author 13651
 *
 */
public class CountDownLatchStu {

	public static void main(String[] args) {
		
		final CountDownLatch cdLatch = new CountDownLatch(10); // 表示有10个线程使用这个闭锁对象
		
		MyRunnable myRunnable = new MyRunnable(cdLatch);
		
		long start = System.currentTimeMillis();
		for(int i = 0; i < 10; i++) { // 启动10个线程
			new Thread(myRunnable).start();
		}
		try {
			cdLatch.await(); // 中断
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		long end = System.currentTimeMillis();
		
		System.out.println("所有线程执行完的耗费时长:" + (end - start) + "ms");
		
	}
}

class MyRunnable implements Runnable {

	private CountDownLatch cdLatch;
	
	public MyRunnable(CountDownLatch cdLatch) {
		this.cdLatch = cdLatch;
	}
	
	@Override
	public void run() {
		synchronized (this) {
			try {
				for(int i = 0; i <= 5000000; i++) {
					if(i %2 == 0) {
						System.out.println("ThreadName = " + Thread.currentThread().getName() +"---" + i);
					}
				}
			} finally {
				cdLatch.countDown(); // 在线程执行完的最后一步,将该闭锁对象的count--
			}
		}
	}
}

测试
这里执行了10个线程,每个线程计算了5000000次判断计算和打印,所以就不把运行结果截完了,直接看最终结果:
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值