写点什么

Java 高并发编程课后总结

作者:Java高工P7
  • 2021 年 11 月 11 日
  • 本文字数:4521 字

    阅读完需:约 15 分钟

private static final AtomicInteger poolNumber = new AtomicInteger(1);


private final ThreadGroup group;


private final AtomicInteger threadNumber = new AtomicInteger(1);


private final String namePrefix;


DefaultThreadFactory() {


SecurityManager var1 = System.getSecurityManager();


this.group = var1 != null?var1.getThreadGroup():Thread.currentThread().getThreadGroup();


this.namePrefix = “pool-” + poolNumber.getAndIncrement() + “-thread-”;


}


public Thread newThread(Runnable var1) {


Thread var2 = new Thread(this.group, var1, this.namePrefix + this.threadNumber.getAndIncrement(), 0L);


if(var2.isDaemon()) {


var2.setDaemon(false);


}


if(var2.getPriority() != 5) {


var2.setPriority(5);


}


return var2;


}


}


RejectedExecutionHandler


RejectedExecutionHandler 也是一个接口,只有一个方法


public interface RejectedExecutionHandler {


void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);


}


当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用 RejectedExecutionHandler 的 rejectedExecution 方法。


4、说一下线程池内部使用规则


线程池的线程执行规则跟任务队列有很大的关系。


下面都假设任务队列没有大小限制:


如果线程数量<=核心线程数量,那么直接启动一个核心线程来执行任务,不会放入队列中。


如果线程数量>核心线程数,但<=最大线程数,并且任务队列是 LinkedBlockingDeque 的时候,超过核心线程数量的任务会放在任务队列中排队。


如果线程数量>核心线程数,但<=最大线程数,并且任务队列是 SynchronousQueue 的时候,线程池会创建新线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。


如果线程数量>核心线程数,并且>最大线程数,当任务队列是 LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是 LinkedBlockingDeque 并且没有大小限制时,线程池的最大线程数设置是无效的,他的线程数最多不会超过核心线程数。


如果线程数量>核心线程数,并且>最大线程数,当任务队列是 SynchronousQueue 的时候,会因为线程池拒绝添加任务而抛出异常。


任务队列大小有限时


当 LinkedBlockingDeque 塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。


SynchronousQueue 没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。


  1. atomicinteger 常用方法


public final int getAndSet(int newValue) //给 AtomicInteger 设置 newValue 并返回加 oldValue


public final boolean compareAndSet(int expect, int update) //如果输入的值和期望值相等就 set 并返回 true/false


public final int getAndIncrement() //对 AtomicInteger 原子的加 1 并返回当前自增前的 value


public final int getAndDecrement() //对 AtomicInteger 原子的减 1 并返回自减之前的的 value


public final int getAndAdd(int delta) //对 AtomicInteger 原子的加上 delta 值并返加之前的 value


public final int incrementAndGet() //对 AtomicInteger 原子的加 1 并返回加 1 后的值


public final int decrementAndGet() //对 AtomicInteger 原子的减 1 并返回减 1 后的值


public final int addAndGet(int delta) //给 AtomicInteger 原子的加上指定的 delta 值并返回加后的值


6、用过 threadlocal 吗?怎么用的?


早在 JDK 1.2 的版本中就提供 java.lang.ThreadLocal,ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。


ThreadLocal 很容易让人望文生义,想当然地认为是一个“本地线程”。


其实,ThreadLocal 并不是一个 Thread,而是 Thread 的局部变量,也许把它命名为 ThreadLocalVariable 更容易让人理解一些。


ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


访问自己内部的副本变量。


ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。


class ConnectionManager {


private static Connection connect = null;


public static Connection openConnection() {


if(connect == null){


connect = DriverManager.getConnection();


}


return connect;


}


public static void closeConnection() {


if(connect!=null)


connect.close();


}


}


