Java基础
多线程的状态
新建状态
当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码
就绪状态
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
运行状态
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.
阻塞状态
线程运行过程中,可能由于各种原因进入阻塞状态:
1>线程通过调用sleep方法进入睡眠状态;
2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3>线程试图得到一个锁,而该锁正被其他线程持有;
4>线程在等待某个触发条件;
死亡状态
有两个原因会导致线程死亡:
1) run方法正常退出而自然死亡,
2) 一个未捕获的异常终止了run方法而使线程猝死。
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.
Java对象头有什么
怎么让多个线程顺序执行
public static void main(String[] args) throws Exception {
thread1.start();
thread1.join();
thread2.start();
thread2.join();
thread3.start();
thread3.join();
thread4.start();
thread4.join();
thread5.start();
thread5.join();
}
Synchronized底层原理,monitor操作过程
同步方法通过ACC_SYNCHRONIZED关键字隐式的方法进行加锁。当线程要执行的方法被标注上ACC_SYNCHRONIZED时,需要先获得锁才能执行该方法。
同步代码块通过monitorenter和 monitorexit来执行加锁。当线程执行到monitorenter时候要先获得锁,才能执行后面的方法。当线程执行到monitorexit的时候要释放锁。每个对象自身要维护着一个被加锁次数的计数器,当计数器不为0时,只有获得锁的线程才能再次获得锁
Synchronized锁的升级过程,
首先,锁一共有4种状态,级别从低到高:无锁——>偏向锁——>轻量锁——>重量锁。锁可以升级但不能降级
偏向锁:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步代码快并获得锁时,会在对象头和锁帧中记录存储偏向锁的线程ID,以后该线程在进入同步代码块先判断对象头的MarkWord里是否存储着指向线程的偏向锁,如果存在就直接获得锁
轻量级锁:当其他线程尝试竞争偏向锁,锁升级为轻量级锁。线程在执行同步块之前,JVM会先在当前线程的锁帧中创建用于存储锁记录的空间,并将对象头的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,标识其他线程竞争锁,当前线程便尝试使用自旋来获得锁。
重量级锁:锁在原地循环等待的时候,是会消耗CPU资源的。所以自旋必须要有一定的条件控制,否则如果一个线程执行同步代码块的时间很长,那么等待锁的线程会不断的循环反而会消耗CPU资源。默认情况下锁自旋的次数是10次,可以使用-XX:PreBlockSpin参数来设置自旋锁等待的次数。10次后如果还没获取锁,则升级为重量级锁
Synchronized和lock区别
区别1:Synchronized 是Java的一个关键字,而Lock是java.util.concurrent.Locks 包下的一个接口;
区别2:Synchronized 使用过后,会自动释放锁,而Lock需要手动上锁、手动释放锁。(在 finally 块中(unlock))
区别3:Lock提供了更多的实现方法,而且 可响应中断、可定时, 而synchronized 关键字不能响应中断;
eg:
Lock() ; //获取锁
tryLock(); //获取锁
tryLock(long time, TimeUnit unit); //在一定时间单位内等待后,尝试获取锁;
lockInterruptibly(); //获取锁,可响应中断;
响应中断:
A、B 线程同时想获取到锁,A获取锁以后,B会进行等待,这时候等待着锁的线程B,会被Tread.interrupt()方法给中断等待状态、然后去执行其他的事情,而synchronized锁无法被Tread.interrupt()方法给中断掉;
区别4:synchronized关键字是非公平锁,即,不能保证等待锁的那些线程们的顺序,而Lock的子类ReentrantLock默认是非公平锁,但是可通过一个布尔参数的构造方法实例化出一个公平锁;
区别5:synchronized无法判断,是否已经获取到锁,而Lock通过tryLock()方法可以判断,是否已获取到锁;
区别6:Lock可以通过分别定义读写锁提高多个线程读操作的效率。
区别7:二者的底层实现不一样:synchronized是同步阻塞,采用的是悲观并发策略;Lock是同步非阻塞,采用的是乐观并发策略(底层基于volatile关键字和CAS算法实现)
CAS:
CAS,即Compare And Swap,意思是:比较并替换。
CAS算法需要3个操作数:内存地址V,旧预期值A,将要更新的目标值B。
CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。
CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
通常将 CAS算法 用于同步的方式是:从地址 V 读取值 A,执行多步计算来获得新值B,然后使用 CAS算法 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
通常将 CAS算法 用于同步的方式是:从地址 V 读取值 A,执行多步计算来获得新值B,然后使用 CAS算法 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
Volatile关键字
Volatile是JMM(Java内存模型)中用于保证可见性和有序性的轻量级同步机制。
它主要有两个作用,一个是保证被修饰共享变量的可见性,也就是多个线程操作读写时,能被其他线程感知到,在读取到自己的工作内存中,写入变量后又会强制将自己的新值刷新回主内存;另外一个重要作用,用于阻止指令重排序。我们熟知的双检查单例中,instance必须要用到volatile修饰,原因是new singleton时,一般有三个步骤:
- 分配一块内存
- 在内存上初始化singleton对象
- 把这块内存地址返回赋值给instance
但是经过编译器的优化,2和3的顺序有可能颠倒,也就是说你可能拿到的instance可能还没有被初始化,访问instance的成员变量就可能发生空指针异常,而volatile可以阻止这种情况的发生。
简述Lock与ReentrantLock
Lock 接是 java并发包的顶层接口。
可重入锁 ReentrantLock 是 Lock 最常见的实现,与 synchronized 一样可重入。ReentrantLock 在默认情况下是非公平的,可以通过构造方法指定公平锁。一旦使用了公平锁,性能会下降。
多线程下操作共享资源,如何保证安全
一般使用锁,对能保证一致性的最小模块加锁。
A给B转账,多线程下如何保证一致性?
用到了ThrealLocal类,来实现。ThrealLocal中是map集合,存储的是map(thread,T):键标识是当前线程,T标识泛型值。功能:能够唯一标识一个线程,不同线程可以有不同标识。这样可以保证在转账过程中,只能在同一线程中进行事务的操作。保证不同线程之间,事务的操作是隔离的
try {
//获取连接池对象,就当如到threadlical对象中。
connection = getDataResource().getConnection();
/把connection放入threadlocal/
threadLocal.set(connection);
} catch (SQLException e) {
e.printStackTrace();
}
threadLocal
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题
threadLocal实现原理
set
每次我们每次往ThreadLocal中set值就是存入了当前线程对象的threadLocals属性里,而threadLocals的类型是ThreadLocalMap。ThreadLocalMap 可以理解为 ThreadLocal 类实现的定制化的 HashMap。
get
同set方法一样,也是先根据当前线程获取ThreadLocalMap对象,然后在map中取值。
AQS
简述:
AQS其实就是一个可以给我们实现锁的框架
内部实现的关键是:先进先出的队列、state状态
定义了内部类ConditionObject
拥有两种线程模式独占模式和共享模式。
在LOCK包中的相关锁(常用的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建,一般我们叫AQS为同步器。
AQS是一个多线程同步器,是一个用来构建锁的基础框架,JUC包中很多组件都依靠他作为底层实现,例如CountDownLatch,Semaphora等组件。AQS提供了两种锁机制分别是共享锁(读锁)和排他锁(写锁)。共享锁指多个线程共同竞争同一个共享资源时多个线程同时都能够获得,而排他锁再同一时间只能有一个线程能够获得共享资源。AQS通过设置一个由volatile修饰的Int类型的互斥变量state来实现互斥同步,state=0时表名该锁可以被获取,state>=1时时表明该锁已经被获取。当当前锁为空闲时,多个线程同时使用CAS方式去修改State的值(保证了原子性,可见性),最后只有一个线程修改成功并获得锁。其他线程失败后便会执行unsafe类的park方法进行阻塞。这里的阻塞方式创建一个双向队列顺序存储锁竞争的线程,并先进先出的去获取锁(公平锁方式)。非公平锁方式是不管双向链表队列中是否有阻塞的线程它都不会进入队列而是直接去尝试修改互斥变量以获得锁。
wait/sleep区别
1.来自不同的类
wait =>object
sleep =>thread
2.关于锁的释放
wait会释放锁
sleep不会释放
3.适用范围是不同的
wait:必须在同步代码块
sleep:可以在任何地方
4.是否需要捕获异常
wait不需要捕获异常
sleep需要捕获异常
notify()、notifyAll()的区别
-
notify()
用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。
-
notifyAll()
用于唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。
Java多线程之间的通信方式
在Java中线程通信主要有以下三种方式:
-
wait()、notify()、notifyAll()
如果线程之间采用synchronized来保证线程安全,则可以利用wait()、notify()、notifyAll()来实现线程通信。这三个方法都不是Thread类中所声明的方法,而是Object类中声明的方法。原因是每个对象都拥有锁,所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作。并且因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。另外,这三个方法都是本地方法,并且被final修饰,无法被重写。
wait()方法可以让当前线程释放对象锁并进入阻塞状态。notify()方法用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。notifyAll()用于唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了已就绪(将要竞争锁)的线程,阻塞队列存储了被阻塞的线程。当一个阻塞线程被唤醒后,才会进入就绪队列,进而等待CPU的调度。反之,当一个线程被wait后,就会进入阻塞队列,等待被唤醒。
-
await()、signal()、signalAll()
如果线程之间采用Lock来保证线程安全,则可以利用await()、signal()、signalAll()来实现线程通信。这三个方法都是Condition接口中的方法,该接口是在Java 1.5中出现的,它用来替代传统的wait+notify实现线程间的协作,它的使用依赖于 Lock。相比使用wait+notify,使用Condition的await+signal这种方式能够更加安全和高效地实现线程间协作。
Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition() 。 必须要注意的是,Condition 的 await()/signal()/signalAll() 使用都必须在lock保护之内,也就是说,必须在lock.lock()和lock.unlock之间才可以使用。事实上,await()/signal()/signalAll() 与 wait()/notify()/notifyAll()有着天然的对应关系。即:Conditon中的await()对应Object的wait(),Condition中的signal()对应Object的notify(),Condition中的signalAll()对应Object的notifyAll()。
-
BlockingQueue
Java 5提供了一个BlockingQueue接口,虽然BlockingQueue也是Queue的子接口,但它的主要用途并不是作为容器,而是作为线程通信的工具。BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。
程序的两个线程通过交替向BlockingQueue中放入元素、取出元素,即可很好地控制线程的通信。线程之间需要通信,最经典的场景就是生产者与消费者模型,而BlockingQueue就是针对该模型提供的解决方案。
Spring框架和SpringMVC框架
控制反转(IOC)
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
这个过程就叫控制反转 :
控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的 反转 : 程序本身不创建对象 , 而变成被动的接收对象 . 依赖注入 : 就是利用set方法来进行注入的.
IOC是一种编程思想,由主动的编程变成被动的接收
AOP(面向切面编程)
主要用了代理模式,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
1.Spring 创建IOC容器 先扫扫描包中的所有由@Service 和@Component修饰的类,并为它们创建对象,放在Spring IOC容器中。 2.寻找切面类 Spring在创建完对象后,开始寻找由 @Aspect 修饰的切面类并获取切面类中的所有方法。 3.寻找切面类的方法中带有表达式的部分 接下来,Spring找到所有由合法表达式修饰的方法 4.查找有相应方法的类 随后,Spring检查它所扫描到的所有类,并将上一步中找到的方法与所有类进行对照,找出有这个(些)方法的类(这个类就是被代理类)。 5.创建动态对象 最后,Spring根据上一步找到的被代理类以及切面类创建动态类的动态对象并放入Spring IOC容器中。
类型:
前置通知(Before):在目标方法被调用之前调用通知功能; 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么; 返回通知(After-returning ):在目标方法成功执行之后调用通知; 异常通知(After-throwing):在目标方法抛出异常后调用通知; 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
spring用到了什么设计模式
1、代理模式:在AOP和remoting中被用的比较多
2、单例模式:在spring配置文件中定义的bean默认为单例模式
3、模板方法模式:解决代码重复问题
父类定义骨架(共同方法的实现,调用哪些方法及顺序),某些特定方法由子类实现(父类是空方法,子类继承后再重写)
4、前端控制器模式:spring提供了DispatcherServlet来对请求进行分发
5、依赖注入模式:贯穿于BeanFactory和ApplicationContext接口的核心理念
6、工厂模式:bean
7、适配器模式:实现方式:springmvc中的适配器HandlerAdapter
8、装饰器模式:
实现方式:类名中包含Wrapper,或者是Decorator,就是装饰器模式
实质:动态地给一个对象添加一些额外的职责,比生成子类更灵活
9、观察者模式
实现方式:spring的事件驱动模型使用的是观察者模式,常用的地方就是listener的实现
具体实现:事件机制的实现包括事件源、事件、事件监听器:
ApplicationEvent抽象类【事件】
ApplicationListener接口【事件监听器】
ApplicationContext接口【事件源】
10、策略模式
实现方式:spring框架的资源访问Resource接口,是具体资源访问策略的抽象,也是所有资源访问类所实现的接口
springmvc执行流程
1、 用户发送请求至前端控制器DispatcherServlet。
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet调用HandlerAdapter处理器适配器。
5、 HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)。
6、 Controller执行完成返回ModelAndView。
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8、 DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9、 ViewReslover解析后返回具体View。
10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
11、 DispatcherServlet响应用户。
bean的生命周期
Spring中常用注解
1、@Controller
对应表现层的Bean,也就是Action,将标注了此注解的类纳入进spring容器中管理
2、@RequestMapping
RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
3、@Resource和@Autowired
@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
4、@ModelAttribute和 @SessionAttributes
代表的是:该Controller的所有方法在调用前,先执行此@ModelAttribute方法,可用于注解和方法参数中,可以把这个@ModelAttribute特性,应用在BaseController当中,所有的Controller继承BaseController,即可实现在调用Controller时,先执行@ModelAttribute方法。@SessionAttributes即将值放到session作用域中,写在class上面。
5、@PathVariable
用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。
6、@requestParam
@requestParam主要用于在SpringMVC后台控制层获取参数,类似一种是request.getParameter("name"),它有三个常用参数:defaultValue = "0", required = false, value = "isApp";defaultValue 表示设置默认值,required 铜过boolean设置是否是必须要传入的参数,value 值表示接受的传入的参数类型。
7、@ResponseBody
作用: 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。
使用时机:返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;
8、@Component
相当于通用的注解,当不知道一些类归到哪个层时使用,但是不建议。
9、@Repository
用于注解dao层,在daoImpl类上面注解。
10、@configuration
1.表明当前类是一个配置类,是方法bean的源
2.将@Configuration配置的AppConfig的beanDefinitioin属性赋值为full类型的,保证AppConfig类型 可以转变为cglib类型
3.将@Configuration配置的AppConfig由普通类型转变为cglib代理类型,后会生成cglib代理对象,通 过代理对象的方法拦截器,
可以解决AppConfig内部方法bean之间发生依赖调用的时候从容器中去获取,避免了多例的出现