0001 多线程入门
第一节 进程与线程的区别
1. 什么是进程:进程是一个应用程序
2.什么是线程:线程是一条执行路径,main是主线程,自己创建的是子线程。一个进程中最少有一个线程(主线程)
3.创建线程三种方式:
1)继承Thread
2)实现Runnable接口
3使用匿名内部类方式
Thread thread =new Thread(new Runnable(){})
4.为啥不能直接调用run方法启动线程?
如果直接调用run方法其实是用主线程在跑,没有起到多线程的作用,所以要调用start方法才行
5.主线程跟子线程没有关联,所有哪个执行完并不能控制,但是开始执行的时候一定是主线程开始执行先的
6.在实现Runnable接口时,不能直接用new Runnable().start()这种方式去执行线程,应为Runnable中并没有start
方法,所有需要new Thread(),然后将Runnable传入到Thread中,最后才调用start()方法
7. 线程5个状态:
0002 多线程之间实现同步
1.什么是线程安全问题?
多个线程同时共享一个全局变量或者静态变量,做写的操作
2.怎样解决线程安全问题?
1)使用synchronize 2) jdk1.5 并发lock
synchronize使用方法(只适用于单个jvm,集群无效):
1)同步代码块:将可能发生线程安全问题的代码包裹起来,synchronize(同一个数据){ }
2)同步函数: 在方法上加synchronize
3)静态同步函数:在方法加上static 使用synchronize修饰
3.多线程死锁
class ThreadTrain6 implements Runnable { // 这是货票总票数,多个线程会同时共享资源 private int trainCount = 100; public boolean flag = true; private Object mutex = new Object(); @Override public void run() { if (flag) { while (true) { synchronized (mutex) { // 锁(同步代码块)在什么时候释放? 代码执行完, 自动释放锁. // 如果flag为true 先拿到 obj锁,在拿到this 锁、 才能执行。 // 如果flag为false先拿到this,在拿到obj锁,才能执行。 // 死锁解决办法:不要在同步中嵌套同步。 sale(); } } } else { while (true) { sale(); } } } public synchronized void sale() { synchronized (mutex) { if (trainCount > 0) { try { Thread.sleep(40); } catch (Exception e) { } System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票."); trainCount--; } } } } public class DeadlockThread { public static void main(String[] args) throws InterruptedException { ThreadTrain6 threadTrain = new ThreadTrain6(); // 定义 一个实例 Thread thread1 = new Thread(threadTrain, "一号窗口"); Thread thread2 = new Thread(threadTrain, "二号窗口"); thread1.start(); Thread.sleep(1); threadTrain.flag = false; thread2.start(); } }
0003多线程之间通讯wait和notify
1.多线程通讯:多个线程操作一个共享变量,但是操作的方法不同
2.wait要和notify,synchronize配合使用
3.为啥wait和notify定义在Object 中
有的时候要用多线程进行同步,锁有时候是自定义的,自定义有可能是类,所以直接把方法定义到父类Object中,这样所有的类都能使用
Lock锁
1.Synchronize和Lock锁的区别?
Synchronize不能手动开锁,Lock需要手动开关锁
2.Lock 搭配Condition
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
notEmpty.await(); //类似于synchronize中的wait
notFull.signal(); //类似于synchronize中的notify
怎样停止线程?
1.使用退出标志,让run运行完后自动终止
2.使用stop方法强行终止(不推荐)
3.使用interrupt终止
0004 java并发编程
1.有T1,T2,T3三个线程,怎样保证T2在T1执行完后执行,T3在T2执行完后执行?(利用join)
在多线程下,怎样保证主线程最后执行?
public class TestJoin { public static void main(String[] args) { Thread t1 = new MyThread("线程1"); Thread t2 = new MyThread("线程2"); Thread t3 = new MyThread("线程3"); try { //t1先启动 t1.start(); t1.join(); //t2 t2.start(); t2.join(); //t3 t3.start(); t3.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } class MyThread extend Thread{ public MyThread(String name){ setName(name); } @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+": "+i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
第二节 java内存模型
多线程三大特性:原子性,可见性,有序性
原子性:几个步骤要么全部执行要么全部不执行
可见性:当多个线程访问同一变量,一个线程修改了这个变量的值,其他线程能够立即看到修改的值
有序性:程序执行按照代码先后顺序执行
java内存模型(JMM):多线程
jvm内存结构:堆,栈,方法区
共享内存模型指的是java内存模型(JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见
JMM定义了线程和主线程之间的抽象关系,线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存
t1从主内存中获取了变量,修改后还没有更新回主存,然后t2从主内存中获取变量并修改,此时就会出现线程安全问题
第三节 volatile关键字
Volatile 关键字的作用是变量在多个线程之间可见。
代码:
class ThreadVolatileDemo extends Thread { public boolean flag = true; @Override public void run() { System.out.println("开始执行子线程...."); while (flag) { } System.out.println("线程停止"); } public void setRuning(boolean flag) { this.flag = flag; } } public class ThreadVolatile { public static void main(String[] args) throws InterruptedException { ThreadVolatileDemo threadVolatileDemo = new ThreadVolatileDemo(); threadVolatileDemo.start();//启动的时候把默认的值true,复制到了该线程的本地内存 Thread.sleep(3000); threadVolatileDemo.setRuning(false); //主线程先从共享内存中复制一份到本地内存,修改为false后会返回给共享内存,但是子线程一直在运行中,没有从共享内存中取最新值过去,所以导致了子线程中的值一直为true
System.out.println("flag 已经设置成false"); Thread.sleep(1000); System.out.println(threadVolatileDemo.flag); } }
运行结果:
已经将结果设置为fasle为什么?还一直在运行呢。
原因:线程之间是不可见的,读取的是副本,没有及时读取到主内存结果。
解决办法使用Volatile关键字将解决线程之间可见性, 强制线程每次读取该值的时候都去“主内存”中取值
第四节 AtomicInteger使用
public class VolatileNoAtomic extends Thread { static int count = 0; private static AtomicInteger atomicInteger = new AtomicInteger(0); @Override public void run() { for (int i = 0; i < 1000; i++) { //等同于i++ atomicInteger.incrementAndGet(); } System.out.println(count); } public static void main(String[] args) { // 初始化10个线程 VolatileNoAtomic[] volatileNoAtomic = new VolatileNoAtomic[10]; for (int i = 0; i < 10; i++) { // 创建 volatileNoAtomic[i] = new VolatileNoAtomic(); } for (int i = 0; i < volatileNoAtomic.length; i++) { volatileNoAtomic[i].start(); } } }
volatile和synchronize区别?
仅靠volatile不能保证线程的安全性。(原子性)
①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。
线程安全性包括两个方面,①可见性。②原子性。
从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。
第五节 ThreadLocal
ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal的接口方法
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
- void set(Object value)设置当前线程的线程局部变量的值。
- public Object get()该方法返回当前线程所对应的线程局部变量。
- public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null
class Res { // 生成序列号共享变量 public static Integer count = 0; public static ThreadLocal
threadLocal = new ThreadLocal () { protected Integer initialValue() { return 0; }; }; public Integer getNum() { int count = threadLocal.get() + 1; threadLocal.set(count); return count; } } public class ThreadLocaDemo2 extends Thread { private Res res; public ThreadLocaDemo2(Res res) { this.res = res; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "---" + "i---" + i + "--num:" + res.getNum()); } } public static void main(String[] args) { Res res = new Res(); ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res); ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res); ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res); threadLocaDemo1.start(); threadLocaDemo2.start(); threadLocaDemo3.start(); } } 实现原理:
-
ThreadLoca通过map集合
Map.put(“当前线程”,值);
第六节 线程池
Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newCachedThreadPool:
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int index = i; // try { // Thread.sleep(index * 1000); // } catch (InterruptedException e) { // e.printStackTrace(); // } cachedThreadPool.execute(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + "---" + index); } }); }
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
// 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待 final ExecutorService newCachedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int index = i; newCachedThreadPool.execute(new Runnable() { public void run() { try { Thread.sleep(1000); } catch (Exception e) { // TODO: handle exception } System.out.println("i:" + index); } }); }
总结:因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
newScheduledThreadPool
// 创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下: ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5); newScheduledThreadPool.schedule(new Runnable() { public void run() { System.out.println("delay 3 seconds"); } }, 3, TimeUnit.SECONDS);
newSingleThreadExecutor
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int index = i; newSingleThreadExecutor.execute(new Runnable() { @Override public void run() { System.out.println("index:" + index); try { Thread.sleep(200); } catch (Exception e) { // TODO: handle exception } } }); }
0005 数据交换格式和SpringIOC底层实现
Json(数据格式) Jsonp(跨域)
移动端(安卓、IOS)通讯方式采用http协议+JSON格式 走restful风格。
很多互联网项目都采用Http协议+JSON
因为xml比较重WebService服务采用http+xml格式 银行项目使用比较多
常用json解析框架
fastjson(阿里)、gson(谷歌)、jackson(SpringMVC自带)
使用fastjson解析json
第二节 java反射
什么是Java反射机制?
在运行期间动态获取这个类的所有信息
应用场景?
Jdbc加载驱动
Spring IOC
框架
反射机制获取类的三种方法:
//第一种方式: Classc1 = Class.forName("Employee"); //第二种方式: //java中每个类型都有class 属性. Classc2 = Employee.class; //第三种方式: //java语言中任何一个java对象都有getClass 方法 Employeee = new Employee(); Classc3 = e.getClass(); //c3是运行时类 (e的运行时类是Employee)
第七节 SpringIOC概述
什么是SpringIOC底层实现原理?
1.读取bean的XML配置文件
2.使用beanId查找bean配置,并获取配置文件中class地址。
3.使用Java反射技术实例化对象
4.获取属性配置,使用反射技术进行赋值。
public class ClassPathXmlApplicationContext { private String xmlPath; /** * * @param xmlPath * spring xml 配置路径 */ public ClassPathXmlApplicationContext(String xmlPath) { this.xmlPath = xmlPath; } public Object getBean(String beanId) throws Exception { // 解析xml器 SAXReader saxReader = new SAXReader(); Document read = null; try { // 从项目根目录路径下 读取 read = saxReader.read(this.getClass().getClassLoader().getResourceAsStream(xmlPath)); } catch (Exception e) { e.printStackTrace(); } if (read == null) { return null; } // 获取根节点资源 Element root = read.getRootElement(); Listelements = root.elements(); if (elements.size() <= 0) { return null; } Object oj = null; for (Element element : elements) { String id = element.attributeValue("id"); if (StringUtils.isEmpty(id)) { return null; } if (!id.equals(beanId)) { continue; // throw new Exception("使用beanId:" + beanId + ",未找到该bean"); } // 获取实体bean class地址 String beanClass = element.attributeValue("class"); // 使用反射实例化bean Class> forNameClass = Class.forName(beanClass); oj = forNameClass.newInstance(); // 获取子类对象 List attributes = element.elements(); if (attributes.size() <= 0) { return null; } for (Element et : attributes) { // 使用反射技术为方法赋值 String name = et.attributeValue("name"); String value = et.attributeValue("value"); Field field = forNameClass.getDeclaredField(name); field.setAccessible(true); field.set(oj, value); } } return oj; // 1.使用beanId查找配置文件中的bean。 // 2.获取对应bean中的classpath配置 // 3.使用java反射机制实体化对象 } public static void main(String[] args) throws Exception { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext( "applicationContext.xml"); User bean = (User) applicationContext.getBean("user2"); System.out.println("使用反射获取bean" + bean.getUserId() + "---" + bean.getUserName()); } }
0006 自定义注解与设计模式
第一节 注解概述与内置注解
什么是注解?
Jdk1.5新增新技术,注解。很多框架为了简化代码,都会提供有些注解。可以理解为插件,是代码级别的插件,在类的方法上写:@XXX,就是在代码上插入了一个插件。
注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。
注解分类:内置注解(也成为元注解 jdk 自带注解)、自定义注解(Spring框架)
内置注解:
(1) @SuppressWarnings 在程序前面加上可以在javac编译中去除警告--阶段是SOURCE
(2) @Deprecated 带有标记的包,方法,字段说明其过时----阶段是SOURCE
(3)@Overricle 打上这个标记说明该方法是将父类的方法重写--阶段是SOURCE
第二节 自定义注解
@Target(value = { ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface OneAnnotation { int beanId() default 0; String className() default ""; String[]arrays(); } 使用: @OneAnnotation(beanId = 123, className = "className", arrays = { "111", "222" }) public void add() { }
第三节 实现ORM框架映射
@Retention(RetentionPolicy.RUNTIME) public @interface SetProperty { /** * * @methodDesc: 功能描述:(字段名称) * @author: 余胜军 * @param: @return * @createTime:2017年8月27日 上午12:14:02 * @returnType:@return String * @copyright:上海每特教育科技有限公司 */ String name(); /** * * @methodDesc: 功能描述:(长度) * @author: 余胜军 * @param: @return * @createTime:2017年8月27日 上午12:14:25 * @returnType:@return int * @copyright:上海每特教育科技有限公司 */ int leng(); } public class Main { public static void main(String[] args) throws ClassNotFoundException { // 1.反射class Class> classForName = Class.forName("com.entity.Sudent"); // 2.获取表名称注解F SetTable setTable = classForName.getAnnotation(SetTable.class); // 3.获取所有的成员属性 Field[] declaredFields = classForName.getDeclaredFields(); StringBuffer sf = new StringBuffer(); sf.append(" select "); String fromName = setTable.value(); for (int i = 0; i < declaredFields.length; i++) { Field field = declaredFields[i]; // 4.属性字段 SetProperty sb = field.getAnnotation(SetProperty.class); sf.append(" " + sb.name() + " "); if (i == declaredFields.length - 1) { sf.append(" from "); } else { sf.append(" , "); } } sf.append(" " + fromName); System.out.println(sf.toString()); } }
第四节 常用设计模式
单例模式:保证一个对象在JVM中只能有一个实例,常见单例 懒汉式 饿汉式
懒汉式:需要的时候才去实例化,线程不安全
饿汉式:当class文件被加载的时候,初始化,天生线程安全
懒汉式:
public classSingleton{
private static Singleton singleton;
//构造器私有化,不让外界初始化
private Singleton(){
}
//由于线程不安全,则加上
public static synchronized Singleton getSingleton(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
饿汉式:
public class Singleton{
//在类加载时就初始化了,天生线程安全
private static final Singleton singleton=new Singleton();
private Singleton(){
}
public static Singleton getSingleton(){
return singleton;
}
}
工厂模式:
public interface Car { public void run(); } public class AoDi implements Car { @Override public void run() { System.out.println("奥迪...."); } } public class BenChi implements Car { @Override public void run() { System.out.println("奔驰...."); } } public class CarFactory { static public Car createCar(String carName) { Car car = null; if (carName.equals("奥迪")) { car = new AoDi(); } else if (carName.equals("奔驰")) { car = new BenChi(); } return car; } public static void main(String[] args) { Car car1 = CarFactory.createCar("奥迪"); Car car2 = CarFactory.createCar("奔驰"); car1.run(); car2.run(); } }
代理模式:
静态代理:
public class XiaoMing implements Hose { @Override public void mai() { System.out.println("我是小明,我要买房啦!!!!haha "); } } class Proxy implements Hose { private XiaoMing xiaoMing; public Proxy(XiaoMing xiaoMing) { this.xiaoMing = xiaoMing; } public void mai() { System.out.println("我是中介 看你买房开始啦!"); xiaoMing.mai(); System.out.println("我是中介 看你买房结束啦!"); } public static void main(String[] args) { Hose proxy = new Proxy(new XiaoMing()); proxy.mai(); } }
jdk动态代理:jdk使用反射原理生成代理类
public interface Hose { public void mai(); } public class XiaoMing implements Hose { @Override public void mai() { System.out.println("我是小明,我要买房啦!!!!haha "); } } public class JDKProxy implements InvocationHandler { private Object tarjet; public JDKProxy(Object tarjet) { this.tarjet = tarjet; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("我是房产中介.....开始监听你买房啦!"); Object oj = method.invoke(tarjet, args); System.out.println("我是房产中介.....结束监听你买房啦!"); return oj; } } class Test222 { public static void main(String[] args) { XiaoMing xiaoMing = new XiaoMing(); JDKProxy jdkProxy = new JDKProxy(xiaoMing); Hose hose=(Hose) Proxy.newProxyInstance(xiaoMing.getClass().getClassLoader(), xiaoMing.getClass().getInterfaces(), jdkProxy); hose.mai(); } }
CGLIB代理:
import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class Cglib implements MethodInterceptor { @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("我是买房中介 , 开始监听你买房了...."); Object invokeSuper = methodProxy.invokeSuper(o, args); System.out.println("我是买房中介 , 开结束你买房了...."); return invokeSuper; } } class Test22222 { public static void main(String[] args) { Cglib cglib = new Cglib(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(XiaoMing.class); enhancer.setCallback(cglib); Hose hose = (Hose) enhancer.create(); hose.mai(); } }
jdk动态代理是由Java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。
注:asm其实就是java字节码控制.
总结: 代理分两种,一种是静态代理,一种是动态代理
其中动态代理分为jdk动态代理和Cglib动态代理
jdk动态代理底层是用反射实现,Cglib动态代理使用Asm框架
SpringAop底层实现是用Cglib动态代理
0007 Java网络编程
第一节 Socket
1.网络模型
2.TCP协议与UDP协议区别
tcp: a、建议连接,形成传输数据的通道.
b、在连接中进行大数据量传输,以字节流方式
c 通过三次握手完成连接,是可靠协议
d 必须建立连接,效率会稍低
udp: a、是面向无连接, 将数据及源的封装成数据包中,不需要建立建立连接
b、每个数据报的大小在限制64k内
c、因无连接,是不可靠协议
d、不需要建立连接,速度快
3.Http底层实现原理
网络模型图
TCP三次握手过程:
在TCP/IP协议中,TCP协议采用三次握手建立一个连接。
第一次握手:建立连接时,客户端发送SYN包(SYN=J)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到SYN包,必须确认客户的SYN(ACK=J+1),同时自己也发送一个SYN包(SYN=K),即SYN+ACK包,此时服务器V状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=K+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据,
四次分手:
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。
1.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在 一个报文里来发送。
但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以 未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报 文和FIN报文多数情况下都是分开发送的.
2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
0010 深入理解Servlet
1.Servlet生命周期
构造方法: 创建servlet对象的时候调用。默认情况下,第一次访问servlet的时候创建servlet对象 只调用1次。证明servlet对象在tomcat是单实例的。
init方法: 创建完servlet对象的时候调用。只调用1次。
service方法: 每次发出请求时调用。调用n次。
destroy方法: 销毁servlet对象的时候调用。停止服务器或者重新部署web应用时销毁servlet对象。
只调用1次。
Servlet默认是单例,在请求访问时才创建,属于懒汉式单例
Servle生命周期
Tomtcat内部代码运行: 1)通过映射找到到servlet-class的内容,字符串: com.itmayiedu.a_servlet.FirstServlet 2)通过反射构造FirstServlet对象 2.1 得到字节码对象 Class clazz = class.forName("com.itmayiedu.a_servlet.FirstServlet"); 2.2 调用无参数的构造方法来构造对象 Object obj = clazz.newInstance(); ---1.servlet的构造方法被调用 3)创建ServletConfig对象,通过反射调用init方法 3.1 得到方法对象 Method m = clazz.getDeclareMethod("init",ServletConfig.class); 3.2 调用方法 m.invoke(obj,config); --2.servlet的init方法被调用 4)创建request,response对象,通过反射调用service方法 4.1 得到方法对象 Methodm m =clazz.getDeclareMethod("service",HttpServletRequest.class,HttpServletResponse.class); 4.2 调用方法 m.invoke(obj,request,response); --3.servlet的service方法被调用 5)当tomcat服务器停止或web应用重新部署,通过反射调用destroy方法 5.1 得到方法对象 Method m = clazz.getDeclareMethod("destroy",null); 5.2 调用方法 m.invoke(obj,null); --4.servlet的destroy方法被调用
如果init方法初始化东西过多,第一次访问会很慢,所以需要将Servlet在启动时马上初始化:
、
Servlet 多线程安全问题
线程安全代码:
package com.servlet; import java.io.IOException; import java.util.Date; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ServetlDemo4 extends HttpServlet { private int i = 1; @Override public void init() throws ServletException { System.out.println("ServetlDemo4...init()"); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置编码格式 // resp.setContentType("text/html;charset=utf-8"); resp.setCharacterEncoding("utf-8");// 内容编码,防止出现中文乱码 resp.setContentType("text/html;charset=utf-8"); synchronized (ServetlDemo4.class) { // 向浏览器输出内容 resp.getWriter().write("这是第" + i + "次访问..."); try { Thread.sleep(5000); } catch (Exception e) { // TODO: handle exception } i++; } } @Override public void destroy() { System.out.println("ServetlDemo4...destroy()"); } }