JavaSE之多线程+网络编程
1、线程与进程
进程
- 计算机内存中运行的每个独立的应用程序就是一个进程,每个进程都有一个独立的内存空间
线程
- 是进程中的一个执行路径,所有线程共享一个内存空间,线程之间可以自由切换,并发执行;一个进程至少有一个线程;
- 线程是进程的进一步划分,一个进程启动后,里面的若干条执行路径又可以划分成若干个线程。
2、线程调度
分时调度
- 所有的线程轮流使用CPU,平均分配每个线程的CPU使用时长;
抢占式调度
- java使用抢占式调度,每个线程可以设置优先级,优先级越高,线程优先执行的概率越大;优先级相同,则会随机执行一个;
- CPU使用抢占式调度模式在多个线程之间进行高速切换;对于CPU的一个核心来说,某个时刻只能执行一个线程,由于CPU在多线程之间的切换速度特别快,给人的感觉就像是在同一时刻执行。
- 多线程并不能提高程序的运行速度,但是能够提高程序运行的效率,让CPU的使用率更高。
3、同步与异步
同步:排队执行,效率低但是安全;
异步:同时执行,效率高但是数据不安全。
4、并发与并行
并发:指两个或多个事件在同一时间段内发生;
并行:指两个或多个事件在同一时刻发生(同时发生)。
5、实现多线程的方式
5.1、继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
// 这里的代码 就是一条新的执行路径
// 这个执行路径的触发方式不是调用run()方法,而是通过Thread对象的start()方法来启动任务
}
}
5.2、实现Runnable接口
开发中比较常用,相对于继承Thread有以下优点:
- 通过创建任务,给线程分配任务实现多线程,更适合多个线程执行同时执行相同任务的情况;
- 可以避免单继承带来的的局限性;
- 任务与线程分离,提高程序的健壮性;
- 线程池技术支持Runnable类型的任务,不支持Thread类型的任务。
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程的任务
}
}
5.3实现Callable接口
实现Callable接口创建的任务具有返回值;
// 实现Callable接口
class MyCallable implements Callable<V> {
@Override
public V call() throws Exception {
return v;
}
}
public static void main(String[] args) {
Callable<V> c = new MyCallable();
// 通过FutureTask包装器创建Thread线程
FutureTask<V> f = new FutureTask<>(c)
new Thread(f).start();
// get方法获取返回值
V j = f.get();
}
6、线程常用方法
6.1、静态方法
- currentThread( ):获取正在执行的当前线程;
- sleep( ):导致一个线程睡眠N毫秒,但不会释放当前线程的锁;
- yield( ):线程让步;
6.2、实例方法
- getName( ):返回当前线程名;
- setName( ):设置线程名;
- interrupt( ):中断此线程;
- isAlive( ):判断一个线程是否存活;
- join( ):等待该线程死亡;
- setDaemon( ):设置线程为守护线程;
- setPriority( ):设置一个线程的优先级;
- getPriority( ):获得一个线程的优先级;
6.3、Object类中有关线程的方法
- wait( ):线程等待,注意会释放对象的锁;
- notify( ):唤醒该线程;
- notifyAll( ):唤醒所有等待的线程。
7、线程阻塞
可以简单理解为比较消耗时间的操作,导致线程处于等待状态,例如:读取文件操作、接收用户输入等。
8、线程结束
线程在执行完run方法后,一般会自动结束,API中提供的stop是非线程安全的,不提倡使用;以下介绍两种线程结束的方式:
(1)使用退出标记
public class ThreadSafe extends Thread {
// volatile关键字保证同一时刻只能由一个线程来修改exit的值
public volatile boolean exit = false;
public void run() {
while (!exit){
//do something
}
}
}
(2)interrupt方法退出线程
Thread t = new Thread(new MyThreadRunnable());
t.start();
t.interrupt();
class MyThreadRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 通过添加中断标记,在该位置处编写代码自杀的代码,释放所有资源
e.printStackTrace();
return;
}
}
}
9、线程的生命周期
一个线程在被创建并启动以后,既不是一启动就进入执行状态、也不是一直处于执行状态;在线程的生命周期中,要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead) 5种状态;当线程启动之后,不可能一直占用CPU独自运行,CPU需要在多线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
9.1、新建状态(New)
使用new关键字创建一个线程,该线程处于新建状态,仅由JVM为其分配内存,并初始化其成员变量的值;
9.2、就绪状态(Runnable)
线程调用start方法后就处于就绪状态;
9.3、运行状态(Running)
处于就绪状态的线程获得了CPU,并且开始执行run方法,则为运行状态;
9.4、阻塞状态(Blocked)
线程让出CPU使用权,暂时停止运行;阻塞分为三种情况:
- 等待阻塞(o.wait—>等待队列)
- 同步阻塞(lock—>锁池)
- 其他阻塞(sleep/join)
9.5、线程死亡(Dead)
线程运行结束后即为死亡状态。
10、线程不安全的解决办法
10.1、同步代码块
private Object o = new Object();
synchronized (o){
// 代码体
}
10.2、同步方法
public synchronized boolean sale() {
// 非静态方法锁住的是this对象
// 静态方法锁住的是类名.class获取的字节码文件
return false;
}
10.3、显式锁 Lock 子类 ReentrantLock
以上两种同步代码块与同步方法都属于隐式锁;
// 参数设置为true则表示公平锁
private Lock l = new ReentrantLock(true);
// 锁的使用与打开
l.lock();
// 代码体
l.unlock();
11、公平锁与不公平锁
公平锁:先到先得,按照排队顺序执行;
不公平锁:隐式锁 与 显式锁都属于不公平锁,谁先抢到谁先执行。
12、死锁
线程死锁的发生与电脑的性能有关,性能越好越容易发生死锁;由于个人电脑配置较差,在测试时很难遇到死锁问题;
简单的例子就是线程A等待线程B执行完毕,线程B又在等待线程A执行完毕,陷入无线等待中的情况则为死锁;
死锁的避免方案:
从根源上解决,不要在任何有可能导致锁产生的锁里调用另一个方法让另一个锁产生;就是说这个方法 目前已经有一个锁了,就不要再去找其他有可能产生锁的方法;
13、四种线程池
13.1、缓存线程池
缓存线程池是长度无限制的,创建方法如下:
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
// 代码
}
});
// 可以有多个execute方法
13.2、定长线程池
定长线程池长度是指定的数值;
// 长度设置为2
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
}
});
13.3、单线程线程池
单线程线程池与定长线程池值设为1一样,只有一条执行路径;
ExecutorService service = Executors.newSingleThreadExecutor();
13.4、周期任务定长线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 周期执行任务:
* 参数1:定时执行的任务;
* 参数2:延时时长数字(第一次执行在什么时间之后);
* 参数3:周期时长数字(没隔多久执行一次);
* 参数4:时长数字的单位,TimeUnit的常量指定
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
}
}, 5, 1, TimeUnit.SECONDS);
14、Lambda表达式
java1.8引入,Lambda表达式属于函数式编程思想;
使用条件:某个接口只能有一个自己特有的方法;
// Lambda表达式
Thread t = new Thread(() -> {
System.out.println();
});
t.start();
15、计算机网络
分布在不同地域的计算机,通过硬件等网络设备使用通讯线路互相连接形成的一个网格系统;计算机网络可以很方便的进行信息的传递以及资源共享。
16、IP地址
IP地址:是计算机在网络中的唯一标识;
本机IP:127.0.0.1/localhost。
17、域名
域名简单的理解为IP地址的别名,可以更方便的访问网站,例如当用户输入域名后,计算机会访问域名解析商,然后得到ip地址,再进行访问。
18、端口号
端口号的范围:0~65535;
端口是计算机中程序的编号,一个程序可以有多个端口号;
设置端口号时,应避免使用01024之间的端口号,从1025往后使用,因为01024已经被一些知名软件和操作系统所占用。
19、通信协议
是计算机之间交流的标准;
是对数据的传输速率、传入接口、步骤控制、出错控制等指定的一套标准;
常用的通信协议有:
1、http协议:超文本传输协议,端口号80;
2、https协议:完全的超文本传输协议,端口号443;
3、ftp协议:文件传输协议,端口号21;
4、TCP协议:传输控制协议;
5、UDP协议:数据报协议。
20、网络编程程序的分类
B/S程序:浏览器/服务器程序;
C/S程序:客户端/服务器程序。
21、TCP协议-OSI网络模型
该网络模型指的是从一台计算机的软件中,将数据发送到另一台计算机软件的过程。
七层网络模型:应用层/表现层/会话层/传输层/网络层/数据链路层/物理层。
22、三次握手与四次挥手
22.1、三次握手
TCP协议客户端与服务器连接时, 存在三次握手操作, 确保消息能准确无误的发送。
第一次握手,客户端向服务器发出连接请求,等待服务器确认;
第二次握手,服务器向客户端返回一个响应,通知客户端收到了连接请求;
第三次握手,客户端再次向服务器发送确认信息,确认连接。
22.2、四次挥手
断开连接是时 , 存在四次挥手操作。
第一次挥手,客户端向服务器发送断开连接请求,关闭数据传送;
第二次挥手,服务器收到请求,返回ACK响应,确认是否断开;
第三次挥手,服务器关闭与客户端的连接,返回一个FIN给客户端;
第四次挥手,客户端返回ACK报文确认。
23、TCP协议的C/S程序
23.1、搭建服务器
使用ServerSocket 类创建服务器,创建完毕后,会绑定一个端口号。
然后此服务器可以等待客户端连接 。
每连接一个客户端 ,服务器就会得到一个新的Socket对象,用于跟客户端进行通信 。
以下代码结合多线程搭建服务器:
// 1、搭建服务器
ServerSocket server = new ServerSocket(55565);
// 2、等待客户端连接
while (true) {
Socket socket = server.accept();
new Thread() {
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
23.2、客户端搭建
使用Socket类创建客户端对象;
Socket是两台计算机之间通信的端点 ,是网络驱动提供给应用程序编程的一种接口,一套标准,一种机制 。
Socket socket = new Socket("127.0.0.1", 55565);
注意:
- 客户端与服务器获取流的顺序是相反的;
- 必须是一方输出一方输入,不能同时输入或输出。