线程与进程理论知识基础
1、并发与并行的区别:并发侧重于在同一实体上,如一台处理器上"同时"处理多个任务,不能脱离时间单位。而并行则侧重于在不同实体上,如多台处理器上同时处理多个任务。
2、申请内存是以进程来申请的。Java程序天生就是多线程的。
typescript
public class OnlyMain {
public static void main(String[] args) {
//Java 虚拟机线程系统的管理接口
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的monitor和synchronizer信息,仅仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos =
threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程ID和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "] "
+ threadInfo.getThreadName());
}
}
}
不到9行代码启动了6个线程。

finalize()方法不一定会被调用,因为他是在Finaizer这个线程触发的,如果主线程退出,该线程也退出,就不会执行了。
3、新开启线程的方式:只有两种。这是JDK官方说法,Thread源码上标注的。(1)通过Thread,线程的抽象;(2)实现Runnable,对任务的抽象。
4、线程调用stop()
的存在不安全性,Stop()
已被放弃,因为这些方法带有强制性,不建议使用,会强制回收线程,可能会导致线程占有的资源不会正常释放。这会导致不可预料的后果。如果你确认该情况强制回收没有问题,是可以的。Interrupt()
方法可以使用。我们应该用这个,相当于告诉这个线程,你应该中断了,但是中断时间由线程自己做主。线程是协作式,而不是抢占式,也就是通知,而不是强制占用停止。另外还有isInterrupted()
与interrupted()
,两者都是用来判断当前线程是否已经被置为了中断。需要注意调用interrupted()
后会将中断标志位置为true,执行完之后又变成false,其他函数没有这个操作。不建议自定义一个setCancel()
来做处理,因为一旦线程被挂起,例如调用sleep()
,wait()
,take()
等方法,线程根本不会去执行cancel代码块。使用isInterrupted()
还有个好处,就是调用sleep()
,wait()
,take()
会抛出InterruptedException
异常,一旦抛出这个异常,interrupted
的标志位也会被改为false。为什么JDK在此时直接要改为true呢?因为线程被唤醒之后,可能已经获得了运行的资源,如果直接中断,可能会出现资源时间浪费的情况,如果想改为true,可以调用interrupt
方法。 Future
可以实现中断,和线程执行完成的回调。处于死锁状态的线程不会理会interrupt。

scss
while(!isInterrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()
+" in InterruptedException interrupt flag is "
+isInterrupted());
// 在这里做资源释放之后再改标识位。
interrupt();
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " I am extends Thread.");
}
5、如何在Runnable去中断一个线程?
csharp
private static class UseRunnable implements Runnable{
@Override
public void run() {
// 中断一个线程。
while(!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ " I am implements Runnable.");
}
System.out.println(Thread.currentThread().getName()
+" interrupt flag is "+Thread.currentThread().isInterrupted());
}
}
6、连续两次调用start方法会怎么样?会报错。线程只能启动一次。run()
和 start()
的区别:
csharp
public void run() {
if (target != null) {
target.run();
}
}
如果没有指定ruannable,那么Thread直接调用了run方法是没有效果的,直接调用run(),会在当前线程执行。run可以反复调用,就是一个普通的方法。
7、线程的6种状态:

8、Join方法:A线程正在执行,调用线程B的join()
方法时,A会挂起,等待B执行完之后才会继续执行A,如果B调用C线程.Join(),这样需要等BC都执行完成之后才能继续A,就像栈一样。
9、线程优先级:在操作系统层面,线程优先级的作用并不大。
10、除了main 线程,其余的都是守护线程。通过 new thread()启动的都是用户线程,非守护线程,如果你想自己设置为一个守护线程,只需要调用 useThread.setDaemon(true)
;另外需要注意:守护线程中finally不一定起作用。用户线程先结束,守护线程后结束。
csharp
try {
while (!isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ " I am extends Thread.");
}
System.out.println(Thread.currentThread().getName()
+ " interrupt flag is " + isInterrupted());
} finally {
// 守护线程中finally不一定起作用,这取决于当前操作系统有没有给这个守护线程分配时间片。
System.out.println(" .............finally");
}

11、Synchoronized 关键字,内置锁,可以做用于class对象锁(也有说类锁,每个类对应的一个class对象)和对象。对象头里边会有几个标志位,可以用来标记是否获取了锁。实现原理:
可以看一下字节码:
这两个指令是由JVM虚拟机添加的。注释掉之后,这两个指令就没有了。
每一个monitor是有一个对象与之对应的,叫Monitor对象,所以Synchoronized实际上就是对这个monitor对象进行加锁,执行完之后再释放锁。详情可以参考:blog.csdn.net/JAYU_37/art... 如果给方法添加Synchoronized关键字,那么方法还会多一个ACC_SYNCHRONIZED的flag

