1、线程与进程
线程(thread):线程是运行的程序单元,依托于进程存在。一个进程可以包含多个线程,多线程可以共享一块内存空间和一组系统资源。
进程(processes):进程是程序的一次动态执行,通常每一个进程都拥有自己独立的内存空间和系统资源
2、线程的创建
- 继承Thread类,重写run方法
- 实现runnable接口,实现run方法
- 实现Callable接口,实现call方法
- lambda表达式创建线程
1)、继承thread类,代码实现;
class ThreadTest{ public static void main(String[] args) throws Exception{ MyThread thresd = new MyThread(); thread.start(); } } class MyThread extends Thread{ @Override public void run(){ System.out.println("123"): } }
2)、实现runnable接口,代码实现
class ThreadTest{ publiic static void main(String[] args){ MyRunnable runnable = new MyRunnable (); new Thread(runnable).start(); } } class MyRunnable implements Runnable{ @Override public void run(){ System.out.println("122"); } }
3)、实现Callable 接口,代码
class ThreadTest{ public static void main(String[] agrs){ MyCallable callable = new MyCallable(); //定义返回结果 FutureTask<String> result = new FutureTask(callable ); //执行程序 new Thread(result).start(); System.out.println(result.get()); } } class MyCallable implements Callable{ @Override public String call(){ System.out.println("1222"); return "ok"; } }
4)、Lambda表达式创建线程
jdk 8 之后可以使用Lambda表达式方便的创建线程,参考代码如下:
new Thread(() ->System.out.println("lambda of thread")).start();
3、线程等待
wait():当使用wait()方法时,必须先持有线程的锁,否则会抛出illegalMonitorStateException
使用notify、notifyall()唤醒线程
notify:唤醒对像的等待池中的一个线程
notifyall:唤醒对象等待池中的所有线程
使用 System.exit(0)
可以让整个程序退出;
要中断单个线程,可配合 interrupt()
对线程进行“中断”
4、线程优先级
默认情况下,一个线程的优先级继承于父类。可以使用setPriority方法设置(1-10)优先级,数字越大优先级越高。
代码如下:
Thread thread = new Thread(() -> System.out.println("java")); thread.setPriority(10); thread.start();
5、死锁
死锁:死锁是指两个或两个以上 的进程执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象
如何预防死锁:
- 尽量使用tryLock(long timeout,TimeUtil util)的方法,设置超时时间,超时可以退出,预防死锁
- 尽量使用java.util.concurrent并发类代替自己手写锁;
- 尽量降低锁的使用粒度,尽量不要几个功能共用一把锁;
- 尽量减少同步代码块
6、如何保证一个线程执行完在执行第二个线程?
使用join()方法,等待上一个线程执行完之后再执行当前线程。示例代码如下:
Thread joinThread = new Thread(() ->{ try{ System.out.Println("执行前"); Thread.sleep(1000); System.out.println("执行后"); }catch(InterruptedException e){ e.printStackTrace(); } }); joinThread.start(); joinThread.join(); System.out.println("主程序");
7、线程的常用方法
- currentThread():返回当前正在执行的线程引用
- getName():返回线程名称
- setPriority()/getpriority():设置/返回此线程的优先级
- isAlive():检测线程是否处于 活动状态(正在运行或准备运行状态)
- sleep():使线程休眠
- join():使等待线程执行
- yield():让同优先级的线程有执行机会
- interrupted():使线程处于中断状态,但不能真正中断线程
8、wait()和sleep()的区别:
- sleep()不释放锁;wait()释放锁
- 存在的类不同,sleep来自thread,wait来自object
- 用法不同,sleep时间到了会自动恢复;wait需要使用notify或notifyall唤醒
9、线程中的start()和run()区别:
start()方法用于启动线程,run()方法用于执行线程的运行时代码。run()可以重复调用,而start()只能调用一次。
10、线程有哪些状态
jdk 8 中线程有6种状态:
- new:尚未启动
- runnable:正在执行中
- blocked:阻塞(被同步锁或io锁阻塞)
- waiting:永久等待状态
- time_waiting:等待指定时间重新被唤醒状态
- terminated:执行完成
11、线程的调度策略
线程调度器选择优先级最高的线程运行,但如果产生以下情况,就会终止线程的运行:
- 线程中调用了yield()方法,让出了对CPU的占用权
- 线程中调用了sleep()方法,使线程进入睡眠状态;
- 线程由于IO操作而阻塞
- 另一个更高优先级的线程出现
- 在支持时间片的系统中,该进程的时间片用完。
1、线程池
线程池(Thread Pool):把一个或多个线程通过统一方式进行调度和重复使用的技术,避免了线程过多带来的使用上的开销。
2、为什么使用线程池
- 可重复使用已有线程,避免对象创建、消亡和过度切换的性能开销
- 避免大量创建同类线程而带来的资源过度竞争和内存溢出的问题
- 支持更多功能,比如延迟任务线程池和缓存线程池
3、线程池使用
创建线程池的方式:
- ThreadPoolExecutor
- Executors(可创建6种不同的线程池类型)
ThreadPoolExecutor的使用
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor (2,10,10L,TimeUtil.SECONDS,new LinkedBlockingQueue(1(100)); //ThreadPoolExecutor 参数说明:从左到右依次代表:线程池中核心线程数、线程池中最大线程数、线程池限制超时时间、时间单位、线程池中的任务队列
threadPoolExecutor.execute(new Runnable(){ @Override public void run(){ System.out.println("执行线程池"); } });
4、线程池执行方法:execute()、submit()
二者区别在于submit()方法可以接收线程池执行的返回值
具体使用:
//创建线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor (2,10,10L,TimeUtil.SECONDS,new LinkedBlockingQueue(1(100)); //execute 使用 threadPoolExecutor.exexute(new Runnable() @Override public void run(){ System.out.println("线程启动"); } }); //submit使用 Future<String> future = threadPoolExecutor.submit(new Callable<String>() @Override public String call() throws Exeception{ System.out.println("hello"); return "okay"; } }); System.out.printlln(future.get());
5、线程池关闭
线程池关闭,可以使用shutdown()或shutdownNow()方法,二者区别:
- shutdown():不会立即终止线程池,而是要等所有任务队列中的任务都执行完后才会终止。执行完shutdown方法之后,线程池就不会再接受新任务了。
- shutdownNow():执行该方法线程池的状态立即变成STOP状态,并试图停止所有正在执行的线程,不再处理还在线程池队列中等待的任务,执行此方法会返回为执行的任务
6、ThreadPollExecutor常用方法
- submit/execute:执行线程池
- shutdown/shutdownNow:终止线程池
- isShutdown:判断线程是否终止
- getActiveCount:获取正在运行的线程数
- getCorePoolSize:获取核心线程数
- getMaximumPoolSize:获取最大线程数
- getQueue:获取线程池中的队列任务
- allowCoreThreadTimeOut:设置空闲时是否回收核心线程
7、线程池的工作原理
当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程池进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过最大线程数,就会拒绝执行策略。
8、executors(实际开发中不推荐使用executors的方式来创建线程池,避免OOM(OutOfMemoryError)内存溢出)
executors可以创建6种线程池:
- fixedThreadPool:创建一个数量固定的线程池,超出的任务会在队列中等待空闲的线程,可以控制线程的最大并发数
- CachedThreadPool:短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并缓存线程以重复使用,如果限制60秒没被使用,则会被移除缓存
- SingleThreadExecutor:创建一个单线程线程池
- ScheduledThreadPool:创建一个数量固定的线程池,支持执行定时任务或周期性任务
- WorkStealingPool:jdk 8 新增创建线程池的方法,创建时不设置任何参数,则以当前机器处理器个数作为线程个数,此线程池会并行处理任务,不能保证执行顺序
9、单线程的线程池存在的意义
单线程线程池提供了队列功能,如果有多个任务会排队执行,可以保证人物质性的顺序性。
1、ThreadLocal(线程安全的)
threadLocal用于解决多线程间的数据隔离问题。也就是说ThreadLocal会为每个线程创建一个单独的变量副本。
2、ThreadLocal有什么用
ThreadLocal的应用场景:
- ThreadLocal可以用来管理Session,因为每个人的信息都不一样,所以适合用ThreadLocal来管理
- 数据库连接,为每个线程分配一个独立的资源,也适合用ThreadLocal来实现
3、ThreadLocal可以通过子类InheritableThreadLocal实现数据共享
ThreadLocal inheritableThreadLocal =new InheritableThreadLocal(); inheritableThreadLocal.set("test"); new Thread(() -> System.out.println(inheritableThreadLocal.get())).start();
4、ThreadLocal正确使用方法
为了解决ThreadLocal中OOM的问题,只需要在使用完ThreadLocal之后,调用remove()方法
5、ThreadLocal和Synchronized有什么区别
ThreadLocal和Synchronized都用于解决多线程并发访问,防止任务在共享资源上产生冲突。
但是,ThreadLocal与Synchronized有本质的区别:Synchronized用于实现同步机制,是利用锁机制使变量或代码块在某一时刻只能被一个线程访问,是一种“以时间换空间”的方式。而ThreadLocal为每一个线程提供了独立的变量副本,这样每个线程(变量)的操作都是独立的,是一种“以空间换时间”的方式
1、线程安全
线程安全的解决方案:
- 数据不共享,单线程可见,比如ThreadLocal就是单线程可见的;
- 使用线程安全类,比如StringBuffer
- 使用同步代码块或者锁
2、线程同步和锁
synchronized
synchronized是java提供的同步机制,当一个线程正在操作同步代码块(synchronized修饰的代码)时,其他线程只能阻塞等待原有线程执行完再执行。
使用:
1)、使用synchronized修饰代码块
2)、使用synchronized修饰方法
3、悲观锁、乐观锁
悲观锁:悲观锁认为对同一个数据的并发操作,一定会发生改变,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁:与悲观锁相反,它获取数据的时候,并不担心数据被修改,每次获取数据的时候也不会加锁,只是在更新数据的时候,判断现有的数据是否和元数据一致来判断数据是否被其他线程操作,如果没有被其他线程操作则更新,如果被其他线程操作则不更新。
4、synchronized是哪种锁的实现?为什么
synchronized是悲观锁的实现,因为synchronized修饰的代码快,每次执行都会进行加锁操作,同时只允许一个线程进行操作,所以是悲观锁的实现。
5、volatile的作用是什么?
volatile是Java虚拟机提供的最轻量级的同步机制,党变量被定义成volatile之后,具备两种特性:
- 保证变量对所有线程的 可见性,当一个线程改变了这个变量的值,修改的新值对其他线程是可见的(可以立即得知的)
- 禁止指令重排序优化,普通变量仅仅能保证在该方法在执行过程中得到正确结果,但是不能保证程序代码的执行顺序。
6、volatile和synchronized有什么区别
synchronized既能保证可见性又能保证原子性,而volition只能保证可见性。比如,i++如果使用synchronized修饰 是线程安全的,而volition会有线程安全提
9、CAS(Compare and Swap)会产生什么问题?怎么解决
CAS是标准的 乐观锁的实现,会产生ABA的问题,ABA问题通常解决办法是添加版本号,每次修改操作时版本号加一。