JAVA多线程相关知识学习总结

JAVA多线程相关知识学习总结


阅读目录

一、信号量。
二、线程池。
三、Lock接口及重入锁。
四、ThreadLocal。
五、原子操作类。
六、生产者-消费者模式。

一、信号量

信号量的概念:简单来说,信号量就是一种在多线程情况下,协调多个线程并分配公共资源的工具。它维护了一个许可集,在许可不可用时堵塞所有的资源请求。线程可以通过申请和释放方法获取或者释放访问许可。信号量也有方法可以增加一个许可。
  • 举个例子:以前去上海的时候,在陆家嘴有一家迪士尼旗舰店。去的时候人很多,有一个工作人员在门口组织顾客排队,保持店内人数一个量,里面的人出来多少,外面多少人再进去。这个工作人员就相当于“信号量”。

实现一个基础的信号量:

package MyThread;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class Semaphore {
	private List<Object> locks = Collections.synchronizedList(new ArrayList<Object>());
	private int permitNum = 1;
	private int nowPermitNum = 1;// 当前的许可数量
	private boolean permitNumGrow = false;
	private boolean fair = false;
	Random random = new Random();

	public Semaphore(int permitNum, boolean permitNumGrow, boolean fair) {
		this.fair = fair;
		this.permitNum = permitNum;
		this.nowPermitNum = permitNum;
		this.permitNumGrow = permitNumGrow;
	}

	public Semaphore(int permitNum) {
		this(permitNum, true, false);
	}

	public Semaphore() {
		this(1);
	}

	public void acquire() {
		Object lock = new Object();
		synchronized (lock) {
			if (nowPermitNum > 0) {
				nowPermitNum--;
			} else {
				locks.add(lock);
				try {
					lock.wait();// 没有许可就等待
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	public void release() {
		if (locks.size() > 0) {
			int index = 0;
			if (!fair) {
				index = Math.abs(random.nextInt()) % locks.size();
			}
			Object lock = locks.get(index);
			locks.remove(lock);
			synchronized (lock) {
				lock.notify();// 唤醒一个等待的线程
			}
		} else if (nowPermitNum < permitNum || permitNumGrow) {
			nowPermitNum++;
		}
	}

	public int getAvailablePermitNum() {
		return nowPermitNum;
	}

}

(JDK5过后提供了新号量的默认实现)
在这里插入图片描述


二、线程池

线程池的产生:线程的创建和销毁都需要毕竟大的系统资源,在JVM里过多的创建线程可能会导致系统内存消耗过度资源耗尽。为了减少线程的创建和销毁,尽可能的利用现有的对象进行服务,线程池就诞生了。
线程池的作用:线程池通过对多个任务重复使用线程,线程的创建消耗就分摊到了这多个任务上,这样的同时在请求到达的时候,线程就已经创建完毕,减少了创建线程带来的延迟。解决了线程生命周期开销资源消耗大的问题,响应也变得更快。
线程池的组件:
  • 线程池管理器:

    • 创建、销毁、管理线程,将工作中的线程放入线程池。
  • 工作线程

    • 可以循环执行任务的线程,在没有任务执行的时候进行等待。
  • 任务队列

    • 提供一种缓冲机制,将没有处理的任务放入任务队列。
  • 任务接口

    • 每个任务必须实现的接口,主要用来规定任务的入口、任务执行过完后的收尾工作、工作执行的状态等,工作线程通过该接口调度任务的执行。

简单实现:

package com.chinasofti.se.threads;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.TimeUnit;

public class ExecutorServiceTest {
   public static void main(String[] args) throws InterruptedException, ExecutionException {
   	ExecutorService service = Executors.newScheduledThreadPool(5);
   	Set<Callable<Integer>> tasks = new HashSet<>();
   	tasks.add(()->{
   		String name = Thread.currentThread().getName();
   		for (int i = 0; i <10; i++) {
   			System.out.println(name+(i+1)+"只绵羊...");
   		}
   		return 1;
   	});
   	tasks.add(()->{
   		String name = Thread.currentThread().getName();
   		for (int i = 0; i <10; i++) {
   			System.out.println(name+(i+1)+"颗星星...");
   		}
   		return 2;
   	});
   	tasks.add(()->{
   		String name = Thread.currentThread().getName();
   		for (int i = 0; i <10; i++) {
   			System.out.println(name+(i+1)+"颗沙子...");
   		}
   		return 3;
   	});
   	
   	List<Future<Integer>> result = service.invokeAll(tasks);
   	for (Future<Integer> future : result) {
   		System.out.println("线程任务执行结果"+future.get());
   	}
   	
   	service.shutdown();
   }

   private static void testPool() throws InterruptedException, ExecutionException {
   	ExecutorService service = Executors.newScheduledThreadPool(5);
   	for (int j = 0; j < 10; j++) {
   		Future<?> future = service.submit(() -> {
   			String name = Thread.currentThread().getName();
   			System.out.println(name + " 执行任务");
   			try {
   				TimeUnit.SECONDS.sleep(1);
   			} catch (InterruptedException e) {
   				e.printStackTrace();
   			}
   		});
   		System.out.println("得到结果" + future.get());
   	}
   	service.shutdown();
   }
}

(JDK5引入的Executor框架的最大优点是把任务的提交和执行解耦,要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。)


三、Lock接口及重入锁

Lock的由来:为了清晰的表达如何加锁和释放锁,JDK5中加入了Lock接口,提供了更清晰的语义。
Lock的默认实现:
  • ReentrantLock:
    • 可重入的独占锁。该对象与synchronized关键字有着相同的表现和更清晰的语义,而且还具有一些扩展的功能。可重入锁被最近的一个成功lock的线程占有(unlock后释放)。该类有一个重要特性体现在构造器上,构造器接受一个可选参数,是否是公平锁,默认是非公平锁。
      • 公平锁:先来一定先排队,一定先获取锁。
      • 非公平锁:不保证上述条件。非公平锁的吞吐量更高。

四、ThreadLocal

ThreadLocal的由来:为了在同一个线程中共享一个变量(既不同线程的变量取值不同)。创建一个共享的散列表,用线程对象作为键,需要线程中共享的变量作为值保存起来,每次使用散列表.get(Thread.currentThread())的方式获取共享变量的本线程版本。 JDK优化了上述过程打造出了性能更加优秀的线程内变量共享工具ThreadLocal。
ThreadLocal的作用:为了解决多线程程序中的并发问题。
  • ThreadLocal并不是一个Thread,而是Thread局部变量。当ThreadLocal维护变量时,它为每个使用该变量的线程提供独立的变量副本,每个线程都可以独立的随意改变自己的副本,而不会影响到其他线程的变量副本。
  • ThreadLocal的方法:
    • void set(T value)
      • 将此线程局部变量的当前线程副本中的值设置为指定值。
    • void remove()
      • 移除此线程局部变量当前线程的值。
    • protected T initialValue()
      • 返回此线程局部变量的当前线程的“初始值”。
    • T get()
      • 返回此线程局部变量的当前线程副本中的值

五、原子操作类

原子性:所谓原子性即指该操作无法分割,即便是在多线程环境下,进入该操作后只有完成操作的所有步骤才能被其他线程阻断,因此不会出现外部线程插入操作导致数据失效问题。
原子操作类:原子操作类相当于泛化的Volatile变量,能够支持原子读取-修改-写操作。

原子操作类在Java.util.concurrent.atomic包下,可以分为四种类型的原子更新类:

  • 原子更新基本类型
  • 原子更新数组类型
  • 原子更新引用
  • 原子更新属性

具体到每个类实现方法都不同,读者可以自行查Api。


六、生产者-消费者模式

在这里插入图片描述

此模型有如下几点:

  • 生产者仅仅在仓储未满时候生产,仓满则停止生产
  • 消费者仅仅在仓储有产品时候才能消费,仓空则等待
  • 当消费者发现仓储没产品可消费时候会通知生产者生产
  • 生产者在生产出可消费产品时候,应该通知等待的消费者去消费

(代码等笔者彻底掌握后会贴上来,读者请自行找相关文章了解。)

       感谢您的阅读,如果有误欢迎指出。后续会找时间完善该文章。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值