一次性搞清楚,Java并发编程在各主流框架中的应用,保证看懂

关键字 volatile 是 Java 提供的最轻量级的同步机制,Java 内存模型对 volatile 专门定义了一些特殊的访问规则。下面我们就看它的规则。当一个变量被 volatile 修饰后,它将具备以下两种特性。

  • 线程可见性:当一个线程修改了被 volatile 修饰的变量后,无论是否加锁,其他线程都可以立即看到最新的修改,而普通变量却做不到这点。

  • 禁止指令重排序优化:普通的变量仅仅保证在该方法的执行过程中所有依赖赋值结果的地方都能获取正确的结果,而不能保证变量赋值操作的顺序与程序代码的执行顺序一致。举个简单的例子说明下指令重排序优化问题,代码如下:

public class ThreadStopExample {

private static boolean stop;

public static void main(String[] args) throws InterruptedException {

Thread workThread = new Thread(new Runnable() {

public void run() {

int i= 0;

while (!stop) {

i++; try{

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace(); } } } }); workThread.start(); TimeUnit.SECONDS.sleep(3);

stop = true;

}}

我们预期程序会在 3s 后停止,但是实际上它会一直执行下去,原因就是虚拟机对代码进行了指令重排序和优化,优化后的指令如下:

if (!stop)

While(true)

workThread 线程在执行重排序后的代码时,是无法发现变量 stop 被其它线程修改的,因此无法停止运行。要解决这个问题,只要将 stop 前增加 volatile 修饰符即可。volatile 解决了如下两个问题。第一,主线程对 stop 的修改在 workThread 线程 中可见,也就是说 workThread 线程 立即看到了其他线程对于 stop 变量 的修改。第二,禁止指令重排序,防止因为重排序导致的并发访问逻辑混乱。

一些人认为使用 volatile 可以代替传统锁,提升并发性能,这个认识是错误的。volatile 仅仅解决了可见性的问题,但是它并不能保证互斥性,也就是说多个线程并发修改某个变量时,依旧会产生多线程问题。因此,不能靠 volatile 来完全替代传统的锁。根据经验总结,volatile 最适用的场景是 “ 一个线程写,其他线程读 ”,如果有多个线程并发写操作,仍然需要使用锁或者线程安全的容器或者原子变量来代替。下面我们继续对 Netty 的源码做分析。上面讲到了 ioRatio 被定义成 volatile,下面看看代码为什么要这样定义。

final long ioTime = System.nanoTime() - ioStartTime;

runAllTasks(ioTime * (100 - ioRatio) / ioRatio);

通过代码分析我们发现,在 NioEventLoop 线程 中,ioRatio 并没有被修改,它是只读操作。既然没有修改,为什么要定义成 volatile 呢?继续看代码,我们发现 NioEventLoop 提供了重新设置 IO 执行时间比例的公共方法。

public void setIoRatio(int ioRatio) {

if (ioRatio <= 0 || ioRatio > 100) {

throw new IllegalArgumentException(“ioRatio: " + ioRatio + " (expected: 0 < ioRatio <= 100)”);

} this.ioRatio = ioRatio;

}

首先,NioEventLoop 线程 没有调用该 set 方法,说明调整 IO 执行时间比例 是外部发起的操作,通常是由业务的线程调用该方法,重新设置该参数。这样就形成了一个线程写、一个线程读。根据前面针对 volatile 的应用总结,此时可以使用 volatile 来代替传统的 synchronized 关键字,以提升并发访问的性能。

ThreadLocal 的应用及源码解析

========================

