多线程--基础
-
创建线程的三方式
- 继承Thread类,重写run()方法
- 实现Runnable接口,重写run()方法
- 使用线程池创建(ThreadPoolExecutor 或 Executors):本质上依旧是前两种
- 小案例:龟兔赛跑
- 继承Thread类,重写run()方法
-
线程的几种状态
- NEW:尚未启动的线程处于此状态(新生)
- RUNNABLE:在Java虚拟机中执行的线程处于这种状态(就绪或者运行)
- TIME_WAITING:在指定的等待时间内等待另一个线程执行操作的线程处于此状态(阻塞)
- WAITING:无限期等待另一线程执行特定操作的线程处于此状态(等待)
- BLOCKED:等待监视器锁定被阻止的线程处于此状态(锁)
- TERMINATED:线程已完成执行
-
改变线程状态的几种方法
- start():
- 使此线程开始执行;Java虚拟机调用此线程的run方法
- 结果是两个线程同时运行:当前线程(从对start方法的调用返回)和另一个线程(执行其run方法)
- 多次启动一个线程是不合法的。特别是,一个线程一旦完成执行就不能重新启动
- sleep(long millis): 使当前执行的线程休眠,休眠时间为知道的millis数(单位为毫秒)
- yield(): 让线程直接进入就绪状态,重新等待cpu调用,不占资源,礼让并不是一定成功的,有可能线程刚进入就绪状态就被cpu重新调度
- join():join让启动的线程进入阻塞状态让别的线程先走,可以设置时间,如果时间过了别的线程还没走完则开始继续线程(相当于插队)
- wait():当前线程必须拥有此对象的监视器。线程释放此监视器的所有权,并等待直到另一个线程通过调用notify方法或notifyAll方法通知等待此对象监视器唤醒的线程。(让线程进入等待状态,等待其它线程唤醒;对一个对象使用wait(),不是让该对象wait,而是让持有该对象锁的线程wait()!)
- notify(): 唤醒等待此对象监视器的单个线程。如果有线程正在等待这个对象,则选择其中一个线程被唤醒。选择是任意的,是根据实现的自由裁量权来进行的。线程通过调用其中一个等待方法来等待对象的监视器(唤醒其它的等待线程)
- setDaemon(true): 将当前线程变为守护线程(所有线程默认为用户线程,即JVM会等线程执行完后才停止运行,而守护线程则不受此保护,JVM不会等待守护线程执行完毕)
- 线程状态转换图:
- start():
-
线程同步
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块- 同步方法(public synchronized void method(int args){})
synchronized方法控制对“成员变量|类变量”对象的访问:每个对象对应一把锁,每个调用synchronized方法的线程都必须获得该方法所属对象的锁方能执行,否则所属线程阻塞,方法一旦执行,则独占该锁,直到从方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。 - 同步块(synchronized (obj){},obj称为同步监视器)
- obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器是this即该对象本身,或class对象(没有static就是锁当前实例化对象,加了static锁的是位于元空间的类的class对象)
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器为锁,锁定并访问
- 同步方法(public synchronized void method(int args){})
-
死锁问题
- 死锁:指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进
- 死锁案例: