Java 高并发编程课后总结
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 没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。
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 为变量在每个线程中都创建了一个副本,那么每个线程可以
访问自己内部的副本变量。
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 获取和设置线程的变量值。
所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
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 方法不会释放锁
评论