多线程概引入
线程是程序执行的一条路径,一个进程可以包含多个线程,多线程的执行可以提高程序的效率,其中并发就是多个任务都请求运行,但是处理器只能同时接受一个任务,就把多个任务轮流执行,宏观上看起来像同时执行;并行就是多个任务同时执行(甲任务执行的同时乙任务同时在执行,需要多核CPU)
创建线程的两种方式
public class Demo2_Thread {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
}
}
//方式1:继承Thread类,重写run方法
class MyThread extends Thread {
Threadpublic void run() {
for(int i = 0; i < 3000; i++) {
System.out.println("a");
}
}
}
//方式2:实现Runnabel接口,重写run方法
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中
System.out.println("a");
}
}
}
线程的生命周期
- 新建状态(New):新创建了一个线程对象
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权,Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。
- 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(sleep是不会释放持有的锁)
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
当前正在执行的线程被阻塞之后,其他线程就可以获得执行的机会。被阻塞的线程会在合适的时候重新进入就绪状态,注意是就绪状态而不是运行状态。也就是说,被阻塞线程的阻塞解除后,必须重新等待线程调度器再次调度它。
Thread提供的内部状态来维护线程的状态,如下:
public enum State {
/**
* 新建状态,未start
*/
NEW,
/**
* 运行状态,线程运行中
*/
RUNNABLE,
/**
* 阻塞状态(线程被阻塞等待监视器锁)
*/
BLOCKED,
/**
* 等待状态,线程等待另外一个线程,比如如下方法线程将进入等待
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
* 如一个线程调用Object.wait方法,等待另一个线程调用Object.notify()或者Object.notifyAll(),或者一个线程调用join()方法,等待其他线程终止
*/
WAITING,
/**
* 等待状态(固定等待时间)
* 调用如下方法可能进入TIME_wAITING状态
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* 中止状态,当线程运行完成、被打断、被中止
*/
TERMINATED;
}
线程状态流转图
从图中可以看出,线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。而就绪和运行状态之间的转换通常不受程序控制,而是由系统线程调度所决定。当处于就绪状态的线程获得处理器资源时,该线程进入运行状态;当处于运行状态的线程失去处理器资源时,该线程进入就绪状态。但有一个方法例外,调用yield()方法可以让运行状态的线程转入就绪状态
注意:CPU竞争有很多种策略。Unix系统使用的是时间片算法,而Windows则属于抢占式的。
Thread类常见属性
public
class Thread implements Runnable { //实现Runnable
/*
* 创建后第一个被执行,为类注册本地函数,才可以调用JNI函数
* 让JVM找到你的本地函数,Thread中很多方法都是调用JNI
* 所有调用native方法的类第一步都是执行该方法
*/
private static native void registerNatives();
static {
registerNatives();
}
//线程名称,volatile可见
private volatile String name;
//线程优先级,从1-10 ,值越高代表优先级越大
private int priority;
//是否是守护线程,默认创建的都是false
private boolean daemon = false;
//线程组
private ThreadGroup group;
//设置当前线程栈大小
private long stackSize;
//线程ID
private long tid;
//生成线程ID
private static long threadSeqNumber;
//线程实际运行该接口的run方法,Thread其实也是一个Runnable
private Runnable target;
//线程状态,初始值为0
private volatile int threadStatus = 0;
// ThreadLocalMap,线程本地变量ThreadLocal的map值
ThreadLocal.ThreadLocalMap threadLocals = null;
//私有的内部使用生产线程ID的方法
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
//当前正在运行的线程
public static native Thread currentThread();
//返回当前线程ID
public long getId() {
return tid;
}
}
多线程中的方法解析
构造方法
/**
* 无参构造,默认执行Thread实现Runnable的run方法
*/
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
* 传入Runnable,执行传入Runnable的run方法
*/
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
/**
* 传入线程名创建Thread,默认执行Thread实现Runnable的run方法
*/
public Thread(String name) {
init(null, null, name, 0);
}
/**
* 线程初始化方法
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
设置线程名
/**
* 设置线程名,synchronized方法
*/
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;
}
设置优先级
只能设置1-10,优先级高的线程拿到CPU运行权的几率高
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;
}
判断线程的状态
native方法,为了测试某个线程是否已经死亡,当线程处于就绪、运行、阻塞了种状态时,该方法将返回true;当线程处于新建、死亡状态时,该方法将返回false。
public final native boolean isAlive();
线程run方法
线程被调用时执行的就是run方法,Thread实现Runnable,如果构造Thread时候传入Runnable target 执行的是Runnable的run方法,否则执行重写Thread类的run方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
线程睡眠
静态native方法,需要捕获异常,如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,让出CPU,则可以通过调用Thread类的静态sleep()方法来实现。在睡眠指定时候后自动唤醒。此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器(锁)的所属权。 即线程持有的锁并没有释放(线程执行同步代码块或同步方法时,调用sleep方法时候,不会释放锁,其他线程不能够进入同步方法或者代码块),sleep(0):则表示触发操作系统立刻重新进行一次CPU竞争。
public static native void sleep(long millis) throws InterruptedException;
线程让步
静态native方法,yield()方法是一个和sleep()方法有点相似,也是Threard类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。yield()只是让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。使用yield()的目的是让相同优先级的线程之间能适当的轮转执行,因此让步的作用不一定能达到。实际上,当某个线程调用了yield()方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。yield并不会释放对象锁。
public static native void yield();
线程等待和唤醒
通过调用Object类的wait方法使持有锁对象线程挂起等待,进入到一个和该锁对象相关的等待池中,同时会释放对象锁,直到其他线程调用此锁对象的notify()或者notifyAll()唤醒拥有该锁对象的线程(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放,等执行完同步块后锁释放)调用wait方法的一个或多个线程就会解除wait状态,重新参与竞争对象锁,程序如果可以再次得到锁,就可以继续向下运行。
obj.wait(),与Obj.notify()必须要与synchronized(obj)一起使用,也就是wait,与notify是针对已经获取了obj锁进行的操作,从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。如下代码:
class TestThread implements Runnable{
private String name;
private Object prev;
private Object self;
private TestThread(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
self.notify(); //A(获取c,a锁,唤醒a锁,) -B(获取a,b锁,唤醒b锁)
} // A(获取c,a锁,唤醒a锁,释放a锁) -B(获取a,b锁,唤醒b锁,释放b锁)
try {
prev.wait(); //A(获取c,a锁,唤醒a锁,释放a锁,释放c锁),B线程执行;- B(获取a,b锁,唤醒b锁,释放b锁,释放a锁)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
TestThread pa = new TestThread("A", c, a);
TestThread pb = new TestThread("B", a, b);
TestThread pc = new TestThread("C", b, c);
new Thread(pa).start();
Thread.sleep(100); //确保按顺序A、B、C执行
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
wait()和sleep()的区别
共同点:
- 在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
- wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。
不同点:
- Thread类的方法:sleep(),yield()等 ,Object的方法:wait()和notify(),notifyAll等
- sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
- wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
- sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
sleep()方法和yield()方法的区别
- sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法只会给优先级相同,或优先级更高的线程执行机会
- sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态:而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用yield()方法暂停之后,立即再次获得处理器资源被执行
- sleep()方法声明抛出了InterruptcdException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有声明抛出任何异常
- sleep()方法和yield()方法都不会释放锁,sleep()方法比yield()方法有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行
线程加入
Thread提供了让一个线程等待另一个线程完成的方法join()方法。当在某个程序执行中调用t.join()方法时,调用线程将被阻塞,直到t线程执行完毕为止,当前线程再由阻塞转为就绪。t.join()方法只会使主线程(或者说调用t.join()的线程)进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
可见join方法底层是调用wiat()方法,所以上面你的解释就看的明白了,join()方法只会使主线程(或者说调用join()的线程)进入阻塞,同时释放了同步锁。
线程中断
在Thread类中,关于线程中断的方法有以下三个
/** 线程中断方法,调用interrupt0(仅仅是设置中断标记) */
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
/** 判断线程是否中断,调用native的isInterrupted方法,并且clear中断标记 */
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
/** 判断线程是否中断,调用native的isInterrupted方法,没有clear中断标记 */
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
private native void interrupt0();
其中:interrupt()方法: 作用是中断线程,从代码可以看出
- 本线程中断自身是被允许的,将"中断标记"设置为true,并不会中断该线程运行,如果一个线程如果有被中断的需求,可以这样做。
- 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
- 在调用阻塞方法时正确处理InterruptedException异常(例如,catch异常后就结束线程。)
- 其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
- 如果线程处于被阻塞状态(如:Thread.sleep、Thread.join、Object.wait、LockSupport.park),那么线程将立即退出被阻塞状态,它的“中断状态”会被清除并抛出一个InterruptedException异常,例如A线程通过wait()进入阻塞状态,此时通过interrupt()中断该A线程;调用interrupt()会立即将A线程的中断标记设为true,但是由于A线程处于阻塞状态,所以该“中断标记”会立即被清除为false,同时,会产生一个InterruptedException的异常。
- 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。但是可以通过interrupted()或者isInterrupt()方法查看并做出处理
- 如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回
interrupted方法:作用于当前线程,判断的是当前线程是否处于中断状态。是类的静态方法,同时会清除线程的中断状态。
isInterrupted方法:作用于调用该方法的线程对象(不一定是当前运行的线程。如可以在A线程中去调用B线程对象的isInterrupted方法),判断的是当前线程是否处于中断状态。不会清除线程的中断状态。
上面两个方法底层都是调用native的isInterrupted(boolean ClearInterrupted),只是一个清除一个不清除中断状态。
public static void main(String[] args) throws Exception {
//打印结果: Interrupt:false
Thread.currentThread().interrupt(); //中断线程
// 因为在上面的一句代码可以中断线程,所以if判断线程是否中断就会得到的事true
// 但是 Thread.interrupted()这个方法执行完后就会清除线程的中断状态,
if(Thread.interrupted()) //判断线程是否被中断,默认是false,改方法执行完后会清楚线程的中断状态,如果是中断状态(true),执行完后为false
System.out.println("Interrupted:"+ Thread.interrupted());
else{
System.out.println("Not Interrupted"+Thread.interrupted());
}
守护线程
根据源码可以看出默认创建的线程都是非守护线程,通过以下方法可以将线程设置成守护线程;
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
守护线程的作用是为其他工作线程服务,只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器)。
需要注意的是:
- 守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
- 设置守护线程需要在线程对象创建之前用线程对象的setDaemon方法
- 守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。
线程退出:被系统调用在线程退出之前,清理线程相关的属性
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
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;
}
线程初始化
构造线程的底层调用了init方法,设置一些线程组,优先级,名字,target等等
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//1. 判断线程名不能为null,否则抛出异常
if (name == null) {
throw new NullPointerException("name cannot be null");
}
//2. 设置线程名
this.name = name;
//3. 设置当前运行的线程为父线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
//4.如果当前线程组为空
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
//4.1 将security的线程组赋值给当前线程的线程组
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
//如果4.1没有执行或者为空,将当前线程线程组设置线程的线程组
g = parent.getThreadGroup();
}
}
//5. 校验线程组是否可用
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
//6. 设置线程组
this.group = g;
// 7.子线程会继承父线程的守护属性
this.daemon = parent.isDaemon();
//8. 子线程继承父线程的优先级属性
this.priority = parent.getPriority();
// classLoader
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);
//9 当父线程的 inheritableThreadLocals 的属性值不为空时
// 会把 inheritableThreadLocals 里面的值全部传递给子线程
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
/* Set thread ID */
//10 线程 id 自增
tid = nextThreadID();
}
异常处理
线程出现未捕获异常后,JVM将调用Thread中的dispatchUncaughtException方法把异常传递给线程的未捕获异常处理器。
/**
* 为单个线程设置一个属于线程自己的uncaughtExceptionHandler,粒度是线程
*
*/
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
/**
* 设置一个静态的默认的UncaughtExceptionHandler。所有线程中的Exception在抛出并且未捕获的情况下,都会从此路过。
* 进程fork的时候设置的就是这个静态的defaultUncaughtExceptionHandler,粒度整个进程。
*/
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
从代码中可以看出如果没有设置UncaughtExceptionHandler ,则将使用线程所在的线程组来处理这个未捕获异常。线程组ThreadGroup实现了UncaughtExceptionHandler,所以可以用来处理未捕获异常。ThreadGroup类定义如下:
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
}
默认情况下,线程组处理未捕获异常的逻辑是,首先将异常消息通知给父线程组,然后尝试利用一个默认的defaultUncaughtExceptionHandler来处理异常,如果没有默认的异常处理器则将错误信息输出到System.err。也就是JVM提供给我们设置每个线程的具体的未捕获异常处理器,也提供了设置默认异常处理器的方法。即首先尝试通过UncaughtExceptionHandler处理异常,如果没有处理成功就通过defaultUncaughtExceptionHandler来处理异常。
捕获线程异常使用:
public static void main(String[] args) throws InterruptedException {
System.out.println("我的名字1是" + Thread.currentThread().getName());
Thread tr = new Thread(new Runnable() {
@Override
public void run() {
Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("捕获异常");
}
});
System.out.println("我的名字3是" + Thread.currentThread().getName());
if(true)
throw new RuntimeException("我是异常");
System.out.println("我的名字4是" + Thread.currentThread().getId());
}
},"tr线程");
tr.start();
System.out.println("我的名字2是" + Thread.currentThread().getName());
}
运行结果:
我的名字1是main
我的名字2是main
我的名字3是tr线程
捕获异常
checkAccess()方法
用于检查当前正在运行的线程是否具有更新线程组的权限。
public final void checkAccess() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkAccess(this);
}
}
Runnable接口
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
synchronized锁对象
private final Object blockerLock = new Object();