一个线程是执行程序中的一个线程。Java虚拟机允许应用程序具有多个并发运行的执行线程。
每个线程都有一个优先级。具有较高优先级的线程优先于具有较低优先级的线程执行。每个线程可能会也可能不会被标记为守护程序。当在某个线程中运行的代码创建新Thread
对象时,新线程的优先级最初设置为与创建线程的优先级相等,并且当且仅当创建线程是守护程序时,该线程才是守护程序线程。
当Java虚拟机启动时,通常只有一个非守护程序线程(通常调用main
某个指定类的命名方法 )。Java虚拟机将继续执行线程,直到发生以下任何一种情况:
exit
类的方法Runtime
已被调用,安全管理器SecurityManager
已允许进行退出操作。run
方法,要么抛出一个传播到run
方法之外的异常。有两种方法可以创建新的执行线程。
run()
方法上述引用自API文档:https://docs.oracle.com/javase/1.4.2/docs/api/
可知Thread类也是实现了Runnable接口
//常量:最低优先级
public static final int MIN_PRIORITY = 1;
//常量:默认优先级
public static final int NORM_PRIORITY = 5;
//常量:最高优先级
public static final int MAX_PRIORITY = 10;
这些是提供给Thread类的元操作
/* Some private helper methods */
private native void setPriority0(int newPriority);
private native void stop0(Object o);
private native void suspend0();
private native void resume0();
private native void interrupt0();
private static native void clearInterruptEvent();
private native void setNativeName(String name);
private native void start0();
//向调度程序提示当前线程愿意放弃当前使用的处理器。调度程序可以随意忽略此提示
public static native void yield();
//测试此线程是否仍然存在。如果线程已经启动但尚未死亡,则该线程是活动的。
public final native boolean isAlive();
//获取当前线程
@HotSpotIntrinsicCandidate
public static native Thread currentThread();
//注册成本地方法
private static native void registerNatives();
//指定的毫秒数
public static native void sleep(long millis) throws InterruptedException;
@HotSpotIntrinsicCandidate
注解是Java 9引入的新特性,在HotSpot中都有一套高效的实现,该高效实现基于CPU指令,运行时,HotSpot维护的高效实现会替代JDK的源码实现,从而获得更高的效率。补充引用自:https://blog.csdn.net/javaer_lee/article/details/87161952
/* 保留供JVM专用的字段 */
private boolean stillborn = false;
private long eetop;
/* 线程的类加载器 */
private ClassLoader contextClassLoader;
/* 此线程的继承的 */
private AccessControlContext inheritedAccessControlContext;
/* 用于自动编号匿名线程. */
private static int threadInitNumber;
/* 与此线程有关的线程本地值表,该映射由ThreadLocal类提供 */
ThreadLocal.ThreadLocalMap threadLocals = null;
/* 继承的线程本地值表 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
/* 此线程请求的堆栈大小 */
private final long stackSize;
/**
* 该参数供 java.util.concurrent.locks.LockSupport.park.调用
* 由 (private) java.util.concurrent.locks.LockSupport.setBlocker设置
* 通过java.util.concurrent.locks.LockSupport.getBlocker访问
*/
volatile Object parkBlocker;
/*以下三个最初未初始化的字段仅由类java.util.concurrent.ThreadLocalRandom管理。
这些字段用于在并发代码中构建高性能PRNG,因此我们不会冒意外共享的风险,
因此,这些字段使用@Contended隔离。*/
/** The current seed for a ThreadLocalRandom */
@jdk.internal.vm.annotation.Contended("tlr")
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@jdk.internal.vm.annotation.Contended("tlr")
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@jdk.internal.vm.annotation.Contended("tlr")
int threadLocalRandomSecondarySeed;
/* 本线程的ID */
private final long tid;
public long getId() {
return tid;
}
/* 用于在构造函数里生成新线程ID */
private static long threadSeqNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
public final synchronized void setName(String name) {
checkAccess();
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
if (threadStatus != 0) {
setNativeName(name);//本地方法:用于设置线程名称
}
}
public final String getName() {
return name;
}
需要注意的是:JAVA定义的6种状态是JVM的线程状态,而不是操作系统
/* 线程状态枚举 */
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
private volatile int threadStatus;
//返回此线程的状态。此方法设计用于监视系统状态,而不用于同步控制。
public State getState() {
// get current thread state
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
Java中存在两类线程:User Thread(用户线程)、Daemon Thread(守护线程) ,其中守护线程是运行在后台为用户线程提供服务的线程,例如GC回收器
。
需要注意的是,
JVM
便会退出IllegalThreadStateException
异常。更多与守护进程相关的知识点可以看下这篇文章,写的很清晰了
Java中守护线程的总结
//属性
private boolean daemon = false;
/**
* 设置守护进程方法
* @throws IllegalThreadStateException 若设置守护线程时线程活跃抛出该异常
*/
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
/**
* 确定当前正在运行的线程是否具有修改此线程的权限
* @throws SecurityException 不具修改权限将抛出该异常.
*/
public final void checkAccess() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkAccess(this);
}
}
需要注意的是:当线程中的run()方法代码里面又创建了一个新的线程对象时,新创建的线程优先级和父线程优先级一样
/*优先级属性*/
private int priority;
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
public final int getPriority() {
return priority;
}
private ThreadGroup group;
public final ThreadGroup getThreadGroup() {
return group;
}
public static int enumerate(Thread tarray[]) {
return currentThread().getThreadGroup().enumerate(tarray);
}
registerNatives()
用于注册本地方法,主要作用就是将C/C++中的方法映射到Java中的native方法。
/* Make sure registerNatives is the first thing does. */
private static native void registerNatives();
static {
registerNatives();
}
public Thread() {
this(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
this(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
this(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
this(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
this(group, null, name, 0);
}
public Thread(Runnable target, String name) {
this(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
this(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
this(group, target, name, stackSize, null, true);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize, boolean inheritThreadLocals) {
this(group, target, name, stackSize, null, inheritThreadLocals);
}
/**
* 初始化线程
* @param g 所属线程组
* @param target 实现Runnable接口用于执行的对象
* @param name 新线程名称
* @param stackSize 新线程堆栈大小, 为0时表示该参数被忽略
* @param acc 用于继承的访问控制上下文,可为null
* @param inheritThreadLocals 如果值为true,从构造线程继承可继承线程局部变量的初始值
*/
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
/* 1.设置线程名 */
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
/* 2.为设置线程组的准备过程 */
//获取父级线程
Thread parent = currentThread();
//获取安全管理局
SecurityManager security = System.getSecurityManager();
if (g == null) {
//若存在安全管理器,让它分配线程组
if (security != null) {
g = security.getThreadGroup();
}
//若安全管理器并没有分配线程组,则使用父线程的线程组
if (g == null) {
g = parent.getThreadGroup();
}
}
//确定当前正在运行的线程是否具有修改此线程组的权限
g.checkAccess();
//再经由安全管理器进行判断
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(
SecurityConstants.SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
//增加线程组中未启动线程的数量。(需要注意的是,未启动的线程不会添加到线程组中)
g.addUnstarted();
/*3.正式创建实例过程*/
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
this.tid = nextThreadID();
}
这个过程实际上是两个线程在同时运行:当前线程(从调用到返回 start方法)和另一个线程(执行其 run方法)
private Runnable target;
//让线程开始执行,同时虚拟机会调用该线程的run方法
public synchronized void start() {
//0对应于状态“ NEW”,
if (threadStatus != 0)
throw new IllegalThreadStateException();
//通知组该线程即将开始,以便可以将其添加到组的线程列表中,并且可以减少该组的未启动计数
group.add(this);
boolean started = false;
try {
start0();//本地方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
//如果start0抛出了Throwable,则它将被向上传递到调用堆栈
}
}
}
//若run方法没有被覆写,那么它会执行实现了Runnable接口的对象(target)里的run方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
使当前正在执行的线程进入休眠状态(暂时停止执行)达指定的时间。
该线程不会失去任何监视器的所有权。
//本地方法:指定的毫秒数
public static native void sleep(long millis) throws InterruptedException;
//指定的毫秒数 加上 指定的纳秒数。
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0 && millis < Long.MAX_VALUE) {
millis++;
}
sleep(millis);
}
告诉调用者暂时无法执行程序,直到某些行为发生。
/*源码*/
@HotSpotIntrinsicCandidate
public static void onSpinWait() {}
API示例:
考虑一个类中的一个方法,该方法会循环轮询,直到在该方法外设置了一些标志为止。
需要注意的是,对该onSpinWait
方法的调用应放在旋转循环内。
class EventHandler {
volatile boolean eventNotificationNotReceived;
void waitForEventAndHandleIt() {
while ( eventNotificationNotReceived ) {
java.lang.Thread.onSpinWait();
}
readAndProcessEvent();
}
void readAndProcessEvent() {
// Read event from some source and process it
. . .
}
}
/* 线程的中断状态-由JVM直接读取/写入 */
private volatile boolean interrupted;
//判断当前是不是中断状态,不会影响中断状态
public boolean isInterrupted() {
return interrupted;
}
//测试当前线程是否已被中断。通过此方法可以清除线程的 中断状态。
//换句话说,如果要连续两次调用此方法,则第二个调用将返回false(除非在第一个调用清除了其中断状态之后且在第二个调用对其进行检查之前,当前线程再次被中断)。
· public static boolean interrupted() {
Thread t = currentThread();
boolean interrupted = t.interrupted;
if (interrupted) {
t.interrupted = false;
clearInterruptEvent();//本地方法:清除中断
}
return interrupted;
}
//TODO:这部分我还是不怎么理解它的机制
private volatile Interruptible blocker;
private final Object blockerLock = new Object();
//中断此线程
public void interrupt() {
//如果是其他的进程调用中断,通过checkAccess()检测权限
if (this != Thread.currentThread()) {
checkAccess();
// 线程可能在I/O操作中被阻止
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupted = true;
interrupt0(); // 本地方法:通知虚拟机中断
b.interrupt(this);
return;
}
}
}
interrupted = true;
interrupt0();
}
public final void join() throws InterruptedException {
join(0);//0意味着永远等待
}
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
//获取纳秒为单位的时间
final long startTime = System.nanoTime();
long delay = millis;
do {
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS
.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
throw new IllegalArgumentException("timeout value is negative");
}
}
//等待最多millis毫秒 加 nanos纳秒的时间
public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
//存在正纳秒值会向上取整
if (nanos > 0 && millis < Long.MAX_VALUE) {
millis++;
}
join(millis);
}
这是一个给系统调用的方法,使Thread 在实际退出之前有机会进行清理
private void exit() {
if (threadLocals != null && TerminatingThreadLocal.REGISTRY.isPresent()) {
TerminatingThreadLocal.threadTerminated();
}
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
//返回当前线程的线程组及其子组中活动线程的数量的估计值。该值仅为估值,用于调试
public static int activeCount() {
return currentThread().getThreadGroup().activeCount();
}
static void processQueue(ReferenceQueue<Class<?>> queue,
ConcurrentMap<? extends
WeakReference<Class<?>>, ?> map)
{
Reference<? extends Class<?>> ref;
while((ref = queue.poll()) != null) {
map.remove(ref);
}
}
@CallerSensitive
public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,
Reflection.getCallerClass());
}
return contextClassLoader;
}
public void setContextClassLoader(ClassLoader cl) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
contextClassLoader = cl;
}
补充:关于类加载器一时半会解释不清楚,以后写个专门的文章来分析
@CallerSensitive
注解可以看下这篇文章@CallerSensitive 注解的作用
提供的接口
@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
提供的方法
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
//用于对特定的线程设置异常处理器。
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
//用于获取特定的线程的异常处理器。
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
//若当前的线程没有异常处理器,会使用所属线程组的异常处理器
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
//静态方法:用于设置一个默认的全局异常处理器
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(
new RuntimePermission("setDefaultUncaughtExceptionHandler")
);
}
defaultUncaughtExceptionHandler = eh;
}
//静态方法:用于获取一个默认的全局异常处理器
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}
详见我的另一篇博客:
Java 线程异常处理—UncaughtExceptionHandler
private static native StackTraceElement[][] dumpThreads(Thread[] threads);
private static native Thread[] getThreads();
public static void dumpStack() {
new Exception("Stack trace").printStackTrace();
}
public StackTraceElement[] getStackTrace() {
if (this != Thread.currentThread()) {
// check for getStackTrace permission
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(
SecurityConstants.GET_STACK_TRACE_PERMISSION);
}
// optimization so we do not call into the vm for threads that
// have not yet started or have terminated
if (!isAlive()) {
return EMPTY_STACK_TRACE;
}
StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this});
StackTraceElement[] stackTrace = stackTraceArray[0];
// a thread that was alive during the previous isAlive call may have
// since terminated, therefore not having a stacktrace.
if (stackTrace == null) {
stackTrace = EMPTY_STACK_TRACE;
}
return stackTrace;
} else {
return (new Exception()).getStackTrace();
}
}
public static Map<Thread, StackTraceElement[]> getAllStackTraces() {
// check for getStackTrace permission
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(
SecurityConstants.GET_STACK_TRACE_PERMISSION);
security.checkPermission(
SecurityConstants.MODIFY_THREADGROUP_PERMISSION);
}
// Get a snapshot of the list of all threads
Thread[] threads = getThreads();
StackTraceElement[][] traces = dumpThreads(threads);
Map<Thread, StackTraceElement[]> m = new HashMap<>(threads.length);
for (int i = 0; i < threads.length; i++) {
StackTraceElement[] stackTrace = traces[i];
if (stackTrace != null) {
m.put(threads[i], stackTrace);
}
// else terminated so we don't put it in the map
}
return m;
}
/** cache of subclass security audit results */
/* Replace with ConcurrentReferenceHashMap when/if it appears in a future
* release */
private static class Caches {
/** cache of subclass security audit results */
static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits =
new ConcurrentHashMap<>();
/** queue for WeakReferences to audited subclasses */
static final ReferenceQueue<Class<?>> subclassAuditsQueue =
new ReferenceQueue<>();
}
/**
* Verifies that this (possibly subclass) instance can be constructed
* without violating security constraints: the subclass must not override
* security-sensitive non-final methods, or else the
* "enableContextClassLoaderOverride" RuntimePermission is checked.
*/
private static boolean isCCLOverridden(Class<?> cl) {
if (cl == Thread.class)
return false;
processQueue(Caches.subclassAuditsQueue, Caches.subclassAudits);
WeakClassKey key = new WeakClassKey(cl, Caches.subclassAuditsQueue);
Boolean result = Caches.subclassAudits.get(key);
if (result == null) {
result = Boolean.valueOf(auditSubclass(cl));
Caches.subclassAudits.putIfAbsent(key, result);
}
return result.booleanValue();
}
private static boolean auditSubclass(final Class<?> subcl) {
Boolean result = AccessController.doPrivileged(
new PrivilegedAction<>() {
public Boolean run() {
for (Class<?> cl = subcl;
cl != Thread.class;
cl = cl.getSuperclass())
{
try {
cl.getDeclaredMethod("getContextClassLoader", new Class<?>[0]);
return Boolean.TRUE;
} catch (NoSuchMethodException ex) {
}
try {
Class<?>[] params = {ClassLoader.class};
cl.getDeclaredMethod("setContextClassLoader", params);
return Boolean.TRUE;
} catch (NoSuchMethodException ex) {
}
}
return Boolean.FALSE;
}
}
);
return result.booleanValue();
}
/**
* Weak key for Class objects.
**/
static class WeakClassKey extends WeakReference<Class<?>> {
/**
* saved value of the referent's identity hash code, to maintain
* a consistent hash code after the referent has been cleared
*/
private final int hash;
/**
* Create a new WeakClassKey to the given object, registered
* with a queue.
*/
WeakClassKey(Class<?> cl, ReferenceQueue<Class<?>> refQueue) {
super(cl, refQueue);
hash = System.identityHashCode(cl);
}
/**
* Returns the identity hash code of the original referent.
*/
@Override
public int hashCode() {
return hash;
}
}
/*
作用:获得计算堆栈帧的数量
过时原因:结果从未得到明确定义,并且取决于线程的挂起。
此方法可能会在Java SE的将来版本中删除。
*/
@Deprecated(since="1.2", forRemoval=true)
public int countStackFrames() {
throw new UnsupportedOperationException();
}
/*
作用:强制线程停止执行。
1.如果安装了安全管理器,则将其作为参数调用其checkAccess方法。
这可能会导致引SecurityException(在当前线程中)。
2.如果此线程与当前线程不同(也就是说,当前线程正在尝试停止除自身之外的其他线程),
则另外调用安全管理器的checkPermission方法(带有RuntimePermission(“ stopThread”)参数)。 同样,这可能导致抛出SecurityException(在当前线程中)。
该线程代表的线程被迫停止正在执行的异常操作,并抛出新创建的ThreadDeath对象作为异常。
允许停止尚未启动的线程。如果线程最终启动,则它立即终止。
除非必须执行一些特殊的清理操作,否则应用程序通常不应尝试捕获ThreadDeath
(请注意,抛出ThreadDeath会导致try语句的finally子句在线程正式死亡之前被执行)。
如果catch子句捕获了ThreadDeath对象,则重要的是重新抛出该对象,以便线程实际上死亡。
如果未捕获的异常是ThreadDeath的实例,则对未捕获的异常做出反应的顶级错误处理程序不会打印出消息,也 不会以其他方式通知应用程序
过时原因:此方法仅适用于与suspend一起使用,因为不赞成使用死锁,因此已弃用。
*/
@Deprecated(since="1.2")
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
checkAccess();
if (this != Thread.currentThread()) {
security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
// 状态值零对应于“ NEW”,它不能更改为NOT-NEW,因为我们持有锁
if (threadStatus != 0) {
resume(); //如果线程被挂起,则将其唤醒;
}
// VM可以处理所有线程状态
stop0(new ThreadDeath());
}
/**
作用:恢复挂起的线程。
首先,不带参数调用此线程的checkAccess方法。 这可能导致抛出SecurityException(在当前线程中)。
如果线程处于活动状态但已挂起,则将继续执行该线程并允许其执行。
过时原因:此方法仅适用于与suspend一起使用,因为不赞成使用死锁,因此已弃用。
*/
@Deprecated(since="1.2", forRemoval=true)
public final void resume() {
checkAccess();
resume0();
}
/**
作用:挂起该线程。
首先,不带参数调用此线程的checkAccess方法。 这可能导致抛出SecurityException(在当前线程中)。
如果线程处于活动状态,则将其挂起,并且除非继续进行操作,否则它将不会进一步进行。
弃用原因:此方法已被弃用,因为它容易死锁。
如果目标线程在挂起时在监视器上持有锁以保护关键系统资源,则在恢复目标线程之前,没有线程可以访问该资 源。 如果将恢复目标线程的线程在调用resume之前尝试锁定此监视器,则会导致死锁。
这种僵局通常表现为“冻结”进程。
*/
@Deprecated(since="1.2", forRemoval=true)
public final void suspend() {
checkAccess();
suspend0();
}