【零】JUC

懵懂的女人 提交于 2020-01-08 20:45:53

一、JUC

在 Java 5.0 提供了 java.util.concurrent (简称 JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的 Collection 实现等。

二、多线程

2.1 进程和线程

  1. 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码(静态的)。
  2. 进程(process)是程序的一次执行过程,或是正在运行的一个程序。进程是一个动态过程,即有它自身的产生、存在和消亡的过程。每个Java程序都有一个隐含的主程序,即main方法(动态的,是线程的集合)。
  3. 线程(thread),线程是进程内部的一条具体的执行路径。若一个程序可同一时间执行多个线程,就是支持多线程的(一个线程是进程中的一条执行路径)。

2.2 多线程的优势

  1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
  4. 使用线程可以将耗时任务放到后台去处理,例如等待用户输入、文件读写和网络收发数据等。

2.3 线程安全

当多个线程同时共享同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

2.4 线程安全的解决方式

使用多线程之间同步锁或使用锁(lock)可以解决线程安全问题

2.4.1 使用同步代码块

synchronized(同一个对象){
 				可能会发生线程冲突问题
}	

在同步代码块中,多个线程必须使用的是同一把锁,即同一个对象

2.4.2 使用同步方法

class Ticket
{
	private int number = 30;
	
	
	public synchronized void sale() {
		
		if (number > 0) {
			System.out.println(Thread.currentThread().getName() + "卖出第:\t" + (number--) + "\t 还剩下:" + number);
		}
	}

}

用Thread继承的方式实现多线程,那么同步方法需要是一个静态的方法!

2.4.3 使用Lock解决线程安全

class Ticket
{
	private int number = 30;
	
	//创建锁
	Lock lock = new ReentrantLock();

	public void sale() {
		//上锁
		lock.lock();
		try {
			if (number > 0) {
				System.out.println(Thread.currentThread().getName() + "卖出第:\t" + (number--) + "\t 还剩下:" + number);
			}
		} finally {
			//解锁
			lock.unlock();
		}
		
	}
}

  1. Lock实现提供更广泛的锁定操作可以比使用 synchronized获得方法和声明更好
  2. 他们允许更灵活的结构,可以有完全不同的特性,可以支持多个相关的 Condition对象。
  3. Lock提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

2.5 线程同步

多个线程共享同一个资源的环境下,每个线程工作时不会受到其他线程的干扰称之为多线程之间的同步。

三、多线程的创建方式

3.1 继承Thread类

  1. 定义子类继承Thread类。
  2. 子类中重写Thread类中的run方法。
  3. 创建Thread子类对象,即创建了线程对象。
  4. 调用线程对象start方法启动线程,默认调用run方法。

3.2 实现Runnable接口(优先使用)

  1. 定义子类,实现Runnable接口。
  2. 子类中重写Runnable接口中的run方法。
  3. 通过Thread类含参构造器创建线程对象,将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中。
  4. 调用Thread类的start方法启动线程,其最终调用Runnable子类接口的run方法。

3.3 区别

  1. 继承Thread:线程代码存放Thread子类run方法中。
  2. 实现Runnable:线程代码存在接口的子类的run方法中。
  3. 实现Runnable接口避免了单继承的局限性,多个线程可以共享同一个接口子类的对象,非常适合多个相同线程来处理同一份资源。

3.4 使用Callable接口

  1. 相比run()方法,可以有返回值
  2. 方法可以抛出异常
  3. 支持泛型的返回值
  4. 需要借助FutureTask类,比如获取返回结果。

3.4.1 Future接口

在这里插入图片描述

  1. 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
  2. FutrueTask是Futrue接口的实现类
  3. FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
  4. 多个线程同时执行一个FutureTask,只要一个线程执行完毕,其他线程不会再执行其call()方法。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

class MyThread implements Callable<Integer> {
	@Override
	public Integer call() throws Exception {
		System.out.println(Thread.currentThread().getName()+" Come in call");
		//睡5秒
		TimeUnit.SECONDS.sleep(5);
		//返回200的状态码
		return 200;
		
	}
}


public class CallableTest {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//创建MyThread对象
		MyThread myThread = new MyThread();
		//创建未来任务对象
		FutureTask<Integer> futureTask = new FutureTask<>(myThread);
		//创建处理未来任务的线程
		new Thread(futureTask, "这是未来任务").start();
		System.out.println(Thread.currentThread().getName()+"主线程继续");
		//获取未来任务的结果
		Integer result = futureTask.get();
		System.out.println(result);
		Integer result2 = futureTask.get();
		System.out.println(result2);
	}

}

3.5 使用线程池

JDK 5.0起提供了ExecutorServiceExecutors来实现线程池。

3.5.1 优势

  1. 提高响应速度(减少了创建新线程的时间)
  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  3. 便于线程管理

3.5.2 ExecutorService

  1. 真正的线程池接口。常见子类ThreadPoolExecutor
  2. void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
  3. Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
  4. void shutdown() :关闭连接池

3.5.3 创建线程池的两种方式

  1. 直接通过ThreadPoolExecutor实现类new
  2. 通过工厂类Executors的静态方法创建,本质上也是通过1创建的线程池