ThreadLocal 又称为线程本地存储区(Thread Local Storage,简称为 TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的 TLS 区域。使用 ThreadLocal 变量 的 set(T value) 方法 可以将数据存入该线程本地存储区,使用 get() 方法 可以获取到之前存入的值。

ThreadLocal 的常见应用

不使用 ThreadLocal。

public class SessionBean {

public static class Session {

private String id;

public String getId() {

return id;

} public void setId(String id) {

this.id = id;

} } public Session createSession() {

return new Session();

} public void setId(Session session, String id) {

session.setId(id); } public String getId(Session session) {

return session.getId();

} public static void main(String[] args) {

//没有使用ThreadLocal,在方法间共享session需要进行session在方法间的传递

new Thread(() -> {

SessionBean bean = new SessionBean();

Session session = bean.createSession();

bean.setId(session, “susan”);

System.out.println(bean.getId(session));

}).start();

}

}

上述代码中,session 需要在方法间传递才可以修改和读取,保证线程中各方法操作的是一个。下面看一下使用 ThreadLocal 的代码。

public class SessionBean {

//定义一个静态ThreadLocal变量session,就能够保证各个线程有自己的一份,并且方法可以方便获取,不用传递

private static ThreadLocal session = new ThreadLocal<>();

public static class Session {

private String id;

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

}

public void createSession() {

session.set(new Session());

}

public void setId(String id) {

session.get().setId(id);

}

public String getId() {

return session.get().getId();

}

public static void main(String[] args) {

new Thread(() -> {

SessionBean bean = new SessionBean();

bean.createSession();

bean.setId(“susan”);

System.out.println(bean.getId());

}).start();

}

}

在方法的内部实现中,直接可以通过 session.get() 获取到当前线程的 session,省掉了参数在方法间传递的环节。

ThreadLocal 的实现原理

一般,类属性中的数据是多个线程共享的,但 ThreadLocal 类型的数据 声明为类属性,却可以为每一个使用它(通过 set(T value)方法)的线程存储线程私有的数据,通过其源码我们可以发现其中的原理。

public class ThreadLocal {

/**

  • 下面的 getMap()方法 传入当前线程,获得一个ThreadLocalMap对象,说明每一个线程维护了

  • 自己的一个 map,保证读取出来的value是自己线程的。

  • ThreadLocalMap 是ThreadLocal静态内部类,存储value的键值就是ThreadLocal本身。

  • 因此可以断定,每个线程维护一个ThreadLocalMap的键值对映射Map。不同线程的Map的 key值 是一样的,

  • 都是ThreadLocal,但 value 是不同的。

*/

public T get() {

Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings(“unchecked”)

T result = (T)e.value; return result;

} } return setInitialValue();

} public void set(T value) {

Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);

if (map != null)

map.set(this, value);

else

createMap(t, value); }}

ThreadLocal 在 Spring 中的使用

Spring 事务处理的设计与实现中大量使用了 ThreadLocal 类,比如,TransactionSynchronizationManager 维护了一系列的 ThreadLocal 变量,用于存储线程私有的 事务属性及资源。源码如下。

/**

  • 管理每个线程的资源和事务同步的中心帮助程序。供资源管理代码使用,但不供典型应用程序代码使用。

  • 资源管理代码应该检查线程绑定的资源,如,JDBC连接 或 Hibernate Sessions。

  • 此类代码通常不应该将资源绑定到线程,因为这是事务管理器的职责。另一个选项是,

  • 如果事务同步处于活动状态,则在首次使用时延迟绑定,以执行跨任意数量资源的事务。

*/

public abstract class TransactionSynchronizationManager {

/**

  • 一般是一个线程持有一个 独立的事务,以相互隔离地处理各自的事务。

  • 所以这里使用了很多 ThreadLocal对象,为每个线程绑定 对应的事务属性及资源,

  • 以便后续使用时能直接获取。

*/

private static final ThreadLocal<Map<Object, Object>> resources =

new NamedThreadLocal<Map<Object, Object>>(“Transactional resources”);

private static final ThreadLocal<Set> synchronizations =

new NamedThreadLocal<Set>(“Transaction synchronizations”);

private static final ThreadLocal currentTransactionName =

new NamedThreadLocal(“Current transaction name”);

private static final ThreadLocal currentTransactionReadOnly =

new NamedThreadLocal(“Current transaction read-only status”);

private static final ThreadLocal currentTransactionIsolationLevel =

new NamedThreadLocal(“Current transaction isolation level”);

private static final ThreadLocal actualTransactionActive =

new NamedThreadLocal(“Actual transaction active”);

/**

  • 为当前线程 绑定 对应的resource资源

*/

public static void bindResource(Object key, Object value) throws IllegalStateException {

Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);

Assert.notNull(value, “Value must not be null”);

Map<Object, Object> map = resources.get();

// 如果当前线程的 resources中,绑定的数据map为空,则为 resources 绑定 map

if (map == null) {

map = new HashMap<Object, Object>();

resources.set(map);

}

Object oldValue = map.put(actualKey, value);

if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {

oldValue = null;

}

if (oldValue != null) {

throw new IllegalStateException(“Already value [” + oldValue + “] for key [” +

actualKey + “] bound to thread [” + Thread.currentThread().getName() + “]”);

}

if (logger.isTraceEnabled()) {

logger.trace(“Bound value [” + value + “] for key [” + actualKey + “] to thread [” +

Thread.currentThread().getName() + “]”);

}

}

