1. 进程和线程
1.1 进程
几乎所有的操作系统都支持进程的概念,所有运行中的任务通常对应一个进程(Process)。
- 当一个程序进入内存运行时,即变成一个进程。
进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。
独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
1.2 线程
大部分操作系统都支持多进程并发运行,现代的操作系统几乎都支持同时运行多个任务。
多线程则扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。
线程(Thread)也被称作轻量级进程(Lightweight Process),线程是进程的执行单元。
- 就像进程在操作系统中的地位一样,线程在程序中是独立的、并发的执行流。
当进程被初始化后,主线程就被创建了。
对于绝大多数的应用程序来说,通常仅要求有一个主线程,但也可以在该进程内创建多条顺序执行流,这些顺序执行流就是线程,每个线程也是互相独立的。
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。
线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。
因为多个线程共享父进程里的全部资源,因此编程更加方便;但必须更加小心,因为需要确保线程不会妨碍同一进程里的其他线程线程可以完成一定的任务,可以与其他线程共享父进程中的共享变量及部分环境,相互之间协同来完成进程所要完成的任务。
简单来说:
- 进程:进程是系统进行资源分配和调度的基本单位,可以将进程理解为一个正在执行的程序。
- 线程:线程是程序执行的==最小单位==,一个进程可由一个或多个线程组成。
总结:操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。
1.3 Java中实现多线程的方式
Java语言提主要供了两种实现线程的方式:
- 继承Thread类创建线程类
- 实现Runnable接口创建线程类
1.3.1 继承Thread类创建线程类
实现步骤:
- ①定义类继承Thread类,并重写Thread类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。因此把run()方法称为线程执行体。
- ②创建Thread子类的实例,即创建了线程对象。
- ③调用线程对象的start()方法来启动该线程。
1.3.2 实现Runnable接口创建线程类
实现步骤:
- 定义类实现Runnable接口,并重写Runnable接口的run()方法,该run()方法的方法体就代表了线程需要完成的任务。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用该Thread对象的
start()方法来启动该线程。
1.4 Java线程状态
线程的五种状态:
- 1)
新建状态(New):线程对象实例化后就进入了新建状态。 - 2)
就绪状态(Runnable):线程对象实例化后,其他线程调用了该对象的start()方法,虚拟机便会启动该线程,处于就绪状态的线程随时可能被调度执行。 - 3)
运行状态(Running):线程获得了时间片,开始执行。只能从就绪状态进入运行状态。 - 4)
阻塞状态(Blocked):线程因为某个原因暂停执行,并让出CPU的使用权后便进入了阻塞状态。- 等待阻塞:调用运行线程的wait()方法,虚拟机会把该线程放入等待池。
- 同步阻塞:运行线程获取对象的同步锁时,该锁已被其他线程获得,虚拟机会把该线程放入锁定池。
- 其他线程:调用运行线程的sleep()方法或join()方法,或线程发出I/O请求时,进入阻塞状态。
- 5)
结束状态(Dead):线程正常执行完或异常退出时,进入了结束状态。
1.5 Thread类常用基本方法
| 返类型 | 构造器/方法 | 说明 |
|---|---|---|
| Thread() | 分配新的 Thread 对象。 | |
| Thread(Runnable target) | 分配新的 Thread 对象。 | |
| Thread(Runnable target, String name) | 分配新的 Thread 对象。 | |
| static Thread | currentThread() | 返回对当前正在执行的线程对象的引用。 |
| static void | sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。 |
| static void | sleep(long millis, int nanos) | 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)。 |
| static void | yield() | 暂停当前正在执行的线程对象,并执行其他线程。 |
| void | start() | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
| long | getId() | 返回该线程的标识符。 |
| String | getName() | 返回该线程的名称。 |
| int | getPriority() | 返回线程的优先级。 |
| boolean | isAlive() | 测试线程是否处于活动状态。 |
| void | join() | 等待该线程终止。 |
| void | join(long millis) | 等待该线程终止的时间最长为 millis 毫秒。 |
| void | join(long millis, int nanos) | 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒 |
| void | setName(String name) | 改变线程名称,使之与参数 name 相同。 |
| void | setPriority(int newPriority) | 更改线程的优先级。参数范围[1,10] |
1.6 线程安全问题
许多在单线程情况下的代码放到多线程环境下容易出现线程安全问题。
1.6.1 同步代码块
为了解决线程安全问题,Java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。
同步代码块的语法格式如下:
synchronized (obj) {
//同步代码块
}
obj叫做同步监视器(锁对象),任何线程进入下面同步代码块之前必须先获得对obj的锁;其他线程无法获得锁,也就执行同步代码块。
- 这种做法符合:“加锁-修改-释放锁”的逻辑。锁对象可以是任意对象,但必须保证是同一对象
任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后该线程会释放对该同步监视器的锁定。
1.6.2 同步方法
除了同步代码块,Java的多线程安全支持还提供了同步方法
- 同步方法也是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。
对于synchronized修饰的实例方法(非static方法),无须显式指定同步监视器,同步方法的同步监视器就是this,也就是调用该方法的对象。
1.6.3 同步锁(Lock)
从Java5开始,Java提供了一种功能更强大的线程同步机制——通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。
Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性。
Lock是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
在实现线程安全的控制中,比较常用的是 ReentrantLock(可重入锁)。使用该Lock对象可以显式地加锁、释放锁。
2. 线程安全
2.1 死锁(资源分配)
当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。
一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
在系统中出现多个同步监视器的情况下很容易发生死锁。
2.2 wait()、notify/notifyAll()
- wait()、notify/notifyAll()方法是Object的final方法,无法被重写。
- wait()使当前线程阻塞,前提是必须先获得锁,一般配合synchronized关键字使用,即,一般在synchronized同步代码块里使用wait()、notify/notifyAll()方法。
- 由于wait()、notify/notifyAll()在synchronized代码块执行,说明当前线程一定是获取了锁的。
当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
只有当notify/notifyAll()被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized代码块的代码或是中途遇到wait(),再次释放锁。也就是说,notify/notifyAll()的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll()后立即退出临界区,以唤醒其他线程。 - wait()需要被try...catch包围。
- notify和wait的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。
- notify和notifyAll的区别
- notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
- notifyAll会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll方法。
3. 网络编程的基础知识
3.1 计算机网络
计算机网络是指两台或更多的计算机组成的网络,在同一个网络中,任意两台计算机都可以直接通信,因为所有计算机都需要遵循同一种网络协议。
局域网LAN(LocalAreaNetwork):一般指覆盖范围在10公里以内,一座楼房或一个单位内部的网络。由于传输距离直接影响传输速度,因此,局域网内的通信,由于传输于距离短,传输的速率一般都比较高。目前,局域网的传输速率一般可达到10MB/S和100MB/S,高速局域网传输速率可达到1000MB/S。
广域网WAN(WideAreaNetwork):是指远距离的、大范围的计算机网络。跨地区、跨城市、跨国家的网络都是广域网。由于广域的覆盖范围广,联网的计算机多,因此广域网上的信息量非常大,共享的信息资源很丰富。
INTERNET是全球最大的广域网,它覆盖的范围遍布全世界。- 城域网MAN(MetropolitanAreaNetwork):其覆盖范围在局域网和广域网之间。一般指覆盖范围为一个城市的网
TCP/IP协议泛指互联网协议,其中最重要的两个协议是TCP协议和IP协议。
只有使用TCP/IP协议的计算机才能够联入互联网,使用其他网络协议(例如NetBIOS、AppleTalk协议等)是无法联入互联网的。
3.2 MAC地址、IP地址
MAC地址(物理地址),用于表示网卡的唯一编号。
一般是在网卡出厂时由厂家写入硬件的唯一地址。
在网络中,一个IP地址用于唯一标识一个网络接口(Network Interface)。
一台联入互联网的计算机肯定有一个IP地址,但也可能有多个IP地址。
IP地址分为IPv4和IPv6两种。
IPv4采用32位地址,类似192.168.1.11。
- IPv4地址总共有2^32个(大约42亿),目前已经基本用尽。
IPv6采用128位地址,类似2001:0DA8:100A:0000:0000:1020:F2F3:1428。
- IPv6地址则总共有2^128个。IPv6的地址是根本用不完的,可以宣传可以为地球上的每一粒沙子分配一个IP。
特殊IP地址:127.0.0.1 本机地址
如果一台计算机只有一个网卡,并且接入了网络,那么,它有一个本机地址127.0.0.1,还有一个IP地址,例如192.168.1.11,可以通过这个IP地址接入局域网络。
如果一台计算机有两块网卡,那么除了本机地址,它可以有两个IP地址,可以==分别接入两个网络。比如服务器==
4. 序列化和反序列化
return Json() --> json字符串--> ajax把Json字串串转为Json对象
请求 ajax发送Json对象 -->json字符串--> Controller.action(类 实例)进行了反序列化
- 目的:==把对象进行网络传输或存储==
对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。
- 其他程序一旦获得了这种二进制流(无论是从磁盘中获取的,还是通过网络获取的),都可以将这种二进制流恢复成原来的Java对象。
4.1 序列化的含义和意义:
序列化机制允许将实现序列化的Java对象转换成字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以备以后重新恢复成原来的对象。
序列化机制使得对象可以脱离程序的运行而独立存在。
对象的序列化(Serialize)指将一个Java对象写入IO流中,
与此对应的是,==对象的反序列化(Deserialize)== 则指从IO流中恢复该Java对象。
如果需要让某个对象支持序列化机制,则必须让它的类是可序列化的(serializable)。
为了让某个类是可序列化的,该类必须实现如下两个接口之一:
- Serializable(常用)
- Externalizable
Java中很多类已经实现了Serializable,该接口是一个标记接口,实现该接口无须实现任何方法,它只是表明该类的实例是可序列化的。
所有可能在网络上传输的对象的类都应该是可序列化的,否则程序将会出现异常。
4.1 Properties类
- Properties 类是Java中操作配置文件的类
5. Optional 类
Java应用中最常见的bug就是空值异常。
在Java 8之前,Google Guava引入了 Optionals 类来解决 NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。
Java 8 将Optional加入了官方库。
Optional仅仅是一个容易存放T类型的值或者null的类。它提供了一些有用的接口来避免显式的null检查,可以参考Java 8官方文档了解更多细节。
| 修饰符 | 返回值类型 | 方法 | 说明 |
|---|---|---|---|
| public static | Optional | of(T value) | 创建一个容器,不能为空,否则报空指针异常 |
| public static | Optional | ofNullable(T value) | 创建一个容器,可以包含null |
| public | T | get() | 取容器里面的值,如果为空会抛出 NoSuchElementException |
| public | boolean | isPresent() | 判断容器中是否存在对象 |
| public | void | ifPresent(Consumer<? super T> consumer) | 如果容器中存在对象,则执行 consumer |
| public | Optional | filter(Predicate<? super T> predicate) | 筛选 |
| public | Optional | map(Function<? super T, ? extends U> mapper) | 取容器对象中的属性 |
| public | T | orElse(T other) | 如果对象存在,则输出,对象不存在,则返回 other |
| public | T | orElseGet(Supplier<? extends T> other) | 如果对象存在,则输出,对象不存在,则返回 other |
| public | T | orElseThrow(Supplier<? extends X> exceptionSupplier) | 若是对象不存在,则抛出异常 |
6. ❤️Java基础专栏 - 前篇回顾
- 认识Java,Java程序的生命周期,运行Java程序
- Java数据类型阐述、基本数据类型的占用和范围、二进制的讲述
- 8种基本数据类型的分析、数据类型转换规则、转义字符的列举
- 五种运算符的说明(&&、||、>=、>、<=、<)、4种控制语句(if、for、while、dowhile)、输入和输出说明
- 方法的概念、方法的调用、方法重载、构造方法的创建
- 全方面带你了解Java里的日期与时间内容,介绍 Calendar、GregorianCalendar、Date类
- 正则表达式的使用与常用类分享
- Stream流、文件File相关操作,IO的含义与运用
- Java异常捕捉,throws/throw、finally、try、catch关键字的含义与运用
- 抽象类、接口、内部的运用与作用分析,枚举类型的使用