参考博客
java程序启动至少会启动几个线程?
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for(ThreadInfo threadInfo:threadInfos) {
System.out.println(threadInfo.getThreadId()+"-"+threadInfo.getThreadName());
}
}
}
至少是5个:这是在JDK1.8中运行的结果
5-Attach Listener
4-Signal Dispatcher //分发处理发给JVM信号的线程
3-Finalizer //垃圾回收的线程
2-Reference Handler //清除reference的线程
1-main //主线程
什么是当前线程?
当前在运行的线程称之为当前线程(Current Thread),但当前线程不是固定的。
我们可以使用Thread的currentThread()来获取当前线程的信息。
/**
* Returns a reference to the currently executing thread object.
*
* @return the currently executing thread.
*/
public static native Thread currentThread();
多线程的优点:
资源利用率更好 程序响应更快 程序设计在某方面边的简单
多线程的代价
设计更复杂 上下文切换的开销 增加资源消耗(线程运行的时候需要从计算机里获取一些资源)
JVM划分给每个线程的内存区域称之为线程栈内存
。默认情况下,栈内存的大小1M。也就是说,你每多启动一个线程,至少要多消耗1M的内存资源。
Java创建线程运行时代码的三种方式:
继承Thread类 重写run方法
实现Runnable接口,实现run方法
实现Callable接口,实现call方法。jdk1.5中引进的
但创建线程只有一种方式 创建Thread对象的实例
创建子类还是实现Runnable接口?
对于这两种方式哪种好并没有一个确定的答案,它们都能满足要求。最好实现Runnable接口这种方法。因为Java中有一个线程池
的概念。所谓线程池,可以理解为有一堆线程对象已经创建好了,那么其缺的就是线程运行时代码
。所以我们只需要提供了运行时代码就好了,因此实现Runable接口可能是更好的一种方式。
想要让创建的新线程执行run()方法,必须调用新线程的start方法。
尽管启动线程的顺序是有序的,但是执行的顺序并非是有序的
Jvm和操作系统一起决定了线程的执行顺序,他和线程的启动顺序并非一定是一致的。
线程名
当创建一个线程的时候,如果我们不给线程明确的起一个名字的话,JVM默认会给其指定一个默认的名字。
主线程的名字是就是main,这是固定的。
JVM自动给线程命名的策略:如果没有明确的线程指定名字,JVM就会以Thread为前缀,按照线程创建的顺序,给其命名
获取当前正在执行的线程的名称有两种方式:如果运行时代码是直接覆盖Thread类的run方法编写的,可以通过this.getName()的方式获取线程的名字;如果只是实现了Runable接口,可以通过Thread.currentThread().getName()的方式获取。
Thread对象run方法与start()方法的区别
start()方法用于启动线程,run方法用于执行线程的运行代码,通过start()方法启动线程,然后JVM会回调线程的运行时代码
当我们new出一个Thread对象的时候,其仅仅只是Java中的一个对象而已。只有当我们调用了start方法的时候,其才会真正的成为一个运行的线程,也只有当线程启动后,JVM才会给线程分配相应的资源,例如栈内存空间。
而run方法中的只是线程的运行时代码而已,也就是这个线程要干的事。
覆盖了Thread对象的run方法,同时传入一个Runnable的实现类(实现了其run方法),会执行哪个?
public class Main {
public static void main(String[] args) {
new Thread(new Thread2()) {
public void run() {
System.out.println("Thread ...");
}
}.start();
}
}
public class Thread2 implements Runnable {
@Override
public void run() {
System.out.println("Runnable ...");
}
}
结果是 执行Thread的run方法
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
默认的run方法的逻辑是,如果传递了Runnable的实现类,那么运行Runnable的run方法
但是,在这个案例中,我们重写了这个run方法,所以这段逻辑根本不存在,运行run方法是,实际上运行的是我们覆盖后的方法中的内容。
线程的休眠
线程对象一旦调用其start
方法之后,就会运行,运行的就是run
方法中的代码,等到run
方法中的代码运行结束,线程就执行完成。
如果我们想在线程运行的时候,让其暂停一会,可以调用线程sleep方法,sleep方法的作用是让线程进行休眠,意思是线程暂停执行。在执行的休眠时间内,该线程是不会请求CPU进行执行,因此其他线程可以获得更多的运行机会。
注意sleep方法抛出了一个InterruptedException异常。
线程的中断
中断(interrupt
)表示一个线程应该停止当前所做的事而去另外一件事。通常中断是一个线程给另外一个线程发送中断信号,程序员自行决定如如何进行响应,也就是说收到中断信号后,接下来该做什么。通常情况下,线程收到中断信号后,采取的操作都是停止运行。
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
@Override
public void run() {
for(int i = 0;i<10;i++) {
try {
Thread.sleep(4000);
System.out.println("自定义线程:当前时间 "+new Date().toLocaleString());
}catch (InterruptedException e) {
e.printStackTrace();
System.out.println("自定义线程:收到中断信号,总共循环了"+i+"次");
}
}
}
};
t.start();
Thread.sleep(12000);
System.out.println("主线程:等待12秒发送信号");
t.interrupt();
}
}
这样有一个问题 中断后会继续运行,所以要加一个 在catch里要加上return;
这也间接的说明了interrupt()并不是中断线程,而是发送一个中断的信号
如果在运行时代码中没有抛出InterruptException的方法,那么就需要频繁的调用Thread类的静态方法interrupt()来判断是否收到中断信号
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread() {
@Override
public void run() {
int i = 0;
while(true) {
if(!Thread.interrupted()) {
i++;
System.out.println(i);
}else {
System.out.println("被中断");
return;
}
}
}
};
t.start();
Thread.sleep(10);
t.interrupt();
}
}
//判断当前线程是否被中断,如果这个方法被连续调用两次,那么第二次调用返回false
//(除非第一次调用已经清除了中断状态在第二次调用之前)
//如果线程失活,那么返回false
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
中断状态标记
中断机制的实现是通过一个标记中断状态(interrupt status
)实现的。我们通过调用某个线程对象的interrupt
方法来设置这个标记。当一个线程通过Thread的类的静态方法interrupted
判断到自己被中断后,立即会将这个状态清空。在其他的线程中,我们可以通过调用某个线程对象的isInterrupted方法判断这个线程是否被中断,但是不会中断状态清空。
今天学习结束 ...歇歇