线程的状态
- 在整个线程的生命周期中,线程的状态有6种:
- New:新建状态,新创建的线程,此时尚未调用start()方法;相当于创建Thread对象
- Runnable:运行状态,运行中的线程,已经调用start()方法,线程正在或即将执行run()方法; 相当于开启线程
- Terminated:终止状态,线程已终止,因此执行run()方法执行完毕。
- Blocked:阻塞状态,运行中的线程,在等待竞争锁时,被阻塞,暂不执行。相当于线程没抢到锁进入了阻塞状态
- Wating:等待状态,运行中的线程,因为join()等方法,进入等待;
- Timed Wating:计时等待状态,运行中的线程,因为执行sleep(等待毫秒值),join(等待毫秒值)等方法,进入及时等待;
- Terminated:终止状态,现成已终止,因为执行run()方法,执行完毕。
线程终止的原因有:
- 线程正常终止:run()方法执行到return()语句返回;
- 线程意外终止:run()方法因为未捕获的异常导致线程终止;
- 对某个线程的Thread实例调用stop()方法强制终止(不推荐使用,因为过期)
2.线程插队:join()方法
join()方法的作用
t.join() 方法会让当前线程进入等待池,并等待线程t执行完毕后才会被唤醒。并不影响同一时刻处在运行的其他线程。
public class Test7 {public static void main(String[] args) {//创建子线程Thread t1=new AddThread();Thread t2=new DecThread();//启动子线程t1.start();t2.start();//线程插队try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}//查看最终结果System.out.println(Counter.count);}static class Counter{public static int count=0;public static final Object lock=new Object();}//递增线程static class AddThread extends Thread{public void run(){for (int i = 0; i <100000 ; i++) {synchronized (Counter.lock){Counter.count+=i;}}}}//递减线程static class DecThread extends Thread{public void run(){for (int i=0;i<100000;i++){synchronized (Counter.lock){Counter.count-=i;}}}}
}
join() 方法的实现原理
- join()方法的底层是利用wait()方法实现;
- join()方法是一个synchronized同步方法,当主线程调用线程t.join()方法时,主线程获取线程t的锁,主线程进入wait()方法,等到t线程执行完成后,通过notifyALL()唤醒主线程,主线程释放锁,主线程开始继续执行。
public class Test6 {private static void printWithThread(String content){System.out.println("["+Thread.currentThread().getName()+"线程]"+content);}public static void main(String[] args) {printWithThread("开始执行main方法");//创建子线程Thread myThread=new Thread(new Runnable() {@Overridepublic void run() {printWithThread("子线程开始执行run方法");printWithThread("即将休息1秒,并让出CPU给别的线程使用");try {Thread.sleep(1000);//休眠 timed_wating 当子线程插队时间大于休眠时间,插队成功;当子线程插队时间小于休眠时间则插队失败printWithThread("我已经休息1秒,又重新获得cpu");printWithThread("休息好了,马上退出");} catch (InterruptedException e) {e.printStackTrace();}}});try {//启动子线程myThread.start();printWithThread("我在main方法里面");//主线程myThread.join(1500);//子线程插队 当子线程插队时间大于休眠时间,插队成功;当子线程插队时间小于休眠时间则插队失败printWithThread("main线程结束");} catch (InterruptedException e) {e.printStackTrace();}}
}
join()方法和sleep()方法的区别?
- join()实质是wait()+synchronized实现,所以join()方法结束后会释放锁;
- sleep()不会释放锁的
线程的中断:interrupt()方法
如果线程需要执行 一个很长时间任务,就可能需要中断线程。中断线程就是其他线程给该线程发一个信号,该线程收到信号后结束执行run()方法,使得自身线程能立刻结束运行。
interrupt()方法的作用
interrupt()方法的作用是设置该线程的中断状态为true,线程是死亡、还是等待新的任务或者是继续至下一步,就取决于中断状态。线程会不时地检测这个中断状态值,以判断线程是否应该被中断。(中断状态的值是否为true)。
interrupt()方法的原理
作用:改变线程的中断状态为true,只有支持线程中断的方法才能用线程中断(wait、sleep、join),方法会监视线程中断状态,一旦发现状态为true,就会抛出InterruptedException异常,使方法进入终止状态。
如果线程没有被阻塞或等待,中断方法没有作用
子线程运行时,可以通过isInterrupted()方法随时观察子线程的中断状态
线程让出:yield()方法
yield()方法的作用
- 线程通过调用yield()告诉JVM的线程调度,当前线程愿意让出CPU给其他线程使用。
- 至于系统是否采纳,取决于JVM的线程调度模型:分时调度模型和抢占调度模型
- 分时调度模型:所有的线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU时间片;
- 抢占式调度模式:优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。
子线程2执行过程中,通过yield()让出CPU,使子线程1执行概率变高。
守护线程
用户线程和守护线程的区别
- 用户线程:就是创建的普通线程
- 守护线程:
- 清除reference线程 Reference Handler
- 调用对象finalize方法的线程 Finalizer
- 分发处理给JVM信号的线程 Signal Dispatcher
- 添加事件监听器 Attach Listener
设置守护线程
在调用start()方法之前,调用setDaemon(true) 把该线程标记为守护线程
- 普通用户线程,在没有完成打印内容时,JVM不会结束
- 守护线程不会影响JVM退出
- 用户线程的中断结束后,主线程的执行结束,会导致JVM的结束退出
多线程同步
多线程的数据不一致:当多个线程同时操作一段逻辑或代码,可能会出现数据不一致问题
多线程模式下,保证逻辑正确,对变量进行读写时,必须保证一组指令以原子方式执行:即某一个线程执行时,其他线程必须等待。
实现方式:
- 加锁
- 定义一个具有原子性的变量(AtomicInteger),调用方式实现 public static AtomicInteger count=new AtomicInteger(0); 该方式不用加锁
要让两个线程一次只能操作一个线程,如果加锁,必须保证两个线程加的是同一把锁
创建一个共有的类,当作锁(Object类型的常量)
public static final Object LOCK=new Object();
synchronized (Counter.LOCK){}当多线程调用两个不同对象同一个类时
- 第一种锁:Counter1.class等同于this.getClass()
synchronized (Counter1.class)等同于synchronized (this.getClass())- 第二种锁:静态方法上加synchronized等同于this.getClass() public static synchronized void toAdd()等同于synchronized (this.getClass())
当多个线程调用同一个对象时
- 方法上加synchronized等同于synchronized(this) synchronized (this)等同于public synchronized void dec()
synchronized关键字的用法
修饰普通方法:用到的锁默认为当前方法调用对象
修饰静态方法:其所用的锁,默认为Class对象
修饰代码块:其所用的锁,是指定的某个java对象
当一个线程访问对象的一个synchronized同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
父类中synchronized修饰的方法,如果子类没有重写,则该方法仍然是线程安全性;如果子类重写,并且没有使用synchronized修饰,则该方法是线程不安全的
接口方法不能使用synchronized关键字
构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步
离开synchronized代码块后,该线程锁尺有所,会自动释放
synchronized实现原理
底层原理
使用synchronized保证线程安全,就是保证原子性,使执行过程中不会被其他线程干扰
synchronized代码块是有一对monitorenter/monitorexit指令实现,synchronized是通过对象内部监听器来实现的
每个对象有一个监听器,线程通过执行monitorenter指令尝试获取monitor的所有权,当monitor被占用时就会处于一个阻塞状态;还有一些线程会调用对象的wait(),等wait结束后,抢到的线程获得所有权,其他加入阻塞状态。
锁升级
偏移锁、轻量级锁、重量级锁
jdk6之前,会直接加重量级锁,导致操作系统需要从用户态转为内核态,资源消耗大
jdk6之后,根据JVM检测到不同的竞争状态时,会自动切换到适合的锁实现