java核心技术笔记 第17讲 一个线程两次调用sart()方法会出现什么情况

一个线程两次调用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)它是进程实体的一部分,是操作系统中最重要的记录型数据结构。

进程的三种基本状态:

  1. 就绪(Ready)状态
  2. 执行状态
  3. 阻塞状态
    在这里插入图片描述

线程

线程由来?

再引入线程,则是为了减少程序在并发执行时所付出的时空开销。

进程和线程的关系?

通常一个进程都拥有若干个线程,至少也有一个线程。

线程作为调度和分派的基本单位,而进程作为资源拥有的基本单位。

线程的属性

  • 线程中的实体基本上不拥有系统资源,能保证独立运行。
  • 独立调度和分派的基本单位
  • 并发执行
  • 共享进程

线程上下文切换

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线程往往是不会退出的

发布了93 篇原创文章 · 获赞 26 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/sxj159753/article/details/94758593