一.JAVA线程创建方式
1.1 继承Thread 类
Therad 类是实现了Runnable接口的一个实例,代标一个线程的实例,启动线程的唯一方法是通过Thread 类的start() 实例方法,是一个native方法,将启动一个新线程,执行run()方法。
public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); } } MyThread myThread1 = new MyThread(); myThread1.start();
1.2 实现Runnable 接口
本身已经继承了另一个类,此时可以实现Runnable 接口
public class MyThread extends OtherClass implements Runnable { public void run() { System.out.println("MyThread.run()"); } }MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start();
1.3 ExecutorService 、 Callable 、 Future 有返回值线程
有返回值的任何必须实现Callable 接口 ,执行Callable任务后,可以获取Future对象,在该对象调用get可以获取Callable任务返回的Object。
public class CallableTest implements Callable { private String result; public CallableTest(String result) { this.result = result; } @Override public Object call() { return result; } public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(10); List
1.4 线程池创建
避免线程的创建和销毁,浪费资源,使用缓存策略,也就是线程池。
public class ExecutorServiceTest { public static void main(String[] args) throws Exception { ExecutorService threadPool = Executors.newFixedThreadPool(10); while (true) { threadPool.submit(() -> { System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + "run"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } }}
二.线程的生命周期
2.1 新建状态(new)
使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配
内存,并初始化其成员变量的值。
2.2 就绪状态(runnable)
调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和
程序计数器,等待调度运行。
2.3 运行状态(running)
就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状
态。
2.4 阻塞状态(blocked)
因为某种原因放弃了 cpu 使用权,也即让出了 cpu 时间片,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得 cpu 时间片转到运行(running)状态
等待阻塞(o.wait->等待对列):
运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)中。
同步阻塞(lock->锁池)
运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
其他阻塞(sleep/join)
运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。
2.5 线程死亡(dead)
1.正常结束
run()或 call()方法执行完成,线程正常结束。
2.异常结束
线程抛出一个未捕获的 Exception 或 Error。
3.调用 stop
直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用
三.常见面试题
3.1 并行和并发
并行:同一时刻,两个线程都在执行
并发:同一时刻,只有一个在执行,在同一个时间段内,两个线程都执行了
举例:
以前的手机听歌和拍照在同一时刻是不能同时进行的,现在可以了是因为手机变多核了;
同一时间段内可以听歌和拍照,听歌时拍照功能关闭,拍照时听歌功能关闭,同一时间段内实现了听歌和拍照。
3.2 什么是进程和线程
进程:程序在系统中的活动,是系统进行资源分配和调度的基本单位
线程:进程执行的一个最小单位,多个线程在同一个进程中共享资源
一个进程会在操作系统中开辟堆栈分配内存,再分配资源给到线程。
3.3 sleep 和 wait 的区别
1.sleep 是Thread类的方法,wait属于Object类中方法
2.sleep 导致程序暂停执行指定的时间,让出cpu资源但是不会释放对象的锁,到了指定时间恢复运行状态
3、wait 放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify 方法才进入对象锁定池准备获取对象锁进入运行状态。
3.4 start 和 run 的区别
1.start 的 native方法,会启动一个新线程,此时线程数语就绪状态
2.run是方法调用,在主线程中执行run的方法体
3.5 JAVA 后台线程
1.守护线程(服务线程),优先级比较低,用于为系统中的其他对象和线程提供服务,在启动前setDaemon(true)来设置线程为守护线程;
2.垃圾回收线程就是守护线程,当程序中没有任何线程时,不再产生垃圾,垃圾回收线程也会退出,用户监控和管理系统中的可回收资源。
3.当JVM只剩下守护线程时,JVM就可以退出了。
3.6 线程间的通信方式
volatile 和 synchronized
被关键字volatile修饰的成员变量,任何线程对该变量的访问需要从共享内存中获取,修改时同步刷新道共享内存,保证所有线程对该变量访问的可见性。
wait / notify
join: 等待线程执行结束
3.7 什么是ThreadLocal
3.7.1线程本地变量
ThreadLocalMap
每个线程中都有一个自己的 ThreadLocalMap 类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
将一个共用的 ThreadLocal 静态实例作为 key,将不同对象的引用保存到不同线程的ThreadLocalMap 中,然后在线程执行的各处通过这个静态 ThreadLocal 实例的 get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
ThreadLocalMap 其实就是线程里面的一个属性,它在 Thread 类中定义
ThreadLocal.ThreadLocalMap threadLocals = null;
3.7.2ThreadLocal的作用:
保存线程上下文信息,在任意需要的地方可以获取(可以用于存放 token 数据源 );
线程安全,避免某些情况需要考虑线程安全必须同步带来的性能损失;
线程间数据隔离;
3.7.3 内存泄漏
ThreadLocal 在保存时把自己当作key 存在ThreadLocalMap中,并将key设计成WeakReference弱引用;
Entry的Key使用了弱引用,弱引用会在GC时被回收,那么就存在Key还未被使用,就给回收的情况?
当new ThreadLocal 时,堆中会产生一个ThreadLocal对象的实例,同时有一个指向ThreadLocal实例的强引用;当set 属性时,当前线程的Thread类里有一个ThreadLocalMap ,ThreadLocal实例以自己的内存地址为key,set的值为value,向这个map中添加了键值对,此时同时存在一个强引用和一个弱引用,当强引用不再使用或置为null是,弱引用才会被GC回收。