public static void main(String[] args) {
		//创建一个包含10个线程的线程池
		ExecutorService executorService = Executors.newFixedThreadPool(10);
//		ExecutorService executorService = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 12; i++) {
			executorService.execute(()->{
				System.out.println(Thread.currentThread().getName());
			});
		}
		executorService.shutdown();
	}

四、JUC工具类

4.1 ReentrantReadWriteLock

  1. 对共享资源有读和写的操作,且写操作没有读操作那么频繁
  2. 在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源
  3. 如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyQueue
{
	//创建读写锁
	ReadWriteLock rwl = new ReentrantReadWriteLock();
	
	private Object obj;

	
	public void readObj()
	{
		//上读锁
		rwl.readLock().lock();
		try {
			System.out.println(Thread.currentThread().getName()+"读到的内容是:"+obj);
		} finally {
			//解读锁
			rwl.readLock().unlock();
		}
	}
	
	public void writeObj(Object obj)
	{
		//上写锁
		rwl.writeLock().lock();
		try {
			this.obj = obj;
			System.out.println(Thread.currentThread().getName()+"写入的内容是:"+obj);
		} finally {
			//解写锁
			rwl.writeLock().unlock();
		}
	}
	
}

/**
 * 
 * @Description: 一个线程写入,100个线程读取
 * 
 */
public class ReadWriteLockDemo{
	
	public static void main(String[] args) throws InterruptedException{
		/*
		 * JAVA的并发包提供了读写锁ReentrantReadWriteLock,
		 * 它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。
		 * 
		 * */
		
		
		//创建资源对象
		MyQueue mq = new MyQueue();
		
		//一个线程写
		new Thread(()->{
			mq.writeObj("快放学了");
		}, "AA").start();
		
		//100个线程读
		for (int i = 1; i <= 100; i++) {
			new Thread(()->{
				//读内容
				mq.readObj();
			}, String.valueOf(i)).start();
		}
	}
}

4.2 CountDownLatch

CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.SynchronousQueue;


/**
 * 
 * @Description:
 *  *让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒。
 * 
 * 
 * 解释:6个同学陆续离开教室后值班同学才可以关门。
 * 
 * main主线程必须要等前面6个线程完成全部工作后,自己才能开干 
 */
public class CountDownLatchDemo
{
	public static void main(String[] args) throws InterruptedException
	{
		CountDownLatch cd = new CountDownLatch(6);
		
		//6个同学离开教室
		for (int i = 1; i <= 6; i++) {
			new Thread(()->{
				System.out.println(Thread.currentThread().getName()+" 号同学离开教室!");
				//减少计数
				cd.countDown();
			}, String.valueOf(i)).start();
		}
		
		//等待
		cd.await();
		//班长锁门
		System.out.println(Thread.currentThread().getName()+"班长锁门!");
	}


}

4.3 CyclicBarrier

CyclicBarrier的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程进入屏障通过CyclicBarrier的await()方法。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * 
 * @Description: TODO(这里用一句话描述这个类的作用)  
 *
 * CyclicBarrier
 * 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,
 * 让一组线程到达一个屏障(也可以叫同步点)时被阻塞,
 * 直到最后一个线程到达屏障时,屏障才会开门,所有
 * 被屏障拦截的线程才会继续干活。
 * 线程进入屏障通过CyclicBarrier的await()方法。
 * 
 * 集齐7颗龙珠就可以召唤神龙
 */
public class CyclicBarrierDemo
{
	private static final int NUMBER = 7;
	
	public static void main(String[] args)
	{
		//CyclicBarrier(int parties, Runnable barrierAction) 
		CyclicBarrier cb = new CyclicBarrier(NUMBER,()->{
			System.out.println("可以召唤神龙了!");
		});
		
		
		for (int i = 1; i <= NUMBER; i++) {
			new Thread(()->{
				System.out.println(Thread.currentThread().getName()+"号龙珠被收集!");
				//等待
				try {
					cb.await();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}, String.valueOf(i)).start();
		}
		
	}
}

4.4 Semaphore

  1. acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),要么一直等下去,直到有线程释放信号量,或超时。
  2. release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
  3. 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;


/**
 * 
 * @Description: TODO(这里用一句话描述这个类的作用)
 * 
 * 在信号量上我们定义两种操作:
 * acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
 * 					要么一直等下去,直到有线程释放信号量,或超时。
 * release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
 * 
 * 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
 * 
 * 情景:3个停车位,6部汽车争抢车位
 */
public class SemaphoreDemo
{
	public static void main(String[] args)
	{
		//创建三个停车位
		Semaphore sp = new Semaphore(3);
		
		//6辆车抢夺3个停车位
		for (int i = 1; i <= 6 ; i++) {
			new Thread(()->{
				try {
					//获取资源
					sp.acquire();
					System.out.println(Thread.currentThread().getName()+"号车驶入停车位!");
					//停车3秒
					TimeUnit.SECONDS.sleep(3);
					System.out.println(Thread.currentThread().getName()+"号车驶出停车位!");
					//释放资源
					sp.release();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}, String.valueOf(i)).start();
		}
	}
}

五、Life

愿你被世界温柔以待。
昨天越来越多,明天越来越少,这就叫人生,你之所以觉得时间一年比一年过得快,是因为时间对你一年比一年重要,别因为害怕孤单而凑合着相拥,也别因为一时的别无选择而将就的活着,总要有一段路,需要你独自走过,愿你是阳光,明媚不忧伤。

在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!