一个线程两次调用start()方法会出现什么情况?谈谈线程的生命周期和状态转移。
java线程不允许启动两次。会抛出异常。多次调用start被认为是编程错误
java5之后的线程生命周期:
- 新建(new)
- 就绪(Runnable)
- 运行 (Running)
- 阻塞 (blocking)
- 等待(waiting)
- 结束(Terminated)
各种状态切换的场景:
1.RUNNABLE 与 BLOCKED 的状态转换
线程等待 synchronized 的隐式锁。
等待的线程就会从 RUNNABLE 转换到 BLOCKED 状态。而当等待的线程获得 synchronized 隐式锁时,就又会从 BLOCKED 转换到 RUNNABLE 状态
2. RUNNABLE 与 WAITING 的状态转换
第一种场景,获得 synchronized 隐式锁的线程,调用无参数的 Object.wait() 方法。
第二种场景,调用无参数的 Thread.join() 方法。
当调用 A.join() 的时候,执行这条语句的线程会等待 thread A 执行完,而等待中的这个线程,其状态会从 RUNNABLE 转换到 WAITING。当线程 thread A 执行完,原来等待它的线程又会从 WAITING 状态转换到 RUNNABLE。
第三种场景,调用 LockSupport.park() 方法。
3. RUNNABLE 与 TIMED_WAITING 的状态转换
1.调用带超时参数的 Thread.sleep(long millis) 方法;
2.获得 synchronized 隐式锁的线程,调用带超时参数的 Object.wait(long timeout) 方法;
3.调用带超时参数的 Thread.join(long millis) 方法;
4.调用带超时参数的 LockSupport.parkNanos(Object blocker, long deadline) 方法;
5.调用带超时参数的 LockSupport.parkUntil(long deadline) 方法。
4.从 NEW 到 RUNNABLE 状态
调用线程对象的 start() 方法
5. 从 RUNNABLE 到 TERMINATED 状态
线程 的 interrupt() 方法。
进程与线程
进程
进程由来?
为了对并发执行的程序加以描述和控制,人们引入了“进程”的概念。
进程的定义
进程
是进程实体的运行过程,是系统进行资源分配调度的一个独立单位
属性
- 进程是一个可拥有资源的独立单位;
- 进程同时是一个可独立调度和分派的基本单位
系统是通过什么来控制进程的?
系统总是通过 PCB 对进程进行控制的。
进程控制块 PCB(Process Control Block)它是进程实体的一部分,是操作系统中最重要的记录型数据结构。
进程的三种基本状态:
- 就绪(Ready)状态
- 执行状态
- 阻塞状态
线程
线程由来?
再引入线程,则是为了减少程序在并发执行时所付出的时空开销。
进程和线程的关系?
通常一个进程都拥有若干个线程,至少也有一个线程。
线程作为调度和分派的基本单位,而进程作为资源拥有的基本单位。
线程的属性
- 线程中的实体基本上不拥有系统资源,能保证独立运行。
- 独立调度和分派的基本单位
- 并发执行
- 共享进程
线程上下文切换
cpu通过时间片分配算法来循环执行任务。时间片就是cpu为每个线程执行的时间。这个时间片执行时间很短,通常只有几十毫秒。所以cpu不停的切换,让我们以为线程是同时执行的。
但是线程切换的时候,会保存线程的状态,以便于下次切换回来的时候去加载这些状态。所以线程从保存到加载状态的过程,叫做一次上下文的切换。
上下文切换的弊端就是影响线程的执行速度。
线程实现
- 内核线程:
- 用户线程:用户程序中实现的线程
jdk1.2之前使用的是用户线程,1.2后使用的是内核线程
从操作系统的角度,可以简单认为,线程是系统调度的最小单元,一个进程可以包含多个线程。
状态和方法之间的对应图:
新启线程的方式
1.继承Thread
2.实现Runnable()
3.实现Callable,允许有返回值
public class ThreadDemo {
public static void main(String[] args){
UseCall useCall = new UseCall();
FutureTask<String> futureTask = new FutureTask<>(useCall);
new Thread(futureTask).start();
}
/*实现Callable接口,允许有返回值*/
private static class UseCall implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("I am implements Callable");
return "CallResult";
}
}
}
怎么样才能让Java里的线程安全停止工作?
- 线程自然终止
- 自然执行完
- 抛出未处理异常
- 通过API
- stop(),resume(),suspend()已不建议使用,stop()会导致线程不会正确释放资源,suspend()容易导致死锁。
- java线程是协作式,而非抢占式
调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。 - isInterrupted() 判定当前线程是否处于中断状态。
- static方法interrupted() 判定当前线程是否处于中断状态,同时中断标志位改为false。
- 方法里如果抛出InterruptedException,线程的中断标志位会被复位成false,如果确实是需要中断线程,要求我们自己在catch语句块里再次调用interrupt()。
Thread类中断线程
public class EndThread {
private static class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while(!isInterrupted()) {
System.out.println(threadName+" is run!");
}
System.out.println(threadName+" interrput flag is "+isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new MyThread("myThread");
endThread.start();
Thread.sleep(20);
endThread.interrupt();
}
}
中断Runnable类型的线程
public class EndRunnable {
private static class myRunnable implements Runnable{
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while(!Thread.currentThread().isInterrupted()) {
System.out.println(threadName+" is run!");
}
System.out.println(threadName+" interrput flag is "
+Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
myRunnable useRunnable = new myRunnable();
Thread endThread = new Thread(useRunnable,"myThread");
endThread.start();
Thread.sleep(20);
endThread.interrupt();
}
}
抛出InterruptedException异常的时候,要注意中断标志位
方法里如果抛出InterruptedException,线程的中断标志位会被复位成false,如果确实是需要中断线程,要求我们自己在catch语句块里再次调用interrupt()。
public class HasInterrputException {
private static SimpleDateFormat formater
= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss_SSS");
private static class UseThread extends Thread{
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while(!isInterrupted()) {
try {
System.out.println("UseThread:"+formater.format(new Date()));
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(threadName+" catch interrput flag is "
+isInterrupted()+ " at "
+(formater.format(new Date())));
interrupt();
e.printStackTrace();
}
System.out.println(threadName);
}
System.out.println(threadName+" interrput flag is "
+isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("HasInterrputEx");
endThread.start();
System.out.println("Main:"+formater.format(new Date()));
Thread.sleep(800);
System.out.println("Main begin interrupt thread:"+formater.format(new Date()));
endThread.interrupt();
}
}
守护线程(Daemon Thread)
应用中需要一个长期驻留的服务程序,但是不希望其影响应用退出,就可以将其设置为守护线程,如果JVM发现只有守护线程存在时,将结束进程。
总结就是守护线程与主线程共死。
注意,必须在线程启动之前设置(在start之前)
而且守护线程的finally不能保证一定执行。
Thread thread= new Thread();
//必须在start之前设置
thread.setDaemon(true);
thread.start();
ThreadLocal
ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
使用步骤:
创建一个与特定线程绑定的integer值:
ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
如果我们想拿到值或者设置值,我们只需要调用get()或set()方法
threadLocalValue.set(1);
threadLocalValue.get();
ThreadLocal初始化使用withInitia()方法
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
移除方法:
threadLocal.remove();
具体使用:
public class UseThreadLocal {
static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 100;
}
};
/**
* 运行3个线程
*/
public void StartThreadArray(){
Thread[] runs = new Thread[3];
for(int i=0;i<runs.length;i++){
runs[i]=new Thread(new TestThread(i));
}
for(int i=0;i<runs.length;i++){
runs[i].start();
}
}
/**
*类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
*/
public static class TestThread implements Runnable {
int id;
public TestThread(int id){
this.id = id;
}
public void run() {
System.out.println(Thread.currentThread().getName()+":start");
Integer s = threadLocal.get();//获得变量的值
s = s+id;
threadLocal.set(s);
System.out.println(Thread.currentThread().getName()+":"
+ threadLocal.get());
//threadLocal.remove();
}
}
public static void main(String[] args){
UseThreadLocal test = new UseThreadLocal();
test.StartThreadArray();
}
}
结果:
Thread-0:start
Thread-1:start
Thread-0:100
Thread-2:start
Thread-1:101
Thread-2:102
注意:
它的实现结构,数据存储于线程相关的ThreadLocalMap,其内部条目是弱引用。
通常弱引用都会和引用队列配合清理机制使用,但是ThreadLocal是个例外,它并没有这么做。
这意味着,废弃项目的回收依赖于显式地触发,否则就要等待线程结束,进而回收相应ThreadLocalMap!这就是很多OOM的来源,所以通常都会建议,应用一定要自己负
责remove,并且不要和线程池配合,因为worker线程往往是不会退出的。