1.什么是线程和进程?
进程:进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。
线程:线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法与资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈。
在Java中,线程作为最小调度单位,进程作为资源分配的最小单位。在windows中进程是不活动的,只是作为线程的容器。
2.创建线程的方法?
3.线程使用中常见方法?
1.sleep()和yield():前者会让当前线程从Running进入Timed Waiting(阻塞)状态,其它线程可以使用interrupt方法打断正在睡眠的线程,sleep方法会抛出InterruptedException,睡眠结束后的线程未必会立刻得到执行;调用yield会让当前线程从Running状态进入Runnable就绪状态,然后调度执行其他线程,具体的实现依赖于操作系统的任务调度器。
2.join()方法:t1.join()表示需要等待名为t1的线程执行完毕,可以在其中设置时间参数,指定等待的时间。但是如果等待时间设置过长,只要t1线程执行完毕就会继续执行主线程中的内容,不会一直等待。
4.守护线程
Java中默认所有线程都结束运行,整个进程才会结束,不过守护线程是例外,如果只有守护线程还在运行,整个进程也会结束。
垃圾回收器线程就是一种守护线程;Tomcat中的Acceptor和Poller线程都是守护线程,所以Tomcat接收到shutdown命令后,不会等待它们处理完当前请求。
5.线程状态
1.初始状态:仅仅在语言层面创建了线程对象,还未与操作系统线程关联;
2.可运行状态:(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由CPU调度执行;
3.运行状态:指获取了CPU时间片运行中的状态;
4.阻塞状态:如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入阻塞状态。等BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至可运行状态。与可运行状态的区别是,对阻塞状态的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们。
5.终止状态:表示线程已经执行完毕,生命周期已经结束,不再转换为其他状态。
6.sleep(long n)和wait(long n)的区别?
1.sleep是Thread中的方法,而wait是Object中的方法;
2.sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用;
3.sleep在睡眠的同时,不会释放对象锁,但wait在等待的时候会释放对象锁;
4.不过二者设置后的状态都是TIMED_WAITING;
7.join()方法的底层原理?
1.首先要明确join方法是要等待调用的线程执行完毕
public final synchronized void join(long millis) throws InterruptedException{
long base = System.currentTimeMillis();
long now = 0;
if(mills < 0){
throw new IllegalArgumentException("timeout value is negative");
}
if(mills == 0){
while(isAlive()){
wait(0);
}
}else{
while(isAlive()){
long delay = millis - now;
if(delay <= 0){
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
无参的join方法会一直等待,有参的join方法,不允许传递负数;参数为0时,调用的是wait方法;参数大于0时,先记录当前时间base和已经等待的时间now,只要线程存活,循环计算还需等待的时间delay,如果delay<=0,则跳出循环停止等待,否则,继续等待delay的时间,每次要多已经等待的时间now进行更新,这样设计避免了被虚假唤醒时,之前等待的时间作废的情况;
8.线程各状态之间的转换?

1.new---->runnable
当调用t.start()方法时,由new--->runnable
2.runnable <---->waiting
t线程用synchronized(obj)获取了对象锁后
调用obj.wait()方法时,t线程从runnable--->waiting
调用obj.notify(), obj.notifyAll(), t.interrupt()时,如果竞争锁成功,t线程从waiting--- >runnable, 竞争锁失败,t线程从waiting--->blocked
3.runnable<--->waiting
当前线程调用t.join()方法时,当前线程从runnable--->waiting,注意是当前线程在t线程对 象的监视器上等待;t线程运行结束,或调用le当前线程的interrup()时,当前线程从waiti--- >runnable。
4.runnable<--->waiting
当前线程调用LockSupport.park()方法会让当前线程从runnable--->waiting,调用LockSupport.unpark(目标线程)或调用了线程的interrupt(),会让目标线程从waiting--->runnable
5.runnable<--->timed_waiting
t线程调用synchornized(obj)获取了对象锁后
调用obj.wait(long n)方法时,t线程从runnable--->timed_waiting
t线程等待时间超过了n毫秒,或调用obj.notify(), obj.notifyAll(), t.interrupt()时,竞争锁 成功,t线程从timed_waiting--->runnable, 竞争锁失败,t线程从timed_waiting--->blocked
6.runnable<--->timed_waiting
当前线程调用t.join(long n)方法时,当前线程从runnable--->timed_waiting,注意是当前线程在t线程对象的监视器上等待
当前线程等待时间超过了n毫秒,或t线程运行结束,或调用了当前线程的interrupt()时,当前线程从timed_waiting--->runnable
7.runnable<--->timed_waiting
当前线程调用Thread.sleep(long n),当前线程从runnable--->timed_waiting,当前线程等待时间超过了n毫秒,当前线程从timed_waiting--->runnable
8.runnable<--->timed_waiting
当前线程调用LockSupport.parkNanos(long nanos)或LockSupport.parkUntil(long millis)时,当前线程从runnable--->timed_waiting,调用LockSupport.unpark(目标线程)或调用了线程的interrupt(),或是等待超时,会让目标线程从timed_waiting--->runnable
9.runnable<--->blocked
t线程用synchronized(obj)获取了对象锁时如果竞争失败,从runnable--->blocked
持有obj锁线程的同步代码块执行完毕,会唤醒对象上所有blocked的线程重新竞争,如果其中t线程竞争成功,从blocked--->runnable,其他失败的线程仍然blocked.
9.volatile(易变关键字)
它可以用来修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存。
volatile的底层实现原理是内存屏障,Memory Barrier,对volatile变量的写指令后会加入写屏障,对volatile变量的读指令前会加入读屏障。
10.线程池
1.线程池状态:ThreadPoolExecutor使用int的高3位来表示线程池状态,低29位表示线程数量。
| 状态名 | 高三位 | 接收新任务 | 处理阻塞队列任务 | 说明 |
| RUNNING | 111 | N | Y | |
| SHUTDOWN | 000 | N | Y | 不会接受新任务,但会处理阻塞队列剩余任务 |
| STOP | 001 | N | N | 会中断正在执行的任务,并抛弃阻塞队列任务 |
| TIDYING | 010 | 任务全执行完毕,活动线程为0即将进入终结 | ||
| TERMINATED | 011 | 终结状态 |
这些信息存储在一个原子变量ctl中,目的是将线程池状态与线程个数合二为一,这样就可以利用一次cas原子操作进行赋值。
2.构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:核心线程数目(最多保留的线程数)
maximumPoolSize:最大线程数目
keepAliveTime:生存时间---针对救急线程
unit:时间单位---针对救急线程
workQueue:阻塞队列
threadFactory:线程工厂---可以为线程创建时起个好名字
handler:拒绝策略
救急线程 = 最大线程数 - 核心线程数
2.1
线程池刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务;(不过可以通过设置prestartAllCoreThreads可以预创建核心线程)
当线程数达到corePoolSize并且没有线程空闲,这时如果再加入任务,新加入的任务会被加入workQueue队列排队,直到有空闲的线程。
如果队列选择了有界队列,那么任务超过了队列大小时,会创建maximumPoolSize - corePoolSize数目的线程来救急。
如果线程到达maximumPoolSize仍然有新任务这时会执行拒绝策略。拒绝策略jdk提供了4种实现,其它著名框架也提供了实现
AbortPolicy让调用者抛出RejectedExecutionException异常,这是默认策略,适用于必须通知调用者任务未能被执行的场景;
CallerRunsPolicy让调用者线程自身运行任务,适用于希望通过减缓任务提交速度来稳定系统的场景;
DiscardPolicy放弃本次任务,不会执行任何操作,也不会抛出任何异常。适用于对部分任务丢弃没有影响的场景,或系统负载较高时不需要处理所有任务;
DiscardOldestPolicy放弃队列中最早的任务,本任务取而代之,适用于希望丢弃最旧的任务以保证新的重要人物能够被处理的场景;
Dubbo的实现,在抛出RejectedExecutionException异常之前会记录日志,并dump线程栈信息,方便定位问题
Netty的实现,是创建一个新线程来执行任务
ActiveMQ的实现,带超时等待(60s)尝试放入队列,类似我们之前自定义的拒绝策略
PinPoint的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略
当高峰过去后,超过corePoolSize的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime和unit来控制
11.Java中线程之间如何进行通信?
线程之间的通信主要依赖于共享内存。由于线程共享同一个进程的内存空间,因此可以直接通过共享变量进行通信;
1)共享变量:线程可以通过访问共享内存来交换信息(需要注意同步问题,防止数据竞争和不一致),共享的也可以是文件,例如写入同一个文件来进行通信;
2)同步机制:synchronized:Java中的同步关键字,用于确保同一时刻只有一个线程可以访问共享资源,利用Object类提供的wait(),notify(),notifyAll()实现线程之间的等待、通知机制; reentrantlock:配合condition提供了类似于wait(),notify()的等待通知机制
blockingqueue:通过阻塞队列实现生产者-消费者模式 ; countdownlatch:可以允许一个或者多个线程等待,直到在其他线程中执行的一组操作完成; volatile:Java中的关键字,确保变量的可见性,防止指令重排; semaphore:信号量,可以控制对特定资源的访问线程数;
1088

被折叠的 条评论
为什么被折叠?



