基础
线程
基础
一、JDK和JRE
JDK:java开发工具包,提供了java的开发环境和运行环境;
JRE:java运行环境;
JDK包含了JRE,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具。
二、数据类型
基本数据类型:byte、short、int、long、float、double、char、boolean
引用数据类型:class、interface、数组、String(本质是一个类)、集合
==和equals的区别:
==:基本类型比较的是值是否相同,引用类型比较的是引用是否相同;
equals:本质上就是==,只不过String和Integer等重写了equals方法,把它变成了值比较。
三、String、StringBuffer、StringBuilder
String是字符串常量,final修饰,不可被继承,每次操作都会生成新的String对象,经常改变字符串内容的情况下最好不要使用String;
StringBuffer字符串变量,线程安全,多线程环境推荐使用;
StringBuilder字符串变量,非线程安全,性能高于StringBuffer;
字符串反转可以使用StringBuilder或者StringBuffer的reverse()方法。
四、final
修饰的类不能被继承;修饰的方法不能被重写;修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
五、抽象类和接口
抽象类可以包含抽象方法,不能直接实例化,不能用final修饰;
用extends来继承抽象类,使用implements来实现接口;
抽象类可以有构造函数,接口不能有;
抽象类可以有main方法,并且我们能运行它,接口不能有main方法;
类可以实现很多个接口,但是只能继承一个抽象类;
接口中的方法默认使用public修饰,抽象类中的方法可以是任意访问修饰符。
六、类的实例化顺序
父类静态变量->父类静态代码块->子类静态变量->子类静态代码块->父类非静态变量(实例变量)->父类构造函数->子类非静态变量(实例变量)->子类构造函数
七、BIO、NIO、AIO
网络IO
BIO:同步阻塞式IO,模式简单使用方便,并发处理能力低;
NIO:同步非阻塞IO,是传统BIO的升级,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO:是NIO的升级,实现了异步非堵塞IO,异步IO的操作基于事件和回调机制。
八、集合
1.数据结构
Arrayxxx:底层数据结构是数组,查询快,增删慢;
Linkedxxx:底层数据结构是链表,查询慢,增删快;
Hashxxx:底层数据结构是哈希表,依赖两个方法,hashCode()和equals();
Treexxx:底层数据结构是二叉树,两种方式排序,自然排序和比较器排序。
2.Collection和Collections
java.util.Collection是一个集合接口(集合类的一个顶级接口),它提供了对集合对象进行基本操作的通用方法,Collection接口在Java类库中有很多具体的实现,比如List与Set;
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作(如提供了一个集合不能被修改的方法unmodifiableMap、unmodifiableList、unmodifiableSet)。
3.List、Set、Map之间的区别
Collection(List、Set),单列集合,集合中元素是孤立的;
Map,双列集合,元素由键值两部分组成,常用的有HashMap、HashTable、TreeMap;
4.HashMap
HashMap<K,V>存储数据采用的哈希表结构,遍历顺序不确定,需要重写键的hashCode()、equals()方法,来保证键的唯一、不重复,允许使用null值和null键,非线程安全,数组+链表+红黑树(一种二叉查找树,算法);
往HashMap中put元素时,首先根据key的hashcode重新计算hash值,根据hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,如果数组中该位置没有元素,就直接将该元素放到数组的该位置上;当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率。
5.HashMap、HashTable、TreeMap
HashTable同步,而HashMap非同步,效率上HashMap要高;
HashMap允许空键值,而HashTable不允许;
TreeMap适用于按自然顺序或自定义顺序遍历键(key)。
6.ArrayList和LinkedList
ArrayList是基于数组的数据结构,LinkedList是基于链表的数据结构,
随机访问,ArrayList(O(1))优于LinkedList(O(n)),
新增和删除,LinkedList占优势,只需要更改指针,而ArrayList需要移动数据,
LinkedList更占内存。
7.迭代器Iterator
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而不需要了解该序列的底层结构,迭代器通常被称为轻量级对象,因为创建它的代价小;Collection、List、Set、Map等都包含了迭代器。
Java中的Iterator功能比较简单,并且只能单向移动,
1.使用方法iterator()要求容器返回一个Iterator,第一次调用Iterator的next()方法时,它返回序列的第一个元素,iterator()方法是java.lang.Iterable接口,被Collection继承;
2.使用next()获得序列中的下一个元素;
3.使用hasNext()检查序列中是否还有元素;
4.使用remove()将迭代器新返回的元素删除;
Iterator是Java迭代器最简单的实现,可用来遍历Set和List集合,只能是前向遍历;
ListIterator只能用来遍历List,可以从两个方向遍历List,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
8.迭代器设计模式
包含以下角色:
抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含hasNext()、first()、next()等方法。
具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
//抽象聚合
interface Aggregate
{
public void add(Object obj);
public void remove(Object obj);
public Iterator getIterator();
}
//具体聚合
class ConcreteAggregate implements Aggregate
{
private List<Object> list=new ArrayList<Object>();
public void add(Object obj)
{
list.add(obj);
}
public void remove(Object obj)
{
list.remove(obj);
}
public Iterator getIterator()
{
return(new ConcreteIterator(list));
}
}
//抽象迭代器
interface Iterator
{
Object first();
Object next();
boolean hasNext();
}
//具体迭代器
class ConcreteIterator implements Iterator
{
private List<Object> list=null;
private int index=-1;
public ConcreteIterator(List<Object> list)
{
this.list=list;
}
public boolean hasNext()
{
if(index<list.size()-1)
{
return true;
}
else
{
return false;
}
}
public Object first()
{
index=0;
Object obj=list.get(index);;
return obj;
}
public Object next()
{
Object obj=null;
if(this.hasNext())
{
obj=list.get(++index);
}
return obj;
}
}
public class IteratorPattern
{
public static void main(String[] args)
{
Aggregate ag=new ConcreteAggregate();
ag.add("中山大学");
ag.add("华南理工");
ag.add("韶关学院");
System.out.print("聚合的内容有:");
Iterator it=ag.getIterator();
while(it.hasNext())
{
Object ob=it.next();
System.out.print(ob.toString()+"\t");
}
Object ob=it.first();
System.out.println("\nFirst:"+ob.toString());
}
}
九、反射
反射机制:在运行状态中,对于任意一个类,都能获取这个类的所有属性和方法,对于任意一个对象,都能调用对象中的任意属性和方法。
常用方法:
1.实例对象.getClass(),获取Class对象,仅用于引用类型;
2.className.class,当前没有某个类的对象时,获取Class对象,引用、基本类型都可以;
3.Class.forName(String className),全路径名,获取Class对象,不需要导入类包,解耦。
十、代理模式
代理(Proxy)是一种设计模式,提供了间接对目标对象进行访问的方式,即通过代理对象访问目标对象,这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能,如加日志,加事务等。
Spring AOP的代理实现模式,即加入Spring中的target是接口的实现时,就使用JDK动态代理,否则就使用Cglib代理。
1.静态代理
//接口类
public interface AdminService {
void update();
Object find();
}
//实现类
public class AdminServiceImpl implements AdminService {
public void update() {
System.out.println("修改管理系统数据");
}
public Object find() {
System.out.println("查看管理系统数据");
return new Object();
}
}
//代理类
public class AdminServiceProxy implements AdminService {
private AdminService adminService;
public AdminServiceProxy(AdminService adminService) {
this.adminService = adminService;
}
public void update() {
System.out.println("判断用户是否有权限进行update操作");
adminService.update();
System.out.println("记录用户执行update操作的用户信息、更改内容和时间等");
}
public Object find() {
System.out.println("判断用户是否有权限进行find操作");
System.out.println("记录用户执行find操作的用户信息、查看内容和时间等");
return adminService.find();
}
}
//测试类
public class StaticProxyTest {
public static void main(String[] args) {
AdminService adminService = new AdminServiceImpl();
AdminServiceProxy proxy = new AdminServiceProxy(adminService);
proxy.update();
System.out.println("=============================");
proxy.find();
}
}
//输出
判断用户是否有权限进行update操作
修改管理系统数据
记录用户执行update操作的用户信息、更改内容和时间等
=============================
判断用户是否有权限进行find操作
记录用户执行find操作的用户信息、查看内容和时间等
查看管理系统数据
2.动态代理
为解决静态代理对象必须实现接口的所有方法的问题,Java给出了动态代理,动态代理具有如下特点:
1.Proxy对象不需要implements接口;
2.Proxy对象的生成利用JDK的Api,在JVM内存中动态的构建Proxy对象,需要使用java.lang.reflect.Proxy类的static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invocationHandler);方法;
3.方法参数说明:
a.ClassLoader loader:指定当前target对象使用类加载器,获取加载器的方法是固定的;
b.Class<?>[] interfaces:target对象实现的接口的类型,使用泛型方式确认类型;
c.InvocationHandler invocationHandler:事件处理,执行target对象的方法时,会触发事件处理器的方法,会把当前执行target对象的方法作为参数传入。
//接口类
public interface AdminService {
void update();
Object find();
}
//实现类
public class AdminServiceImpl implements AdminService {
public void update() {
System.out.println("修改管理系统数据");
}
public Object find() {
System.out.println("查看管理系统数据");
return new Object();
}
}
//代理类
public class AdminServiceInvocation implements InvocationHandler {
private Object target;
public AdminServiceInvocation(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("判断用户是否有权限进行操作");
Object obj = method.invoke(target);
System.out.println("记录用户执行操作的用户信息、更改内容和时间等");
return obj;
}
}
//测试类
public class DynamicProxyTest {
public static void main(String[] args) {
AdminService target = new AdminServiceImpl();
AdminServiceInvocation invocation = new AdminServiceInvocation(target);
AdminService proxy = (AdminService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), invocation);
Object obj = proxy.find();
System.out.println("find 返回对象:" + obj.getClass());
System.out.println("----------------------------------");
proxy.update();
}
}
//输出
判断用户是否有权限进行操作
查看管理系统数据
记录用户执行操作的用户信息、更改内容和时间等
find 返回对象:class java.lang.Object
----------------------------------
判断用户是否有权限进行操作
修改管理系统数据
记录用户执行操作的用户信息、更改内容和时间等
3.Cglib代理
动态代理要求target对象是一个接口的实现对象,假如target对象只是一个单独的对象,并没有实现任何接口,这时候就会用到Cglib代理,通过构建一个子类对象,从而实现对target对象的代理,因此目标对象不能是final类,目标对象的方法不能是final或static。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
//目标对象类
public class AdminCglibService {
public void update() {
System.out.println("修改管理系统数据");
}
public Object find() {
System.out.println("查看管理系统数据");
return new Object();
}
}
//代理类
public class AdminServiceCglibProxy implements MethodInterceptor {
private Object target;
public AdminServiceCglibProxy(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类代理对象
return en.create();
}
public Object intercept(Object object, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {
System.out.println("判断用户是否有权限进行操作");
Object obj = method.invoke(target);
System.out.println("记录用户执行操作的用户信息、更改内容和时间等");
return obj;
}
}
//测试类
public class CglibProxyTest {
public static void main(String[] args) {
AdminCglibService target = new AdminCglibService();
AdminServiceCglibProxy proxyFactory = new AdminServiceCglibProxy(target);
AdminCglibService proxy = (AdminCglibService)proxyFactory.getProxyInstance();
System.out.println("代理对象:" + proxy.getClass());
Object obj = proxy.find();
System.out.println("find 返回对象:" + obj.getClass());
System.out.println("----------------------------------");
proxy.update();
}
}
//输出
代理对象:class com.lance.proxy.demo.service.AdminCglibService$$EnhancerByCGLIB$$41b156f9
判断用户是否有权限进行操作
查看管理系统数据
记录用户执行操作的用户信息、更改内容和时间等
find 返回对象:class java.lang.Object
----------------------------------
判断用户是否有权限进行操作
修改管理系统数据
记录用户执行操作的用户信息、更改内容和时间等
十一、克隆
1.浅拷贝与深拷贝
浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝
深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝
2.实现对象克隆
1)实现Cloneable接口并重写Object类中的clone()方法;
2)实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆,代码如下
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class MyUtil {
private MyUtil() {
throw new AssertionError();
}
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T)ois.readObject();
// 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义,这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
}
}
class Person implements Serializable { ... }
Person p1 = new Person();
Person p2 = MyUtil.clone(p1);
十二、异常
1.throw和throws的区别
throws是用来声明一个方法可能抛出的所有异常信息,throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理;而throw则是指抛出的一个具体的异常类型。
2.finally
finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码放在finally代码块中,表示不管是否出现异常,该代码块都会执行,并在return前执行,一般用来存放一些关闭资源的代码。
3.常见的异常类型
NullPointerException:当应用程序试图访问空对象时,则抛出该异常;
SQLException:提供关于数据库访问错误或其他错误信息的异常;
IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出;
NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常;
FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常;
IOException:当发生某种I/O异常时,抛出此异常,此类是失败或中断的I/O操作生成的异常的通用类;
ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常;
ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常;
IllegalArgumentException:向方法传递了一个不合法或不正确的参数时抛出的异常;
ArithmeticException:当出现异常的运算条件时,抛出此异常,例如,一个整数除以零时;
NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常;
NoSuchMethodException:无法找到某一特定方法时,抛出该异常;
SecurityException:由安全管理器抛出的异常,指示存在安全侵犯;
UnsupportedOperationException:当不支持请求的操作时,抛出该异常;
RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。
十三、Java Web
1.jsp和servlet
1.jsp经编译后就变成了Servlet,JVM只能识别java的类,不能识别jsp的代码,Web容器将jsp的代码编译成JVM能够识别的java类;
2.jsp更擅长页面显示,servlet更擅长于逻辑控制;
3.Servlet中没有内置对象,jsp中的内置对象必须通过HttpServletRequest,HttpServletResponse以及HttpServlet对象得到。
4.jsp是Servlet的一种简化,使用jsp只需要完成程序员需要输出到客户端的内容,jsp中的Java脚本如何镶嵌到一个类中,由jsp容器完成,而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。
2.jsp的内置对象
1.request:封装客户端的请求,其中包含来自GET或POST请求的参数;
2.response:封装服务器对客户端的响应;
3.pageContext:通过该对象可以获取其他对象;
4.session:封装用户会话的对象;
5.application:封装服务器运行环境的对象;
6.out:输出服务器响应的输出流对象;
7.config:Web应用的配置对象;
8.page:jsp页面本身(相当于Java程序中的this);
9.exception:封装页面抛出异常的对象。
3.jsp的作用域
四种作用域,page、request、session和application
1.page代表与一个页面相关的对象和属性;
2.request代表与Web客户端发出的一个请求相关的对象和属性,一个请求可能跨越多个页面,涉及多个Web组件,需要在页面显示的临时数据可以置于此作用域;
3.session代表与某个用户与服务器建立的一次会话相关的对象和属性,跟某个用户相关的数据应该放在用户自己的session中;
4.application代表与整个Web应用程序相关的对象和属性,包括多个页面、请求和会话的一个全局作用域。
4.session和cookie
1.由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是session;session是保存在服务端的,有一个唯一标识;在服务端保存session的方法很多,内存、数据库、文件都有,集群的时候也要考虑session的转移,在大型网站,一般会有专门的session服务器集群,用来保存用户会话,这个时候session信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放session。
2.每次HTTP请求的时候,客户端都会发送相应的cookie信息到服务端,实际上大多数的应用都是用cookie来实现session跟踪的,第一次创建session的时候,服务端会在HTTP协议中告诉客户端,需要在cookie里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了;如果客户端的浏览器禁用了cookie,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如sid=xxxxx这样的参数,服务端据此来识别用户。
3.cookie其实还可以用在一些方便用户的场景下,如保存用户名。
十四、网络
1.OSI七层模型
2.TCP
1.特性
传输层协议;面向连接;提供可靠的服务,通过TCP连接传送的数据无差错,不丢失,不重复,且按序到达;每一条TCP连接只能是点到点的;对系统资源要求较多。
2.三次握手
为了实现可靠数据传输,TCP协议的通信双方,都必须维护一个序列号,以标识发送出去的数据包中,哪些是已经被对方收到的,三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤。
3.粘包
发送方产生粘包:采用TCP协议传输数据的客户端与服务器经常是保持一个长连接的状态,当发送的数据包过于的小时,TCP协议默认会启用Nagle算法,将这些较小的数据包进行合并发送也就是粘包;
接收方产生粘包:数据到达接收方,从网络模型的下方传递至传输层,传输层的TCP协议将其放置接收缓冲区,然后由应用层来主动获取;如果不及时获取,而下一个数据又到来并有一部分放入缓冲区,等读取数据时就是一个粘包。
3.UDP
传输层协议;无连接;不保证可靠交付;具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高要求的通信或广播通信;对系统资源要求较少。
4.HTTP
1.特性
应用层协议,详细规定了浏览器和万维网服务器之间互相通信的规则,由请求和响应构成,是一个无状态的协议。
2.工作流程
首先客户端与服务器需要建立连接;建立连接后,客户端发送一个请求给服务器;服务器接到请求后,给予相应的响应信息;客户端接收服务器所返回的信息通过浏览器显示在显示屏上,然后客户端与服务器断开连接。
3.get和post
get在浏览器回退时是无害的,而post会再次提交请求;
get产生的url地址可以被Bookmark,而post不可以;
get请求会被浏览器主动cache,而post不会,除非手动设置;
get请求只能进行url编码,而post支持多种编码方式;
get请求参数会被完整保留在浏览器历史记录里,而post中的参数不会被保留;
get请求在url中传送的参数是有长度限制的,而post没有;
对参数的数据类型,get只接受ASCII字符,而post没有限制。
get比post更不安全,因为参数直接暴露在url上,所以不能用来传递敏感信息。
get参数通过url传递,post放在Request body中。
5.FTP
应用层协议,文件传输协议
线程
一、并发、并行、同步、异步、多线程
并发:同一时间段有几个程序都处于已经启动到运行完毕之间,并且这几个程序都在同一个CPU上运行,并发的两种关系是同步和互斥;
并行:两个或者多个事件在同一时刻发生,单CPU中进程被交替执行,表现出一种并发的外部特征;在多CPU中,进程可以交替执行,还能重叠执行,实现并行处理;
互斥:进程之间访问临界资源时相互排斥的现象;
同步:进程之间存在依赖关系,一个进程结束的输出作为另一个进程的输入,具有同步关系的一组并发进程之间发送的信息称为消息或者事件;
异步:和同步相对,同步是顺序执行,而异步是彼此独立,多线程是实现异步的一个方式,异步是让调用方法的主线程不需要同步等待另一个线程的完成,从而让主线程干其他事情。
二、创建线程的方式
1.继承Thread类创建线程类
定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务,因此把run()方法称为执行体;创建Thread子类的实例,即创建了线程对象;调用线程对象的start()方法来启动该线程。
2.通过Runnable接口创建线程类
定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体;创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象;调用线程对象的start()方法来启动该线程。
3.通过Callable和Future创建有返回值的线程
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值;创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;使用FutureTask对象作为Thread对象的target创建并启动新线程;调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
三、线程的状态
创建状态:在生成线程对象,并没有调用该对象的start方法时;
就绪状态:当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态,线程从等待或者睡眠中回来之后,也会处于就绪状态;
运行状态:线程调度将线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码;
阻塞状态:线程正在运行的时候,被暂停,通常是为了等待某个事件的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞;
死亡状态:如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡,对于已经死亡的线程,无法再使用start方法令其进入就绪。
四、sleep()和wait()
sleep():线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争CPU的执行时间,因为sleep()是静态方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep()方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象;
wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程,被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象的锁,notifyAll()唤醒所有wait线程,notify()随机唤醒一个wait线程。
五、守护线程
调用线程对象的setDaemon(true)方法,则可以将其设置为守护线程,该方法必须在启动线程前调用;
JVM的垃圾回收、内存管理等线程都是守护线程,还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。
六、线程池
1.创建线程池
Executors是一个线程池工厂
public static ExecutorService newSingleThreadExecutor();创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行;
public static ExecutorService newFixedThreadPool(int nThreads);创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程;
public static ExecutorService newCachedThreadPool();创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
2.提交任务
主要有两种方法,execute()和submit()
submit有返回值,而execute没有,submit方便Exception处理
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> System.out.println("hello"));
}
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Long> future = executor.submit(() -> {
System.out.println("task is executed");
return System.currentTimeMillis();
});
System.out.println("task execute time is: " + future.get());
}
3.关闭线程池
shutdown()会将线程池状态置为SHUTDOWN,不再接受新的任务,同时会等待线程池中已有的任务执行完成再结束;
shutdownNow()会将线程池状态置为SHUTDOWN,对所有线程执行interrupt()操作,清空队列,并将队列中的任务返回回来。
七、多线程的运行安全
线程安全体现在三个方面:
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作(atomic,synchronized,Lock);
可见性:一个线程对主内存的修改可以及时地被其他线程看到(synchronized,volatile);
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,(happens-before原则)。
1.atomic
比较并交换(CAS),是原子操作的一种,可用于在多线程编程中实现不被打断的数据交换操作,从而避免多线程同时改写某一数据时由于执行顺序不确定性以及中断的不可预知性产生的数据不一致问题,该操作通过将内存中的值与指定数据进行比较,当数值一样时将内存中的数据替换为新的值;
当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而未成功的线程可以像自旋锁一样,继续尝试,一直等到执行成功;
Atomic包的类的实现绝大调用Unsafe的方法,而Unsafe底层实际上是调用C代码,C代码调用汇编,最后生成出一条CPU指令cmpxchg,完成操作,这也就为啥CAS是原子性的,因为它是一条CPU指令,不会被打断。
2.synchronized
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个线程可以进入到临界区,同时它还可以保证共享变量的内存可见性;
Java中每一个对象都可以作为锁,普通同步方法,锁是当前实例对象;静态同步方法,锁是当前类的class对象;同步方法块,锁是括号里面的对象。
3.synchronized和Lock
synchronized是java内置关键字,在jvm层面,Lock是个java类;
synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
synchronized会自动释放锁(线程执行完同步代码或执行过程中发生异常都会释放锁),Lock需在finally中手动释放锁(unlock()方法),否则容易造成线程死锁;
用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待,如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
Lock锁适合大量代码的同步问题,synchronized锁适合少量代码的同步问题。
4.synchronized和volatile
java内存模型:所有变量都存放在主内存中,线程使用变量时,把变量复制到自己的工作内存,线程操作的是自己工作内存中的变量;
volatile本质是告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住;
volatile仅能使用在变量级别,synchronized则可以使用在变量、方法、和类级别;
volatile仅能实现变量的修改可见性,不能保证原子性,而synchronized则可以保证变量的修改可见性和原子性。
volatile不会造成线程的阻塞,synchronized可能会造成线程的阻塞。
5.死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
八、异步
1.CompletableFuture
public class JavaPromise {
public static void main(String[] args) throws Throwable, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
System.out.println("task started!");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task finished!";
}
}, executor);
future.thenAccept(e -> System.out.println(e + " ok"));
System.out.println("main thread is running");
}
}
2.Async注解
开启注解
@Configuration
@EnableAsync
public class BeanConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);// 设置核心线程数
executor.setMaxPoolSize(10);// 设置最大线程数
executor.setQueueCapacity(20);// 设置队列容量
executor.setKeepAliveSeconds(60);// 设置线程活跃时间(秒)
executor.setThreadNamePrefix("hello-");// 设置默认线程名称
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 设置拒绝策略
executor.setWaitForTasksToCompleteOnShutdown(true);// 等待所有任务结束后再关闭线程池
return executor;
}
}
@Test
public void test3() throws Exception {
System.out.println("main函数开始执行");
myService.longtime();
System.out.println("main函数执行结束");
}
@Async
public void longtime() {
System.out.println("我在执行一项耗时任务");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成");
}
网友评论