Java多线程和并发编程
1. 线程基础知识复习
1.1 线程
1把锁:synchronized
2个并:
并发(concurrent):是在同一实体上的多个事件,是在一台机器上“同时”处理多个任务,同一时刻,其实是只有一个事情在发生。
并行(parallel):是在不同实体上的多个事件,是在多台处理器上同时处理多个任务,同一时刻,大家都在做事情,你做你的,我做我的,各干各的。
3个程:
进程:在系统中运行的一个应用程序,每个进程都有它自己的内存空间和系统资源
线程:也被称为轻量级进程,在同一个进程内会有1个或多个线程,是大多数操作系统进行时序调度的基本单元。
管程:Monitor(锁),也就是我们平时所说的锁。Monitor其实是一种同步机制,它的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码,JVM中同步是基于进入和退出监视器(Monitor管程对象)来实现的,每个对象实例都会有一个Monitor对象,Monitor对象和Java对象一同创建并销毁,底层由C++语言实现。
线程分类:
用户线程:是系统的工作线程,它会完成这个程序需要完成的业务操作。
守护线程:是一种特殊的线程为其他线程服务的,在后台默默地完成一些系统性的任务,比如垃圾回收线程就是最典型的例子。守护线程作为一个服务线程,没有服务对象就没有必要继续运行了,如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以假如当系统只剩下守护线程的时候,守护线程伴随着JVM一同结束工作。
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始运行," + (Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
while (true) {
}
}, "t1");
t1.setDaemon(true);//通过设置属性Daemon来设置当前线程是否为守护线程
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 主线程结束");
}
}
输出:t1 开始运行,守护线程
main 主线程结束--->在main主线程结束后,守护线程会伴随着JVM一同结束工作,即使还有循环没有结束
1.2 线程池
1.2.1 什么是线程池?
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是我们前面学过的线程,这里的任务就是我们前面学过的实现了Runnable或Callable接口的实例对象。
1.2.2 为什么使用线程池
使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力;当然了,使用线程池的原因不仅仅只有这些,我们可以从线程池自身的优点上来进一步了解线程池的好处。
1.2.3 使用线程池有哪些优势
线程和任务分离,提升线程重用性;
控制线程并发数量,降低服务器压力,统一管理所有线程;
提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间
1.2.4 ThreadPoolExecutor部分源码
//构造方法:
public ThreadPoolExecutor(int corePoolSize, //核心线程数量
int maximumPoolSize,// 最大线程数
long keepAliveTime, // 最大空闲时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 饱和处理机制
)
{ ... }
我们可以通过下面的场景理解ThreadPoolExecutor中的各个参数; a客户(任务)去银行(线程池)办理业务,但银行刚开始营业,窗口服务员还未就位(相当于线程池中初始线程数量为0), 于是经理(线程池管理者)就安排1号工作人员(创建1号线程执行任务)接待a客户(创建线程); 在a客户业务还没办完时,b客户(任务)又来了,于是经理(线程池管理者)就安排2号工作人员(创建2号线程执行任务)接待b客户(又创建了一个新的线程);假设该银行总共就2个窗口(核心线程数量是2); 紧接着在a,b客户都没有结束的情况下c客户来了,于是经理(线程池管理者)就安排c客户先坐到银行大厅的座位上(空位相当于是任务队列)等候, 并告知他: 如果1、2号工作人员空出,c就可以前去办理业务; 此时d客户又到了银行,(工作人员都在忙,大厅座位也满了)于是经理赶紧安排临时工(新创建的线程)在大堂站着,手持pad设备给d客户办理业务; 假如前面的业务都没有结束的时候e客户又来了,此时正式工作人员都上了,临时工也上了,座位也满了(临时工加正式员工的总数量就是最大线程数), 于是经理只能按《超出银行最大接待能力处理办法》(饱和处理机制)拒接接待e客户; 最后,进来办业务的人少了,大厅的临时工空闲时间也超过了1个小时(最大空闲时间),经理就会让这部分空闲的员工人下班.(销毁线程) 但是为了保证银行银行正常工作(有一个allowCoreThreadTimeout变量控制是否允许销毁核心线程,默认false),即使正式工闲着,也不得提前下班,所以1、2号工作人员继续待着(池内保持核心线程数量);
1.2.5 Java内置线程池-ExecutorService介绍
//ExecutorService接口是java内置的线程池接口,通过学习接口中的方法,可以快速的掌握java内置线程池的基本使用
//常用方法:
void shutdown() //启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
List<Runnable> shutdownNow() //停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
<T> Future<T> submit(Callable<T> task) //执行带返回值的任务,返回一个Future对象。
Future<?> submit(Runnable task) //执行 Runnable 任务,并返回一个表示该任务的 Future。
<T> Future<T> submit(Runnable task, T result) //执行 Runnable 任务,并返回一个表示该任务的 Future。
1.2.6 Java内置线程池-ExecutorService获取
//获取ExecutorService可以利用JDK中的Executors 类中的静态方法,常用获取方式如下:
static ExecutorService newCachedThreadPool() //创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
//线程池中的所有线程都使用ThreadFactory来创建,这样的线程无需手动启动,自动执行;
static ExecutorService newFixedThreadPool(int nThreads) //创建一个可重用固定线程数的线程池
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
//创建一个可重用固定线程数的线程池且线程池中的所有线程都使用ThreadFactory来创建。
static ExecutorService newSingleThreadExecutor()
//创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
// 创建一个使用单个 worker 线程的 Executor,且线程池中的所有线程都使用ThreadFactory来创建。
2. CompletableFuture
2.1 Future接口理论知识复习
Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消异步任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
举例:比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙完其他事情或者先执行完,过了一会再才去获取子任务的执行结果或变更的任务状态(老师上课时间想喝水,他继续讲课不结束上课这个主线程,让学生去小卖部帮老师买水完成这个耗时和费力的任务)。
2.2 Future接口常用实现类FutureTask异步任务
2.2.1 Future接口能干什么
Future是Java5新加的一个接口,它提供一种异步并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们会就可以通过Future把这个任务放进异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。
2.2.2 Future接口相关架构
目的:异步多线程任务执行且返回有结果,三个特点:多线程、有返回、异步任务(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)
代码实现:Runnable接口+Callable接口+Future接口和FutureTask实现类。
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask(new MyThread());
Thread t1 = new Thread(futureTask); //开启一个异步线程
t1.start();
System.out.println(futureTask.get()); //有返回hello Callable
}
}
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("--------come in");
return "hello Callable";
}
}
2.2.3 Future编码实战和优缺点分析
优点:Future+线程池异步多线程任务配合,能显著提高程序的运行效率。
缺点:
get()阻塞---一旦调用get()方法求结果,一旦调用不见不散,非要等到结果才会离开,不管你是否计算完成,如果没有计算完成容易程序堵塞。
isDone()轮询---轮询的方式会耗费无谓的cpu资源,而且也不见得能及时得到计算结果,如果想要异步获取结果,通常会以轮询的方式去获取结果,尽量不要阻塞。
结论:Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果。
集合线程不安全问题
1. ArrayList
public class ThreadDemo {
public static void main(String[] args) {
// ArrayList线程不安全
// 异常 java.util.ConcurrentModificationException 并发修改异常
//List<String> list = new ArrayList<>();
// 解决方式1: 使用Vector
//List<String> list = new Vector<>();
// 解决方式2: 使用Collections工具类
//List<String> list = Collections.synchronizedList(new ArrayList<>());
// 解决方式3: 使用CopyOnWriteArrayList
// 写时复制,读写分离
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
ArrayList
public boolean add(E e) {//没有synchonized,线程不安全 modCount++; add(e, elementData, size); return true; }
Vector
public synchronized boolean add(E e) {//有synchonized,线程安全 modCount++; add(e, elementData, elementCount); return true; }
CopyOnWriteArrayList
public boolean add(E e) { synchronized (lock) { Object[] es = getArray(); int len = es.length; es = Arrays.copyOf(es, len + 1);//写时复制 es[len] = e; setArray(es); return true; } }
2. HashSet
public class ThreadDemo3 {
public static void main(String[] args) {
// HashSet线程不安全
// 异常 java.util.ConcurrentModificationException
//Set<String> set = new HashSet<>();
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
HashSet
public boolean add(E e) { //没有synchonized,线程不安全 return map.put(e, PRESENT)==null; }
CopyOnWriteArraySet
public boolean add(E e) { return al.addIfAbsent(e); } public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); return indexOfRange(e, snapshot, 0, snapshot.length) < 0 && addIfAbsent(e, snapshot); }
3. HashMap
public class ThreadDemo4 {
public static void main(String[] args) {
//HashMap 线程不安全
//异常 java.util.ConcurrentModificationException
//Map<String, String> map = new HashMap<>();
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
String key = String.valueOf(i);
new Thread(() -> {
map.put(key, UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}