/**

  • 返回当前线程绑定的所有资源

*/

public static Map<Object, Object> getResourceMap() {

Map<Object, Object> map = resources.get();

return (map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap());

}

}

ThreadLocal 在 Mybatis 中的使用

Mybatis 的 SqlSession 对象 也是各线程私有的资源,所以对其的管理也使用到了 ThreadLocal 类。源码如下。

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

private final ThreadLocal localSqlSession = new ThreadLocal<>();

public void startManagedSession() {

this.localSqlSession.set(openSession());

} public void startManagedSession(boolean autoCommit) {

this.localSqlSession.set(openSession(autoCommit));

} public void startManagedSession(Connection connection) {

this.localSqlSession.set(openSession(connection));

} public void startManagedSession(TransactionIsolationLevel level) {

this.localSqlSession.set(openSession(level));

} public void startManagedSession(ExecutorType execType) {

this.localSqlSession.set(openSession(execType));

} public void startManagedSession(ExecutorType execType, boolean autoCommit) {

this.localSqlSession.set(openSession(execType, autoCommit));

} public void startManagedSession(ExecutorType execType, TransactionIsolationLevel level) {

this.localSqlSession.set(openSession(execType, level));

} public void startManagedSession(ExecutorType execType, Connection connection) {

this.localSqlSession.set(openSession(execType, connection));

} public boolean isManagedSessionStarted() {

return this.localSqlSession.get() != null;

} @Override

public Connection getConnection() {

final SqlSession sqlSession = localSqlSession.get();

if (sqlSession == null) {

throw new SqlSessionException(“Error: Cannot get connection. No managed session is started.”);

} return sqlSession.getConnection();

} @Override

public void clearCache() {

final SqlSession sqlSession = localSqlSession.get();

if (sqlSession == null) {

throw new SqlSessionException(“Error: Cannot clear the cache. No managed session is started.”);

} sqlSession.clearCache(); } @Override

public void commit() {

final SqlSession sqlSession = localSqlSession.get();

if (sqlSession == null) {

throw new SqlSessionException(“Error: Cannot commit. No managed session is started.”);

} sqlSession.commit(); } @Override

public void commit(boolean force) {

final SqlSession sqlSession = localSqlSession.get();

if (sqlSession == null) {

throw new SqlSessionException(“Error: Cannot commit. No managed session is started.”);

} sqlSession.commit(force); } @Override

public void rollback() {

final SqlSession sqlSession = localSqlSession.get();

if (sqlSession == null) {

throw new SqlSessionException(“Error: Cannot rollback. No managed session is started.”);

} sqlSession.rollback(); } @Override

public void rollback(boolean force) {

final SqlSession sqlSession = localSqlSession.get();

if (sqlSession == null) {

throw new SqlSessionException(“Error: Cannot rollback. No managed session is started.”);

} sqlSession.rollback(force); } @Override

public List flushStatements() {

final SqlSession sqlSession = localSqlSession.get();

if (sqlSession == null) {

throw new SqlSessionException(“Error: Cannot rollback. No managed session is started.”);

} return sqlSession.flushStatements();

} @Override

public void close() {

final SqlSession sqlSession = localSqlSession.get();

if (sqlSession == null) {

throw new SqlSessionException(“Error: Cannot close. No managed session is started.”);

} try {

sqlSession.close(); } finally {

localSqlSession.set(null);

} }}

J.U.C 包的实际应用

================

线程池 ThreadPoolExecutor

首先通过 ThreadPoolExecutor 的源码 看一下线程池的主要参数及方法。