另外需要注意,如果Synchoronized是加载方法上的,那么编译后的class 代码不会有monitorenter 和monitorexit指令,但是在运行时,JVM还是会把这两个指令加进去的。下边就是Synchoronized锁对象和锁方法的不同之处。
对象头里边包含,GC年龄,属于哪个类(类型指针),如果是数组,还会多一个长度的记录。随着对象的运行,对象头的数据也会发生变化。

一个线程的挂起和恢复,会发生两次上下文切换,大概3~5ms。第一次是从运行态到挂起,挂起之后恢复又是一次切换。这些切换很耗费时间。为了解决这些问题,就引入了偏向锁,轻量级锁,重量级锁。
12、Volatile
关键字,最轻量的同步机制;保证可见性,但是不能保证原子性,无法保证多线程安全性。在一写多读的情况下,用该方式比较合适。
13、 ThreadLocal
,为每一个线程提供一个变量的副本,实现了线程的隔离。ThreadLocal以ThreadLocal为键, 以设置的值为Value,
scala
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 为何定义为数组?
private Entry[] table;
这个数组的初始大小是16,为何将Entry定义为数组?请看以下的代码:
typescript
private static ThreadLocal<Integer> s1 = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 1;
}
};
private static ThreadLocal<Integer> s2 = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 2;
}
};
private static ThreadLocal<String> s3 = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "3";
}
};
线程中我们可以定义多个ThreadLocal变量,这也是我们使用Entry数组的原因。另外,不是多个Map,而是多个ThreadLocal都可以放到一个Map中去。一个线程拥有的ThreadLocal的变量副本可能有多个需要保存,就如上边的代码所示。每一个线程都有一个自己的ThreadLocalMap,以ThreadLocal为键,传入的值为Value进行保存的。对应的数量比例,线程:ThreadLocalMap:ThreadLocal = 1 : 1 : n,但是要注意ThreadLocal可以是多个不同线程的Map的键,不过由于ThreadLocalMap是每个线程独有的,因此不会有影响。
ThreadLocal
导致的一定的内存泄漏问题分析(为什么说一定的?这个后续会分析):

如图所示,在ThreadLocal中,ThreadLocalMap的entry的key是弱引用,在每次GC的时候都会回收。但是我们知道,每一个线程都有一个ThreadLocalMap,而Map中有Entry, 由于ThreadLocal这个很容易被回收,导致我们无法通过ThreadLocal.get()来获取对应的value。造成了内存泄漏。这里的GCroot就是CurrentThreadRef。只有当Thread本身被回收,才能将引用链切断。因此,使用ThreadLocal是一定要注意使用remove() 方法。
为什么说ThreadLocal是发生一定的内存泄漏? 因为他的get和set方法,实际上里边也有可能调用:
java
private int expungeStaleEntry(int staleSlot)
这个方法就是用来清除key为null的value的。
为什么ThreadLocal使用弱引用来作为Map的key?如果是用强引用,这个内存泄漏是必然的。会导致expungeStaleEntry必然无法执行。此情况下,如果不调用remove,一定内存泄漏。
ThreadLocal
的线程不安全问题分析:
typescript
public static Number number = new Number(0);
对于上边的变量,因为使用了static进行修饰,会导致各个线程的ThreadLocal获取到的副本无法隔离,只是隔离了引用,但是对象都是同一个。那么如何解决呢?一是直接去掉static关键字,二是在每一个ThreadLocal初始化的时候给一个初值,这样他们就能够拷贝自己的副本了。
14、线程的协作: wait,notify,notifyAll必须要在 synchronized
代码块中执行,他们都是在object中的方法,是在对象上等待和唤醒。JDK规定的,没得选,不写编译不会报错,运行必定报错。 等待和通知的标准范式 等待:synchronized(对象) { while(条件不满足){ 对象.wait() } //业务逻辑 } 通知: synchronized(对象){ //业务逻辑,改变条件 对象.notify/notifyA11(); // so some things. } 对象.wait(),会释放锁,对象.notify()/notifyAll()// 不会释放锁,直到synchronized代码块执行完成后才释放。 ,如果只有一个条件,可以用notifyAll,多个条件不可以用。如果一个对象包含了多个条件,就不能用notifyAll,因为唤醒的线程有可能不满足对应的条件。notify,随机唤起其中一个,notifyAll, 让所有的线程都来检查条件。
15、各个同步方法对锁的影响: yield()
:让出CPU的执行权,不会释放锁。sleep()
不会释放锁;wait()
会释放锁,唤醒的时候会重新竞争锁,锁到手之后才会继续执行wait()
之后的代码。notify
与notifyAll
对锁没有影响。