装饰模式
动态的给一个对象添加额外的职责。在不改变原对象的前提下,额外的添加一些逻辑。Spring AOP就是这样发展来的。
将对象序列化与反序列化操作的流
OIS与OOS
ObjectInputStream
ObjectOutputStream
若我们的对象想被序列化,需要实现序列化接口
Serializable接口。表示其子类是可以序列化的。
序列化对象的作用:
1: 该对象保存着的数据需要一直使用,我们可以将其序列化后写入硬盘做长久保存,这样,下次程序启动后我们还可以将其读取回来继续使用。
2: 将对象进行网络传输。
JDK中大部分类都实现了序列化接口。例如ArrayList都实现了。也就是说他们都可以被序列化。
反序列化后的对象,和原对象equals比较为true.但是他们不是一个对象,且若属性也是引用类型,那么这些属性也都不是同一个对象,但equals比较也为true。
深度复制对象需要先将对象序列化再反序列化。
这就需要我们使用ObjectOutputStream和ObjectInputStream
但这两个流需要将数据写出和读入,我们不希望在复制过程中先将对象写到磁盘上再读回来,因为这样太慢!
我们既要使用oos和ois又要在内存中完成序列化与反序列化操作。
ByteArrayInputStream ByteArrayOutputStream
这两个流也有明确的来源和去向,这两个流内部维护着一个字节数组。
我们通过ByteArrayOutputStream写出的字节,就是写到了其内部维护的字节数组上了。
通过ByteArrayInputStream读取数据实际上就是将其内部的字节数组中的字节读取出来。
多线程
多线程:使计算机的操作系统可以在同一时刻执行多个任务
操作系统级别的任务就是一个应用程序。每个任务又成为一个进程。
对于一个程序而言,也可能同时运行多个任务,那么每个任务成为一个线程。
并发:进程或线程是并发运行的。OS将时间分成若干段,并分配给每个进程或线程,得到时间片段的进程或线程将得到cpu的执行。所以,微观上进程或线程是走走停停的。宏观上是同时在执行。这种现象称为并发。
线程运行机制:
当我们调用线程的 start()方法时,该 线程会向线程调度注册当前线程。只有注册了,才有机会被分配时间片进行并发运行。
不要去调用run方法。若直接调用run()方法那么就不是并发运行了,而是变成同步运行了。有先后顺序的执行。
时间片段是不均匀的。每个线程分配的时间不是相等的。线程调度机制尽可能均匀的将时间片段分配给不同线程,让他们都有机会得到cpu的运行。对于线程调度机制而言,我们的程序对具体将时间片段分配给那个线程是不可控的。
葛大爷拍戏是典型的多线程并发运
行。
葛大爷是cpu。
秘书是线程调度机制。
赵氏孤儿,非诚勿扰2,让子弹飞这三个剧组就是三个需要并发的线程。
档期就是时间片段。
内部类
静态内部类
public class A{
public int a;
public static class B{
public void say(){
a++;//不行
}
}
}
静态内部类不能直接引用外部类的非静态属性和方法。
实例化静态内部类
A.B b = new A.B();
非静态内部类
public class A{
private int a = 0;
public class B{
public void say(){
a++;//可以
}
}
}
非静态内部类中可以引用外部类的属性和方法
A a = new A();
B b = a.new B();
线程的启动和关闭
start(): 线程的启动方法。若想让run()方法中的代码并发执行,我们需要调用start方法
stop(): 关闭线程的方法。该方法不建议使用,具有 不安全性。停止线程的正确方式应该是让 run方法正常执行完毕。
线程运行过程中若出现类未捕获的异常,该线程会立刻终止。但是不会影响当前进程中其他线程的工作
若当前进程中所有线程都终止了,那么进程终止。
当我们的程序启动时,操作系统分配一个进程来运行我们的程序,而该进程会创建一个线程来调用我们的main方法。
创建线程的另一种方式。
将线程与线程体分开
所谓线程体就是线程要并发执行的逻辑。
最好的模式应该是线程只关心并发操作,不关心具体并发做的事情。我们应将线程体与线程本身解耦。
解耦: 解除依赖关系,自己关心自己的事情。不决定自己不该关心的内容。
单独定义线程体
Runnable接口
该接口用于描述可执行的逻辑,一般描述的就是线程体。
线程进入死亡状态后,不要再调用start()方法。
与线程声明周期相关的方法
static void yield():
主动放弃当次时间片段,进入runnable状态,等待下次分配时间片。主动离开running状态
static void sleep(long time):
从running状态主动进入 block状态
阻塞time毫秒后自动回到runnable状态
若在阻塞过程中被打断睡眠阻塞会引发异常InterruptException
void interrupt():
中断线程
线程的优先级
线程优先级高的线程,被分配时间片段的几率高,换句话说被cpu执行的次数多。
注意,这不是绝对的。但是大多数情况下是这样的。毕竟线程调度分配时间片段是不可控的。
守护线程
又叫后台线程
守护线程的特点,当进程中所有前台线程都终止后,守护线程强制终。当进程中只剩下后台线程时,进程终止。
后台线程是在线程启动前通过方法设置的。
setDaemon(boolean on)
当参数为true时,该线程为后台线程。
设置 后台线程必须在线程调用start()方法之前进行!
线程安全问题
当多线程共享同一数据时,就可能引发线程安全问题。
synchronized关键字
synchronized可以 修饰方法,也可以独立以 代码块的形式存在
若修饰方法,那么该方法就是同步方法,多线程不能同时访问该方法。
synchronized若修饰方法,那么当一个线程进入该方法后,会对当前方法所属的对象加锁。那么其他线程在访问该对象的这个方法时,发现该对象被锁上了,那么就在方法外等待,直到对象上的锁被释放掉为止。
StringBuffer和StringBuilder
用法一样。 StringBuffer是线程安全的。
List集合
Vector是线程安全的集合
ArrayList不是线程安全的
Map
HashMap不是线程安全的
HashTable是线程安全的
Collections 有获取 线程安全的集合的方法
Collections.synchronizedList()
获取一个线程安全的List集合
List list = Collections.synchronizedList()
获取线程安全的Map
Collections.synchronizedMap()
获取线程安全的Set
Collections.synchronizedSet()
wait 与 notify
这两个方法不是在Thread中定义的。而是在Object中定义的。
当线程A调用了B对象wait方法,那么该线程就在B对象上等待。A线程进入wait阻塞。
当线程C调用了B对象wait方法,那么该线程也在B对象上等待。
当B对象调用了notify方法,那么A或B随机一个进入Runnable状态开始运行,另一个仍然处于wait阻塞。
notifyAll方法可以让在当前对象上等待的所有线程进入runnable状态。
java网络编程(Socket编程 )
Socket 套接字:封装着如端口号,ip地址,计算机名等信息的类。
通过Socket我们可以与远程计算机通信。
网络通信模型
C/S:client/server
客户端对服务器端
客户端通常运行在用户的计算机上,客户端是一个特有的程序,实现特有的功能,用于连接服务器端进行通信。
谁发起连接谁是用户。
服务器端程序通常是等待客户端连接,提供功能服务并与之通信。
通信协议: 计算机通信的实质就是相互收发字节。那么按照一定的格式收发字节就是通信协议。
B/S结构:
浏览器端对服务器端。
固定了客户端和通信协议的C/S结构
线程池
频繁的创建线程和销毁线程是非常消耗资源和性能的。
创建一些空的线程。将他们保存起来,当有任务需要并发执行时,我们取出一个空闲的线程来运行这个任务,当任务运行完毕后,在将线程回收,等待下次分配任务。
这样,自始至终我们都只创建了开始创建的那些线程,并可以重复利用来节省性能开销。
ExecutorService
线程池实现
通过Executors类的静态方法创建几个不同实现的线程池
Executors.newCachedThreadPool()
创建一个线程池,当有任务的时候会检查线程池中是否有空线程,若有就是用它,若没有就创建新的线程。
Executors.newFixedThreadPool(int threads)
创建一个具有 固定大小的线程池。
Executors.newScheduledThreadPool(int threads)
创建一个线程池,他可以安排任务在指定 延时后并发执行或定期执行
Executors.newSingleThreadExceutor()
创建一个 单线程的线程池。该池中维护一个任务队列,以单线程方式执行队列中的每一个任务。
双端队列
内部由两个队列实现,他们交替进行存取工作。这样至少可以保证2个线程可以同时进行存取操作。
但是对于两个线程同时进行存或者同时进行取操作,还是要求同步的。
但是比单队列实现线程安全还是要快的。
BlockingDeque双端队列
实现:
ArrayBlockingDeque 该类实现的构造方法要求我们传入一个整数,代表当前队列的长度。所以这个是一个固定大小的双端队列,存取原则为FIFO 先入先出原则
当元素调用offer存入队列时,若队列已满,那么可以设置延时等待,当超过了延时等待会返回存入失败。
LinkedBlockingDeque 变长双端队列。长度不定,随着元素数量而增大,最大可达Integer.MAX_VALUE
重载构造方法可以传入一个整数,使之变为一个定长的队列。存取原则FIFO first input first output
PriorityBlockingDeque
和 LinkedBlockingDeque 类似。但存取不是FIFO而是经过自然排序后获取
SynchronousQueue 特殊的双端队列,存取步骤有要求,必须存一次取一次,交替进行。