目录
- 进程和线程
- start和run
- Thread和Runnable
- 实现线程返回值
- 线程的状态
- sleep和wait
- notify和notifyall
- yield
- interrupt
- synchronized
- sybchronized和ReentrantLock
- CAS
- java线程池
进程和线程
进程和线程的由来
串行:初期的计算机智能串行执行任务,并且需要长时间等待用户输入
批处理:预先将用户的指令集中成清单,批量串行处理用户指令,任然无法并发执行
进程:进程独占内存空间,保存各自运行状态相互间不干扰可以互相切换,为并发处理任务提供了可能
线程:共享进程的内存资源,相互间切换更快速,支持更细粒度的任务控制,使进程内的子任务得以并发执行
进程和线程的区别
进程是资源分配的最小单位,线程是CPU调度的最小单位
- 所有与进程相关的资源,都被记录在PCB中
- 进程是抢占处理机的调度单位;线程属于某个进程,共享其资源
- 线程只由堆栈寄存器、程序计数器和TCB组成
总结
- 线程不能看做独立应用,而进程可看做独立应用
- 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径
- 线程没有独立的地址空间,多进程的程序比多线程程序健壮
- 进程的切换比线程的切换开销大
java进程和线程的关系
- java对操作系统提供的功能进行封装,包括进程和线程
- 运行一个程序会产生一个进程,进程包含至少一个线程
- 每个进程对应一个JVM实例,多个线程共享JVM里的堆
- Java采用点线程编程模型,程序会自动创建主线程
Thread中start和run方法的区别
先通过一个例子,来辅助理解start()和run()
public Class ThreadTest{ private static void attack(){ System.out.println("Fight"); System.out.println("Current thread is:"+Thread.CurrentThread().getName()); } public static void main(String[] args){ Thread t=new Thread(){ public void run(){ attack(); } } System.out.println("Current main thread is:"+Thread.CurrentThread().getName()); t.run(); } }
t.run()——执行结果:
Current main thread is:main
Fight
Current thread is:main
而将t.run改变为t.start(),执行结果变为:
Current main thread is:main
Fight
Current thread is:Thread-0
调用run(),会引用主线程来执行方法,而调用start(),会调用非main的线程来执行attack()
start()会调用JVM_StartThread()创建一个新的子线程,并通过threa_entry去调用run()
区别
- 调用start()会创建一个新的子线程并启动
- run()只是Thread的一个普通方法调用
Thread和Runnable是什么关系?
- Thread是实现了Runnable接口的类,使得run支持多线程
- 因类的单一继承原则,推荐使用runnable接口
MyThread m1=new MyThread(); //继承Thread类 m1.start(); __________________________________ MyThread m2=new MyThread(); //实现runnable接口 Thread t1=new Thread(); t1.start(); //runnable没有start()
如何给run()传参?
实现的方式主要的三种
- 构造函数传参
- 成员变量传参
- 回调函数传参
如何处理线程的返回值
实现的方式主要有三种:
- 主线程等待法
public Class ThreadTest implements Runnable{ private String value; public void run(){ try{ Thread.CurrentThread().sleep(5000); }catch(InterruptedException e){ e.printStackTrace(); } value="we have value"; } public static void main(String[] args){ ThreadTest tt=new ThreadTest(); Thread t=new Thread(tt); t.start(); while(tt.value==null){ //若没有这一步,value可能null,我们无法精准判定子任务返回value值 Thread.CurrentThread().sleep(100); } System.out.println("value:"+tt.value); } }
- 使用Thread类的join()阻塞当前线程以等待子线程处理完毕(join()等待其他线程终止,在当前线程中调用另一个线程的join(),则当前线程转入阻塞状态,直到另一线程运行结束,当前线程再由阻塞转为就绪)
public Class ThreadTest implements Runnable{ private String value; public void run(){ try{ Thread.CurrentThread().sleep(5000); }catch(InterruptedException e){ e.printStackTrace(); } value="we have value"; } public static void main(String[] args){ ThreadTest tt=new ThreadTest(); Thread t=new Thread(tt); t.start(); // while(tt.value==null){ //若没有这一步,value可能null,我们无法精准判定子任务返回value值 // Thread.CurrentThread().sleep(100); // } t.join(); System.out.println("value:"+tt.value); } }
- 通过Callable接口实现:通过FutureTask或者线程池获
public class MyCallable implements Callable<String>{ @Override public String call() throws Exception{ String value="test"; System.out.println("Ready to work"); Thread.CurrentThread().sleep(5000); System.out.println("task done"); return value; } } public class FutureTaskDemo{ public static void main(String[] args)throws ExecutionException,InterruptedException{ FutureTask<String> task=new FutureTask<String>(new Callable()); new Thread(task).start(); if(!task.isDone()){ System.out.println("task has not finished,please wait!"); } System.out.println("task return:"+task.get()); } 执行结果:
public class MyCallable implements Callable<String>{ @Override public String call() throws Exception{ String value="test"; System.out.println("Ready to work"); Thread.CurrentThread().sleep(5000); System.out.println("task done"); return value; } } //第一种:使用Future Task获取 public class FutureTaskDemo{ public static void main(String[] args)throws ExecutionException,InterruptedException{ FutureTask<String> task=new FutureTask<String>(new MyCallable()); new Thread(task).start(); if(!task.isDone()){ System.out.println("task has not finished,please wait!"); } System.out.println("task return:"+task.get()); } } //第二种:使用线程池获取 public class ThreadPoolskDemo{ public static void main(String[] args){ ExecutorService newCachedThreadPool=Executors.newCachedThreadPool(); Future<String> future=newCachedThreadPool,submit(new MyCallable()); new Thread(task).start(); if(!future.isDone()){ System.out.println("task has not finished,please wait!"); } try{ System.out.println("task return:"+task.get()); }catch(InterruptedException e){ e.printStackTrace(); }catch(ExecutionException e){ e.printStackTrace(); }finally{ newCachedThreadPool.shutdown();//关闭线程池 } } }
执行结果:
task has not finished,please wait!
Ready to work
task done
task return:test
线程的状态
官方文档:
六个状态
- 新建(new):创建后尚未启动的线程状态
- 运行(Runnable):包含Running和Ready
- 无限期等待(Waiting):不会被分配CPU执行时间,需要显示被唤醒
- 限期等待(Time Waiting):在一定时间后会由系统自动唤醒
- 阻塞(block):等待获取排它锁
- 结束(Terminated):已终止线程的状态,线程已经结束执行
sleep和wait的区别
基本的差别
- sleep是Thread类的方法,wait是Object类中定义的方法
- sleep()可以在任何地方使用
- wait()只能在synchronized()或synchronized{}中使用(因为Wait只有获取锁,才能够释放锁)
最主要的本质区别
public class WaitSleepDemo{ public static void main(String[] args) { final Object lock=new Object(); new Thread(new Runnable(){ @Override public void run(){ System.out.println("thread A is waiting to get lock"); synchronized(lock){ try{ System.out.println("thread A get lock"); Thread.sleep(20); System.out.println("thread A do wait method"); lock.wait(1000); System.out.println("thread A is done"); }catch(InterruptedException e){ e.printStackTrace(); } } } }).start(); try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } new Thread(new Runnable(){ @Override public void run(){ System.out.println("thread B is waiting to get lock"); synchronized(lock){ try{ System.out.println("thread B get lock"); System.out.println("thread B do sleep 10 ms"); lock.wait(10); System.out.println("thread B is done"); }catch(InterruptedException e){ e.printStackTrace(); } } } }).start(); }
-
执行结果: thread A is waiting to get lock thread A get lock(由于线程A沉睡sleep 20ms>10ms,所以去执行线程B) thread B is waiting to get lock(但由于线程B没有获得lock,所以返回执行线程A) thread A do wait method(lock等待wait 1000ms,释放锁,去执行线程B) thread B get lock thread B do sleep 10 ms thread B is done thread A is done(线程B执行完,等待了1000ms结束后再执行线程A
- Object.wait不仅会让出CPU,还会释放已经占有的同步资源
public class WaitSleepDemo{ public static void main(String[] args) { final Object lock=new Object(); new Thread(new Runnable(){ @Override public void run(){ System.out.println("thread A is waiting to get lock"); synchronized(lock){ try{ System.out.println("thread A get lock"); Thread.sleep(20); System.out.println("thread A do wait method"); lock.wait(1000); System.out.println("thread A is done"); }catch(InterruptedException e){ e.printStackTrace(); } } } }).start(); try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } new Thread(new Runnable(){ @Override public void run(){ System.out.println("thread B is waiting to get lock"); synchronized(lock){ try{ System.out.println("thread B get lock"); System.out.println("thread B do sleep 10 ms"); Thread.sleep(10); System.out.println("thread B is done"); }catch(InterruptedException e){ e.printStackTrace(); } } } }).start(); } }
执行结果: thread A is waiting to get lock thread A get lock(由于线程A沉睡sleep 20ms>10ms,所以去执行线程B) thread B is waiting to get lock(但由于线程B没有获得lock,所以返回执行线程A) thread A do wait method(lock等待wait 1000ms,释放锁,去执行线程B) thread B get lock thread B do sleep 10 ms thread B is done thread A is done(线程B执行完,等待了1000ms结束后再执行线程A)
将sleep和wait互换位置
public class WaitSleepDemo{ public static void main(String[] args) { final Object lock=new Object(); new Thread(new Runnable(){ @Override public void run(){ System.out.println("thread A is waiting to get lock"); synchronized(lock){ try{ System.out.println("thread A get lock"); Thread.sleep(20); System.out.println("thread A do wait method"); Thread.sleep(1000); System.out.println("thread A is done"); }catch(InterruptedException e){ e.printStackTrace(); } } } }).start(); try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } new Thread(new Runnable(){ @Override public void run(){ System.out.println("thread B is waiting to get lock"); synchronized(lock){ try{ System.out.println("thread B get lock"); System.out.println("thread B do sleep 10 ms"); lock.wait(10); System.out.println("thread B is done"); }catch(InterruptedException e){ e.printStackTrace(); } } } }).start(); } }
执行结果: thread A is waiting to get lock thread A get lock thread B is waiting to get lock thread A do wait method thread A is done thread B get lock thread B do sleep 10 ms thread B is done
notify()和notifyall()的区别
public class WaitSleepDemo{ public static void main(String[] args) { final Object lock=new Object(); new Thread(new Runnable(){ @Override public void run(){ System.out.println("thread A is waiting to get lock"); synchronized(lock){ try{ System.out.println("thread A get lock"); Thread.sleep(20); System.out.println("thread A do wait method"); lock.wait(); System.out.println("thread A is done"); }catch(InterruptedException e){ e.printStackTrace(); } } } }).start(); try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } new Thread(new Runnable(){ @Override public void run(){ System.out.println("thread B is waiting to get lock"); synchronized(lock){ try{ System.out.println("thread B get lock"); System.out.println("thread B do sleep 10 ms"); Thread.sleep(10); System.out.println("thread B is done"); }catch(InterruptedException e){ e.printStackTrace(); } } } }).start(); } }
执行结果: thread A is waiting to get lock thread A get lock(由于线程A沉睡sleep 20ms>10ms,所以去执行线程B) thread B is waiting to get lock(但由于线程B没有获得lock,所以返回执行线程A) thread A do wait method(lock等待wait 1000ms,释放锁,去执行线程B) thread B get lock thread B do sleep 10 ms thread B is done ......
由于lock.wait()线程一直在无限的等待中,所以去唤醒它,才能执行下去,要在线程B执行结束后,添加lock.notify()去唤醒线程A,执行结果和第一次结果一样,调用notifall(),结果一样
public class WaitSleepDemo{ public static void main(String[] args) { final Object lock=new Object(); new Thread(new Runnable(){ @Override public void run(){ System.out.println("thread A is waiting to get lock"); synchronized(lock){ try{ System.out.println("thread A get lock"); Thread.sleep(20); System.out.println("thread A do wait method"); lock.wait(); System.out.println("thread A is done"); }catch(InterruptedException e){ e.printStackTrace(); } } } }).start(); try{ Thread.sleep(10); }catch(InterruptedException e){ e.printStackTrace(); } new Thread(new Runnable(){ @Override public void run(){ System.out.println("thread B is waiting to get lock"); synchronized(lock){ try{ System.out.println("thread B get lock"); System.out.println("thread B do sleep 10 ms"); Thread.sleep(10); System.out.println("thread B is done"); lock.notify() }catch(InterruptedException e){ e.printStackTrace(); } } } }).start(); } }
两个概念
- 锁池EntryList
假设线程A已经拥有某个对象(不是类)的锁,而其他线程B、C想要调用这个对象的某个synchronized方法(或者块),由于B、C线程在进入对象的synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池
- 等待池WaitSet
假设线程A调用某个对象的wait(),线程A就会释放该该对象的锁,同时现场A就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁
区别:
- notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会
- notify只会随机选取一个处于等待池中的线程进入锁池中竞争获取锁的机会
yield
概念
当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示
它能让他们线程由运行——>>就绪状态,从而让其它具有相同优先级的等待线程获取执行权。但是,并不能保证当前线程调用yield()之后,其它具有想用优先级的线程一定就能获得执行权,也有可能当前线程又进入到“运行状态”继续运行
如何中断线程
已经被抛弃的方法
- 通过调用stop()停止线程(突然性的停止会导致数据不同步的问题,所以stop()被抛弃了)
- 通过调用suspend()和resume()
目前使用的方法
- 调用interrupt(),通知该线程中断了
- ①如果线程处于阻塞状态,那么线程将立即退出阻塞状态,并抛出一个InterruptedException异常
- ②如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响
- 需要被调用的线程配合中断
- ①在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
- ②如果线程处于正常活动状态,那么会将该线程中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响
线程状态以及状态之间的转换
Synchronized
线程安全问题的主要诱因
- 存在共享数据(也称临界资源)
- 存在多条线程共同操作这些共享数据
解决问题的根本方法
同一时刻有且仅有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再对共享数据进行操作
互斥锁的特性
互斥性:在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。互斥性也称为操作的原子性
可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致
synchronized 锁的不是代码,锁的都是对象
根据获取的锁的分类:获取对象锁和获取类锁
获取对象锁的两种用法
- 同步代码块(synchronized(this),synchronized(类实例对象)),锁是小括号()中的实例对象
- 用不非静态方法(synchronized method),锁是当前对象的实例对象
获取类锁的两种方法
- 同步代码块(synchronized(类.class)),锁是小括号()中的类对象(Class对象)
- 同步静态方法(synchronized static method),锁是当前对象的类对象(Class对象)
对象锁和类锁的总结
- 有线程访问对象的同步代码块,另外的线程可以访问该对象的非同步代码块
- 若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步代码块的线程会阻塞
- 若锁住的是同一对象,一个线程在访问对象的同步方法时,另一个访问对象同步方法的线程会被阻塞
- 若锁住的是同一对象,一个线程在访问对象的同步代码块时,另一个访问对象同步方法的线程会被阻塞,反之亦然
- 同一个类的不同对象的对象锁互不干扰
- 类锁由于也是一种特殊的对象锁,因此表现和上诉1,2,3,4一致,而由于一个类只有一把对象锁,所以同一个类的不同对象使用类锁将会是同步的
- 类锁和对象锁互不干扰
Synchronized和ReentrantLock的区别
ReentrantLock(再入锁)
- 位于java.util.concurrent.locks包
- 和CountDownLatch、FutureTask、Semaphore一样基于AQS实现
- 能够实现比synchronized更细粒度的控制,如控制fairness
ReentrantLock公平性的设置
- ReentrantLock fairLock=new ReentrantLock(true)
- 参数为true,倾向于将锁赋予等待时间最久的线程
- 公平锁:获取锁的顺序按先后调用lock方法的顺序(慎用)
- 非公平锁:抢占的顺序不一定,看运气
- synchronized是非公平锁
ReentrantLock将锁对象化
- 判断是否有线程,或者某个特定线程,在排队等待获取锁
- 代超时的获取锁的尝试
- 感知有没有成功获取锁
是否能将wait otify otifAll对象化
- java.util.concurrent.locks.Condition
总结
- synchronizedhi关键字、ReentrantLock是类
- ReentrantLock可以对获取锁的等待时间进行设置,避免死锁
- ReentrantLock可以获取各种锁的信息
- ReentrantLock可以灵活地实现多路通知
- 机制:sync操作Mark Word,lock调用Unsafe类的Park()
CAS(Compare and Swap)
一种高效实现线程安全性的方法
- 支持原子更新操作,适用于计数器,序列发生器等场景
- 属于乐观锁机制,号称lock-free
- CAS操作失败时由开发者决定是继续尝试,还是执行别的操作
CAS思想
- 包含三个操作数——内存位置(V),预期原值(A)和新值(B)
CAS多数情况下对开发者来说是透明的
- J.U.C的atomic包提供了常用的原子性数据类型以及引用、数组等相关原子类型和更新操作工具,是很多线程安全程序的首选
- Unsafe类虽提供CAS服务,但因能够操纵任意内存地址读写而有隐患
- Java9以后,可以使用Vrarible Handle API来代替Unsafe
缺点
- 若循环时间长,则开销很大
- 只能保证一个共享变量的原子操作
- ABA问题 解决:AtomicAtampedReference
Java线程池
利用Executor创建不同的线程池满足不同场景的需求
Fork/Join框架
- 把大任务分割成若干个小任务并行执行,最终汇总每个小任务结果后得到大任务结果的框
Work-Stealing算法:某个线程从其他队列里窃取任务来执行
为什么使用线程池?
- 降低资源消耗
- 提高线程的可管理性
Exector的框架
J.U.C的三个Executor接口
- Executor:运行任务的简单接口,将任务提交和任务执行细节解耦
- ExecutorService:具备管理执行器和任务生命周期的方法,提交任务机制更完善
- ScheduledExecutorService:支持Future和定期执行任务
ThreadPoolExecutor
ThreadPoolExecutor的构造函数
- CorePoolSize:核心线程数量
- maximumPoolSize:线程不够用时能够创建的最大线程数
- workQueue:任务等待队列
- keepAliveTime:抢占的顺序不一定,看运气
- threadFactory:创建新线程,Executors.defaultThreadFactory()
handler:线程池的饱和策略
- AbortPolicy:直接抛出异常,这是默认策略
- CallerRunsPolicy:用调用者躲在的线程老执行任务
- DiscardOldestPolicy:丢弃队列中靠最前的任务,并执行当前任务
- DiscardPolicy:直接丢弃任务
- 实现RejectedExectionHandler接口的自定义handle
新任务提交execute执行后的判断
流程图
线程池的状态
RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务
SHUTDOWN:不再接受新提交的任务,但可以处理存量任务
STOP:不再接受新提交的任务,也不处理存量任务
TIDING:所有的任务都已终止
TERMINATED:terminated()方法执行完后进入该状态
线程池的大小如何选定
- CPU密集型:线程数=按照核数或者核数+1设定
- I/O密集型:线程数=CPU*(1+平等等待时间/平均工作时间)