多线程
创建线程的三种方式
- 继承Thread类:线程无法继承其他类,不利于功能的扩展
- 实现Runnable接口:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强
- 实现Callable接口:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果
// 方式一
class MyThread extends Thread{
// 重写run方法,设置线程任务
@Override
public void run() {
XXX
}
}
MyThread t = new MyThread();
t.start();
// 方法二
class MyRun implements Runnable {
@Override
public void run() {
XXX
}
}
Runnable t = new MyRun();
Thread ts = new Thread(t);
ts.start();
// 方法三
class MyCall implements Callable<Integer> {
private Integer n;
public Thread2(Integer n){
this.n = n;
}
@Override
public Integer call() throws Exception {
int num = 0;
XXX
return num;
}
}
Callable<Integer> call = new MyCall(10);
FutureTask<Integer> ft = new FutureTask<>(call); // 将Callable包装成FutureTask类型
Thread ts = new Thread(ft);
ts.start();
int result = ft.get(); // 获取任务类返回的结果
System.out.println(result);
**new FutureTask<>(call).get():**如果任务尚未完成ft.get()方法会阻塞当前线程,直到任务执行完成。如果任务尚未完成,ft.get()方法会阻塞当前线程,直到任务执行完成。
Callable/Runnable/Thread关系图
Thread常用方法
| Thread提供的常见构造器 | 说明 |
|---|---|
| public Thread(String name) | 可以为当前线程指定名称 |
| public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
| public Thread(Runnable target, String name) | 封装Runnable对象成为线程对象,并指定线程名称 |
| Thread提供的常用方法 | 说明 |
|---|---|
| public void run() | 线程的任务方法 |
| public void start() | 启动线程 |
| public String getName() | 获取当前线程的名称,线程名称默认是Thread-索引 |
| public void setName(String name) | 为线程设置名称 |
| public static Thread currentThread() | 获取当前执行的线程对象 |
| public static void sleep(long time) | 让当前执行的线程休眠多少毫秒后,再继续执行 |
| public final void join()... | 让调用当前这个方法的线程先执行完! |
public class Test1 {
public static void main(String[] args) {
MyThread t = new MyThread();
Thread s = new Thread(t,"飞机");
s.run(); // 执行线程任务
s.setName("线程1"); // 设置线程名称
s.start(); // 启动线程
}
}
class MyThread implements Runnable {
@Override
public void run() {
Thread t1 = Thread.currentThread(); // currentThread静态方法,返回当前执行的线程对象
for(int i=0;i<5;i++){
System.out.println(t1.getName()+"线程-->"+i);
try {
t1.sleep(1000); // 线程休眠方法
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
线程同步三种方法
线程安全:多线程、共享资源、修改资源
同步代码块
- 对于实例方法建议使用this作为锁对象
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象
synchronized (this) { 异常核心代码块 }
synchronized (xx.class) { 异常核心代码块 }
同步方法
public synchronized void funcation() {
XXX
}
public synchronized static void funcation() {
XXX
}
lock锁
public class Account {
private final Lock lk = new ReentrantLock(); // ReentrantLock为Lock的实现类
public void funcation() {
lk.lock(); // 获得锁
try {
XXX
} finally {
// 保证 lk 锁一定会得到释放
lk.unlock(); // 释放锁
}
}
}
线程池
线程池就是一个可以复用线程的技术
线程池创建的两种方式
- 使用
Executors(线程池的工具类)调用方法返回不同特点的线程池对象。(推荐不使用)
**注意 :**这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。
| 方法名称 | 说明 |
|---|---|
| public static ExecutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。 |
| public static ExecutorService newSingleThreadExecutor() | 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。 |
| public static ExecutorService newCachedThreadPool() | 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉。 |
| public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。 |
public class ExecutorTest {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(4); // 最大线程数量等于核心线程数量
ExecutorService poo12 = Executors.newSingleThreadExecutor(); // 最大线程数量和核心线程数量为1
ExecutorService pool3 = Executors.newCachedThreadPool();
// 最大线程数量为Integer.MAX_VALUE,核心线程数量为0
ScheduledExecutorService pool4 = Executors.newScheduledThreadPool(4); // 可以延迟执行任务
Future<String> f1 = pool.submit(new MyCallable2());
Future<String> f2 = pool.submit(new MyCallable2());
Future<String> f3 = pool.submit(new MyCallable2());
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
}
}
class MyCallable2 implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName();
}
}
- 使用
ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。
参数1:指定线程池的核心线程的数量;
参数2:指定线程池的最大线程数量;
参数3:指定临时线程的存活时间;(空闲线程的最大存活时间)
参数4:指定临时线程存活的时间单位(秒、分、时、天);
参数5:指定线程池的任务队列;
参数6:指定线程池的线程工厂;
参数7:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理);
ExecutorService executor = new ThreadPoolExecutor(3,5,10,TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
// 三个核心线程
executor.execute(new MyRunnable());
executor.execute(new MyRunnable());
executor.execute(new MyRunnable());
// 阻塞队列长度为3
executor.execute(new MyRunnable());
executor.execute(new MyRunnable());
executor.execute(new MyRunnable());
// 两个临时线程:当核心线程忙且阻塞队列满,则会启用临时线程
// 临时线程存在期间不断尝试获取队列中的任务,当临时线程空闲时间超过存活时间则会销毁
executor.execute(new MyRunnable());
executor.execute(new MyRunnable());
// 触发任务拒绝策略,当核心和临时线程忙且阻塞队列满
executor.execute(new MyRunnable());
executor.shutdown(); // 等任务都执行完,再关闭线程池
executor.shutdownNow(); // 立刻关闭线程池,不再接受新任务,打断正在进行的任务
// 处理callable任务
Future<String> f1 = executor.submit(new MyCallable());
try {
System.out.println(f1.get());
} catch (Exception e) {
throw new Exception(e);
}
线程池常用方法
| 方法名称 | 说明 |
|---|---|
| void execute(Runnable command) | 执行 Runnable 任务 |
| Future submit(Callable task) | 执行 Callable 任务,返回未来任务对象,用于获取线程返回的结果 |
| void shutdown() | 等全部任务执行完毕后,再关闭线程池! |
| List shutdownNow() | 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务 |
线程池注意事项
**新建临时线程的时机:**新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
**拒绝新任务:**核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
任务拒绝策略
| 策略 | 说明 |
|---|---|
| ThreadPoolExecutor.AbortPolicy() | 丢弃任务并抛出RejectedExecutionException异常。是默认的策略 |
| ThreadPoolExecutor. DiscardPolicy() | 丢弃任务,但是不抛出异常,这是不推荐的做法 |
| ThreadPoolExecutor. DiscardOldestPolicy() | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
| ThreadPoolExecutor. CallerRunsPolicy() | 由主线程负责调用任务的run()方法从而绕过线程池直接执行 |
并发/并行
进程
- 运行的程序就是一个进程
- 线程属于进程,一个进程可以同时运行多个线程
- 进程中的多个线程其实是并行和并发同时执行的
**并发:**CPU分时轮询的执行线程
**并行:**同一个时刻同时在执行
Java网络编程
基本的通信架构:CS架构( Client客户端/Server服务端 ) 、 BS架构(Browser浏览器/Server服务端)
网络通信三要素:IP地址;端口;协议
端口用来标记标记正在计算机设备上运行的应用程序,被规定为一个16位的二进制,范围是0~65535
端口分类
- 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
- 注册端口:1024~49151,分配给用户进程或某些应用程序。
- 动态端口:49152~65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配。
- 注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则报错。
IP地址分为IPv4和IPv6
IPv4使用32位地址,通常以点分十进制表示:地址分四段,每段用一个十进制数表示,每段用点分开
IPv6使用128位地址,使用冒分十六进制表示:地址分为八段,每段每四位编码成一个十六进制位表示, 每段之间用冒号(:)分开
IP域名:互联网中识别和定位网站的人类可读的名称
DNS域名解析:互联网中用于将域名转换为对应IP地址的分布式命名系统
公网IP是可以连接网络的IP地址;内网IP也称局域网,是只能组织结构内部使用的的IP地址
InetAddress常用方法
InetAddress id = InetAddress.getLocalHost(); // 获取本机IP,返回一个InetAddress对象
System.out.println(id.getHostName()); // 获取IP对象对应的主机名
System.out.println(id.getHostAddress()); // 获取IP对象对应的ip地址信息
InetAddress id1 = InetAddress.getByName("www.baidu.com"); // 根据ip或域名获取IP对象
System.out.println(id1.isReachable(5000));// 判断本机与id1是否可以通信
网络模型
通信协议
计算机中,连接和通信数据规则被成为网络通信协议
UDP协议
- 用户数据报协议
- UDP面向无连接,不可靠传输的通信协议
- 速度快,有大小限制一次最多发送64K,数据不安全,易丢失数据
TCP协议(全双工)
- TCP是一种面向连接的可靠通信协议
- 传输前,采用三次握手方式建立连接,点对点的通信
- 在连接中可进行大数据量的传输
- 传输后,采用四次挥手方式断开连接,确保消息全部收发完毕
- 通信效率相对较低,可靠性相对较高
UDP通信的实现
// 多收多发:只需对主要收发动作进行while死循环
// Client
DatagramSocket socket = new DatagramSocket();
InetAddress id = InetAddress.getLocalHost();
byte[] bytes = "我爱中国".getBytes();
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,id,9999);
socket.send(packet);
socket.close();
// Server
DatagramSocket socket = new DatagramSocket(9999);
byte[] bytes = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
socket.receive(packet);
int len = packet.getLength();
String str = new String(packet.getData(),0,len);
System.out.println(str);
System.out.print(packet.getAddress().getHostAddress()+":");
System.out.println(packet.getPort());
socket.close();
TCP通信的实现
// 多收多发:将收发的动作使用while死循环
// Server接收多个客户端:while死循环负责接收客户端的Socket连接,每接收到一个Socket通信管道后分配给一个独立的线程进行处理
// 高并发时,每次创建新线程容器容易宕机,所以可以使用线程池进行优化
// Client
InetAddress inet = InetAddress.getLocalHost();
Socket socket = new Socket(inet,8787);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("黑马程序员");
dos.close();
socket.close();
// Server
ServerSocket serverSocket = new ServerSocket(8787);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
DataInputStream in = new DataInputStream(is);
System.out.println(in.readUTF());
in.close();
socket.close();