假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:第一,这里面的 2 个方法都没有进行同步,很可能在 openConnection 方法中会多次创建 connect;第二,由于 connect 是共享变量,那么必然在调用 connect 的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用 connect 进行数据库操作,而另外一个线程调用 closeConnection 关闭链接。


所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用 connect 的地方需要进行同步处理。


这样将会大大影响程序执行效率,因为一个线程在使用 connect 进行数据库操作的时候,其他线程只有等待。


那么大家来仔细分析一下这个问题,这地方到底需不需要将 connect 变量进行共享?事实上,是不需要的。假如每个线程中都有一个 connect 变量,各个线程之间对 connect 变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个 connect 进行了修改的。


到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:


class ConnectionManager {


private Connection connect = null;


public Connection openConnection() {


if(connect == null){


connect = DriverManager.getConnection();


}


return connect;


}


public void closeConnection() {


if(connect!=null)


connect.close();


}


}


class Dao{


public void insert() {


ConnectionManager connectionManager = new ConnectionManager();


Connection connection = connectionManager.openConnection();


//使用 connection 进行操作


connectionManager.closeConnection();


}


}


这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。


那么这种情况下使用 ThreadLocal 是再适合不过的了,因为 ThreadLocal 在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。


但是要注意,虽然 ThreadLocal 能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用 ThreadLocal 要大。


从上面的结构图,我们已经窥见 ThreadLocal 的核心机制:


每个 Thread 线程内部都有一个 Map。


Map 里面存储线程本地对象(key)和线程的变量副本(value)


但是,Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值。


所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。


  1. ThreadLocal 类提供如下几个核心方法:


方法名 作用


public T get() get()方法用于获取当前线程的副本变量值


public void set(T value) set()方法用于保存当前线程的副本变量值


public void remove() remove()方法移除当前前程的副本变量值


8、程序、进程、线程的区别是什么? 举个现实的例子说明


程序(Program):是一个指令的集合。程序不能独立执行,只有被加载到内存中,系统为它分配资源后才能执行.


进程(Process):如上所述,一个执行中的程序称为进程。 进程是系统分配资源的独立单位,每个进程占有特定的地址空间。程序是进程的静态文本描述,进程是程序在系统内顺序执行的动态活动。


线程(Thread):是进程的“单一的连续控制流程“。 线程是 CPU 调度和分配的基本单位,是比进程更小的能独立运行的基本单位,也被称为轻量级的进程。线程不能独立存在,必须依附于某个进程。一个进程可以包括多个并行的线程,一个线程肯定属于一个进程。Java 虚拟机允许应用程序并发地执行多个线程。 举例:如一个车间是一个程序,一个正在进行生产任务的车间是一个进程,车间内每个从事不同工作的工人是一个线程。


下面的代码,实际上有几个线程在运行:


public static void main(String[] argc) throws Exception {


Runnable r = new Thread6();


Thread t = new Thread(r, “Name test”);


t.start();


}


两个:线程 t 和 main()方法(主线程)。


9、线程的状态


线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。


阻塞的情况又分为三种:


等待阻塞:运行的线程执行 wait()方法,该线程会释放占用的所有资源,JVM 会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用 notify()或 notifyAll()方法才能被唤醒,wait 是 object 类的方法


同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入“锁池”中。


其他阻塞:运行的线程执行 sleep()或 join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。sleep 是 Thread 类的方法。


新建状态(New):新创建了一个线程对象。


就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取 CPU 的使用权。


运行状态(Running):就绪状态的线程获取了 CPU,执行程序代码。


阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃 CPU 使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。


死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。


10、说说:sleep、yield、join、wait 方法的区别


sleep()方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是 sleep()方法不会释放“锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据。 作用于线程


Thread.sleep()方法用来暂停线程的执行,将 CPU 放给线程调度器。


Thread.sleep()方法是一个静态方法,它暂停的是当前执行的线程。


Java 有两种 sleep 方法,一个只有一个毫秒参数,另一个有毫秒和纳秒两个参数。


与 wait 方法不同,sleep 方法不会释放锁

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
Java高并发编程课后总结