public class ThreadPoolExecutor extends AbstractExecutorService {

/**

  • 核心线程数

  • 当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,

  • 也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize

*/

private volatile int corePoolSize;

/**

  • 最大线程数

  • 当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。

  • 另外,对于无界队列,可忽略该参数

*/

private volatile int maximumPoolSize;

/**

  • 线程存活保持时间

  • 当线程池中线程数 超出核心线程数,且线程的空闲时间也超过 keepAliveTime时,

  • 那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数

*/

private volatile long keepAliveTime;

/**

  • 任务队列

  • 用于传输和保存等待执行任务的阻塞队列

*/

private final BlockingQueue workQueue;

/**

  • 线程工厂

  • 用于创建新线程。threadFactory 创建的线程也是采用 new Thread() 方式,threadFactory

  • 创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池中线程的编号

*/

private volatile ThreadFactory threadFactory;

/**

  • 线程饱和策略

  • 当线程池和队列都满了,再加入的线程会执行此策略

*/

private volatile RejectedExecutionHandler handler;

/**

  • 构造方法提供了多种重载,但实际上都使用了最后一个重载 完成了实例化

*/

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue) {

this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

Executors.defaultThreadFactory(), defaultHandler); } public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

ThreadFactory threadFactory) {

this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

threadFactory, defaultHandler); } public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

RejectedExecutionHandler handler) {

this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

Executors.defaultThreadFactory(), handler); } public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue,

ThreadFactory threadFactory,

RejectedExecutionHandler handler) {

if (corePoolSize < 0 ||

maximumPoolSize <= 0 ||

maximumPoolSize < corePoolSize || keepAliveTime < 0)

throw new IllegalArgumentException();

if (workQueue == null || threadFactory == null || handler == null)

throw new NullPointerException();

this.corePoolSize = corePoolSize;

this.maximumPoolSize = maximumPoolSize;

this.workQueue = workQueue;

this.keepAliveTime = unit.toNanos(keepAliveTime);

this.threadFactory = threadFactory;

this.handler = handler;

} /**

  • 执行一个任务,但没有返回值

*/

public void execute(Runnable command) {

if (command == null)

throw new NullPointerException();

int c = ctl.get();

if (workerCountOf© < corePoolSize) {

if (addWorker(command, true))

return;

c = ctl.get();

} if (isRunning© && workQueue.offer(command)) {

int recheck = ctl.get();

if (! isRunning(recheck) && remove(command))

reject(command); else if (workerCountOf(recheck) == 0)

addWorker(null, false);

} else if (!addWorker(command, false))

reject(command); } /**

  • 提交一个线程任务,有返回值。该方法继承自其父类 AbstractExecutorService,有多种重载,这是最常用的一个。

  • 通过future.get()获取返回值(阻塞直到任务执行完)

*/

public Future submit(Callable task) {

if (task == null) throw new NullPointerException();

RunnableFuture ftask = newTaskFor(task); execute(ftask); return ftask;

} /**

  • 关闭线程池,不再接收新的任务,但会把已有的任务执行完

*/

public void shutdown() {

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

checkShutdownAccess(); advanceRunState(SHUTDOWN); interruptIdleWorkers(); onShutdown(); // hook for ScheduledThreadPoolExecutor

} finally {

mainLock.unlock();

}

tryTerminate();

}

/**

  • 立即关闭线程池,已有的任务也会被抛弃

*/

public List shutdownNow() {

List tasks;

final ReentrantLock mainLock = this.mainLock;

mainLock.lock();

try {

checkShutdownAccess();

advanceRunState(STOP);

interruptWorkers();

tasks = drainQueue();

} finally {

mainLock.unlock();

}

tryTerminate();

return tasks;

}

public boolean isShutdown() {

return ! isRunning(ctl.get());

}

}

线程池执行流程,如下图所示。

一次性搞清楚,Java并发编程在各主流框架中的应用,保证看懂

Executors 提供的 4 种线程池

Executors 类 通过 ThreadPoolExecutor 封装了 4 种常用的线程池:CachedThreadPool,FixedThreadPool,ScheduledThreadPool 和 SingleThreadExecutor。其功能如下。

  1. **CachedThreadPool:**用来创建一个几乎可以无限扩大的线程池(最大线程数为 Integer.MAX_VALUE),适用于执行大量短生命周期的异步任务。

  2. **FixedThreadPool:**创建一个固定大小的线程池,保证线程数可控,不会造成线程过多,导致系统负载更为严重。

  3. **SingleThreadExecutor:**创建一个单线程的线程池,可以保证任务按调用顺序执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值