序列化和反序列化
序列化版本控制
serialVersionUID 是一个类的序列化版本号
如果该量没有定义 JDK会自动给予一个版本号
当需要序列化的类发生变化时,该序列化的版本号就会发生变化,反序列化就会失败
自定义该版本号,只要版本号不发生改变,即使类中的属性或者方法改变,该类的对象依旧可以反序列化
transient关键字
在可序列化的类中用transient修饰的属性无法再反序列化时获得,不能被序列化
序列化对象的属性
需要序列化的类需要实现serializable接口,让其可序列化,该类的属性也需要是可序列化的,当该类的属性也是类时,属性类也需要实现serializable接口
线程
程序运行阶段不同的运行路线
线程类 Thread
自定义线程
1.继承Thread
重写Thread类中的run方法,在run方法内定义线程要执行的任务
2.实现Runnable接口
重写run方法,实例化这个类
在实例化进程时,将这个类对象当作构造方法的参数。
实现接口并重写方法
实例化该类,并当作进程构造方法的参数
线程类常用方法
run() | t.run() 运行t的run方法,普通对象调用方法,属于main线程 执行重写的run方法,多个run运行时,顺序执行 |
start() | t.start() 开启线程,执行t的run方法,属于t线程,多个线程开启时,多个线程同时进行 |
sleep() | Thread.sleep(s) s是毫秒数,让运行到该行代码的线程休眠s毫秒,休眠后会自动启动线程 |
currentThread() | Thread.currentThread() 获取当前进程对象 |
yield() | Thread.yield() 作用是让出资源,让cpu重新分配。 防止一条线程长时间占用cpu资源,达到cpu资源合理分配的效果 sleep(0)也可以达到这样的效果 |
join() | t.join() 加入(插队),例如:在A线程中执行了B.join() B线程运行完毕后,A线程再运行 |
setPriority() | t.setPriority() 设置进程的优先级 优先级范围 1-10 默认是5 设置其他值报错 优先级越高,获取cpu资源的几率越大 |
中断线程
中断线程有三种方法
1.执行stop方法(不推荐)
2.调用interrupt()设置中断状态,这个线程不会中断,我们需要在线程内部判断,中断状态是否被设置,然后执行中断操作
t.isInterrupted() 判断进程t是否被设置中断状态
t.interrupt() 给进程t设置中断状态
判断中断状态
测试方法
3.自定义一个状态属性,在线程外部设置此属性,影响线程内部的运行
定义状态属性
测试方法
在方法三中,定义状态属性时使用volatile修饰
volatile:保证变量的内存可见性,禁止指令重排序
内存可见性:当A线程改变某个变量的值时,其他线程读取时是最新的值
并发编程的三大概念:原子性,有序性,可见性。
原子性:
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执 行。Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
有序性:
即程序执行的顺序按照代码的先后顺序执行。
可见性:
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
线程生命周期
新建:当一个Thread类或其子类的对象被声明并创建时,新的线程对象处于新建状态
就绪:处于新建状态的线程被star()后,将进入线程队列等待cpu时间片,此时它已具备了运行的条件,只是没分配到cpu资源
运行:当就绪的线程被调度并获得cpu资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被认为挂起或执行输入输出操作时,让出cpu并临时中止自己的执行,并进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性的中止或出现异常倒置导致结束
线程安全
多个线程操作一个对象,不会出现结果错乱的情况(确实)
StringBuilder是线程不安全的
为strB添加1000个0
开启两个进程,最后输出strB的长度
返回的长度
StringBuffer是线程安全的
为strB添加1000个0
开启两个进程,最后输出strB的长度
返回的长度
StringBuffer是线程安全的是因为它使用synchronized修饰
要做到线程安全,我们可以使用synchronized,对方法或者代码加锁,达到线程同步的效果
使用synchronized关键字修饰的方法或代码块,同一时间内,只能允许一个线程执行此代码
测试main方法
synchronized方法
synchronized代码块
锁对象
使用synchronized需要指定锁对象
synchronized修饰方法时 成员方法 this
静态方法 类的类对象--->obj.getClass() Easy.class
锁的分类
根据有无对象:悲观锁、乐观锁 悲观锁有锁对象 乐观锁没有锁对象
synchronized是悲观锁
乐观锁的实现方式
公平锁与非公平锁 公平锁就是先来后到
在java中大多数都是公平锁
可重入锁 在同步代码块中遇到相同的锁对象的同步代码块,不需要再获取锁对象的权限,直接进入执行
java里全是可重入锁
根据线程的状态不同:偏向锁,轻量级锁(自旋锁),重量级锁 状态都是相对的,因此该分类都是相对而言
乐观锁的实现方式
乐观锁的实现方式主要分为两种:CAS机制、版本号机制
CAS(Compare and Swap)
CAS操作包括三个操作数:
需要读写的内存位置(V)
进行比较的预期值(A)
拟写入的新值(B)
CAS操作逻辑如下:
如果内存位置V的值等于预期的A值,则将该位置更新为新值B,否则不进行任何操作。
许多CAS的操作是自旋的:
如果操作不成功,会一直重试,直到操作成功为止。
CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的
版本号机制
版本号机制的基本思路是在数据中增加一个字段version,表示该数据的版本号,每当数据被修改,版本号加1。当某个线程查询数据时,将该数据的版本号一起查出来;当该线程更新数据时,判断当前版本号与之前读取的版本号是否一致,如果一致才进行操作。
BIO、NIO和AIO
IO模型主要分类:
同步(synchronous)IO、异步(asynchronous)IO、阻塞(blocking)IO、非阻塞(non-blocking)IO
同步阻塞(blocking-IO)简称BIO
数据的读取写入必须阻塞在一个线程内等待其完成。
这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
同步非阻塞non-blocking-IO)简称NIO
同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
异步非阻塞(asynchronous-non-blocking-IO)简称AIO
异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。
同步和异步
同步
发送一个请求,等待返回,再发送下一个请求,同步可以避免出现死锁,脏读的发生。
异步
发送一个请求,不等待返回,随时可以再发送下一个请求,可以提高效率,保证并发。
阻塞和非阻塞
阻塞
传统的IO流都是阻塞式的。也就是说,当一个线程调用read()或者write()方法时,该线程将被阻塞,直到有一些数据读读取或者被写入,在此期间,该线程不能执行其他任何任务。在完成网络通信进行IO操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量的客户端时,性能急剧下降。
非阻塞
JavaNIO是非阻塞式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程会去执行其他任务。线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入和输出通道。因此NIO可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。