操作系统层面将线程状态分为5种,分别是:新建,就绪,运行,阻塞,死亡
但是在Java的Thread类中,线程的状态枚举如下:
也就是Java将线程分为了六种状态:创建,可运行,阻塞,等待,限时等待和终结
新建状态指的是我们使用new方法新建出来一个线程对象的时候,此时Java还没有将其与OS关联起来,那么这个线程就不会被分配CPU去执行代码,只有调用了start方法之后才会与真正的线程关联起来,此时就从新建状态变为了可运行(就绪)状态,等待分配CPU去执行任务,如果CPU分配给了当前线程,当前线程就会执行对应的任务,任务执行完毕之后,当前线程死亡(终结),注意是死亡,此时这个任务对应的线程以及资源都会被释放。
而并不是所有的任务都会被执行,因此会有部分任务进行阻塞,例如多线程访问同一个锁资源的时候,如果当前线程没有得到锁,那么当前线程就需要进行阻塞,此时CPU被分配给得到锁的线程去执行任务,任务执行完毕之后,锁释放,线程再次去争夺锁,得到锁的话就可以从阻塞状态变为可运行状态,此时只需要等待CPU分配资源就可以运行了。
而当前持锁线程可能执行任务后发现有些条件不满足,那么这个线程不能总是占有锁,因此可以选择调用wait方法去释放锁,然后让自己进入等待状态去等待条件的满足,此时其他线程就可以去竞争锁从而执行他们的任务,等到条件满足,就可以由另一个线程调用notify方法去提醒这个线程再次竞争锁去完成任务。
最后一种是等待的一种情况,叫做现时等待,也就是这次等待可以设定时间。
方法是使用wait方法并且设定等待的时间,再次之前如果时间到了或者另一个线程调用了notify方法,那么这个线程就会被唤醒。
还有一种是sleep方法,这种方法和wait不一样,sleep是不会释放当前线程的锁的,其他线程都得陪着这个线程等,这个线程等待完毕之后在继续执行接下来的任务,调用了sleep方法相对于是告诉CPU你现在先不用执行我的任务了,你可以先去干其他的,相当于让出CPU资源。
首先来两个断点,一个断点打在主线程上,另一个断点打在分支上。
并且设定触发断点的条件为Thread,这样子我们进入debug状态之后就可以以线程作为操控条件了。
主线程断点设置在了最后一句,所以此时前面的语句都已经执行完毕了,但是由于还有一个断点打在了分支线程里面,所以如果我不让他执行,他就得等待
现在切换到分支线程执行语句
此时分支线程的任务已经执行完毕了,那么只有主线程还有任务了。
让主线程执行断点处的语句
可以发现在没有阻塞的情况下,线程状态为NEW->RUNNABLE->TERMINATED
这几个状态都是Thread类中定义的枚举类型。
要让线程阻塞,就让他获取锁失败就行了
首先先创建线程,然后开启线程,但是不让线程执行任务
开启任务之后,先让分支线程执行一下,然后让他触碰到获取锁的位置但是先不执行,然后让主线程去执行获取锁的方法,然后再切换到分支线程去获取锁,此时分支线程获取锁失败,那么再让主线程打印分支线程的状态,此时的状态就是阻塞了。
之后主线程释放锁,分支线程继续执行任务,分支线程变为RUNNABLE状态,然后执行完成任务后死亡。
大致流程也是差不多的,先让分支线程获取到锁,然后此时分支线程调用wait方法把锁让出去,那么主线程就能得到锁,然后查看此时分支线程的状态,主线程把分支线程唤醒之后,分支线程的状态从WAITTING变为了BLOCKING,因为此时分支线程要继续执行任务的话就需要获得锁。
得到锁之后继续重新执行任务,此时主线程发现分支线程的状态就是RUNNABLE了,然后分支线程执行完毕任务,死亡。
线程池的执行流程也就是主线程(业务线程)提交任务到线程池之后,任务的处理流程。
下面来看execute方法
这个方法首先判断要执行的任务是否是空,如果是,返回一个空指针异常。否则,判断当前的工作线程数,如果工作线程数小于核心线程数,那么直接创建一个工作线程执行任务,如果不是,那么判断当前工作队列是否不满,如果是,那么将这个任务放入到工作队列中,如果不是,判断当前是否还有可用的非核心线程(判断maximumPoolSize-corePoolSize>0)即工作线程数是否大于最大线程数,如果不是,那么创建一个非核心线程来执行当前任务,如果是,那么就执行拒绝策略。流程图如下:
1.悲观锁的代表是synchronized和Lock锁
2.乐观锁的代表是Atomiclnteger,使用cas来保证原子性
乐观锁的典型操作就是CAS(compare and set)
这里按照乐观锁的要求,不加锁,每次失败的时候都重试,直到成功
当然,如果想要进行原子操作,需要保证共享变量的可见性,所以一般需要要求变量是volatile类型的。
public class SyncVsCas {static final Unsafe U = Unsafe.getUnsafe();//得到偏移位置static final long BALANCE = U.objectFieldOffset(Account.class,"balance");static class Account{volatile int balance=10;}public static void main(String[] args) {Account account = new Account();while (true) {int o = account.balance;int n = o+5;//第一个参数为:要修改的变量是那个对象的 第二个是偏移量//第三个参数为旧值 第四个参数为新值//这个方法会把o值和我们共享变量的值进行比对 如果一样那么就修改if(U.compareAndSetInt(account, BALANCE, o, n)){//原子性的break;}}System.out.println(account.balance);}
}
悲观锁就是很典型的使用synchronized
public static void sync(Account account){Thread t1 = new Thread(()->{synchronized (account){int o = account.balance;int n = o-5;account.balance=n;}},"t1");Thread t2 = new Thread(()->{synchronized (account){int o = account.balance;int n = o+5;account.balance=n;}},"t2");t1.start();t2.start();System.out.println(account.balance);}