导图社区 Head First Java第15章:网络与线程
Head First Java 是Java入门的必读教材,该导图为书中关于关于网络与线程的总结。
编辑于2021-01-05 18:57:16十五、网络与线程:网络联机 P470-528
网络联机
网络
beatbox聊天程序
实时beatbox聊天程序
聊天程序概述
客户端必须认识服务器 服务器必须认识所有客户端
工作方式

1、客户端连接到服务器
2、服务器建立连接并把客户端加到来宾清单中
3、另一用户连接上来
4、用户a送出信息到聊天服务器
5、服务器将信息送给所有来宾
网络socket连接
连接、传送与接收

1、用户通过建立socket连接来连接服务器
2、用户送出信息给服务器
3、用户从服务器接收信息
建立socket连接
java.net.Socket:代表两台机器之间网络连接的对象
获取服务器信息:IP地址与TCP端口
Socket chatSocket = new Socket("196.164.1.103", 5000);
socket连接的建立代表两台机器之间存有对反的信息(IP与TCP端口)
约定好的端口
TCP端口
 pop3 110
TCP端口时一个16位宽,用来识别服务器上执行程序的数字
0-1023的TCP端口保留给已知的特定服务使用,我们使用1024-65535之间的端口
在编写服务器程序时,会加入程序代码来指定端口号
拓展
众所周知的服务(http、ftp、smtp。。。)有共同标准的端口,其他程序有开发者自定义端口
不同程序不能共享端口,绑定一个端口代表程序要在特定的端口上执行
读取socket
使用BufferedReader从Socket上读取数据

1、建立对服务器的socket连接 Socket chatSocket = new Socket("127.0.0.1", 5000);
127.0.0.1代表本机
2、建立连接到Socket上底层输入串流的InputStreamReader InputStreamReader strream = new InputStreamReader(chatSocket.getInputStream());
chatSocekt.getInputStream()从socket取得输入串流
3、建立BufferedReader来读取 BufferedReader reader = new BufferedReader(stream); String message = reader.readLine();
用PrintWeiter写数据到Socket上

1、对服务器建立socket连接 Socekt chatSocket = new Socket("127.0.0.1", 5000);
2、建立连接到Socket的PrintWriter PrintWriter.writer = new PrintWriter(chatSocket.getOutputStream());
PrintWriter 字符数据与字节间的转换桥梁,可以衔接string和socket两端
3、写入数据 writer.println("message to send"); writer.print("another message");
编写客户端
每日嘉言软件概述

1、连接 客户端脸上服务器并取得输入串流
2、读取 客户端从服务器读取数据
DailyAdviceClient客户端程序
网络socket连接
编写简单的服务器程序
一对socket:当用户创建socket时等待用户请求的ServerSocket,用户通信用socket
工作方式
1、服务器应用程序对特定端口创建出ServerSocket ServeSocket serverSock = new ServerSocket(4242);

2、客户端对服务器应用程序建立Socket连接 Socket sock = new Socket("190.165.1.103", 4242);

3、服务器创建出客户端通信的新Socket Socket sock = server.accept();
 
编写服务器程序
DailyAdviceServer程序代码
accept()会停下来等待要求到达之后才继续
服务器每次只能服务一个用户

使用不同的线程可让服务器同时处理多个客户
简单的客户端程序
编写聊天客户端程序
requestFocus()要求光标
改善客户端程序
第二版:可发送和接收
线程thread、一个独立的执行空间stack、进程process
Thread t = new Thread(); t.start();
线程
线程和Thread
Java有多个线程但只有一个Thread类
线程代表独立的执行空间
每个java程序会启动一个主线程--将main()放入自己的执行空间的最开始,JVM会负责主线程的启动(以及垃圾收集所需的系统线程),程序员负责启动自己建立的线程
一个以上执行空间的意义
执行动作可在执行空间快速来回切换,因此仿佛同时执行几件事
目前执行空间最上的会被执行,100ms内,目前执行的程序代码会被切换到不同空间上的不同方法
流程

1、JVM调用main()
2、main()启动新的线程,新线程启动期间main的线程暂时停止执行
3、JVM在主线程与新线程间切换直至两者都完成为止
启动线程
如何启动新线程
1、建立Runnable对象(线程的任务) Runnable threadJob = new MyRunnable(); 此类时对线程要执行的任务的定义,此方法会在线程的执行空间运行
2、建立Thread对象(执行工人)并赋值Runnale(任务) Thread myThread = new Thread(threadJob); 把Runnable对象传给Thread的构造函数,告诉Thread对象把哪个方法放在执行空间运行--Runnable的run()方法
3、启动Thread myThread.start(); 当新线程启动后,会把Runnable对象的方法摆到新的执行空间中
每个Thread执行一个可以放在执行空间的任务
Thread是工人,Runnable是工作, Runnable带有放在执行空间的第一项的方法run()
Runnable接口只要一个方法run() (因为是接口,所有一定是public)
线程的任务可以被定义在任何实现Runnable的类上
Runnable接口
实现Runnable接口来建立thread的运行任务
新建线程的3个状态

新建、可执行、执行中
线程状态
两种线程状态
典型的可执行/执行中循环
线程有可能会暂时被挡住

线程调度器
我们无法控制调度,没有API可以调度调度器
sleep()可让所有线程都有机会被执行,在指定的沉睡时间之前线程一定不会被唤醒
线程调度
显示调度器有多个不可预测的范例
有不同结果的原因
拓展
用Thread的子类来覆盖run()方法,然后调用Thread的无参构造函数来创建新线程
合法但不好,与设计概念有关
Thread对象不可重复使用,一旦run()完成,该线程就不能再重新启动,但Thread对象可能还活在堆上

线程延迟
确保线程有机会执行的最好方法是让他们周期性的睡一下,调用sleep(),保持两秒内不会醒来进入可执行状态,之后不一定会立即执行,而是回到可执行状态
sleep()有可能抛出InterruptedException异常,所以要包在try/catch中
Thread.sleep()
使用sleep()让程序更可预测
建立与启动两个线程
给线程起名,用名字判断正在运行那个线程
线程会产生并发问题
并发性问题会引起竞争状态,竞争状态会引发数据损毁。这来自于一种状况:两个或以上线程存取单一对象的数据,即两个不同的执行空间上的方法都对同一个对象执行getter或setter
以程序码代表杰纶与晨的问题
类:RyanAndMonicaJob与BankAccount
RyanAndMonica实现Runnable,代表两人都有的行为:检查余额并花掉,检查与花掉之间sleep一下,有BankAccount实例
BankAccount代表共享账户
同一个runnable建立两个线程
杰纶与晨范例
Thread.current()当前线程
踢爆真相
需要对帐户存取的锁
没有交易时,锁时开着的。要交易时,把账户锁上并带走钥匙。交易完成后解锁并归还钥匙,然后可以换其他人存取账户
同步化
让makeWithdrawal()跑起来像个原子
古人认为原子是不可分割的最小单位
使用synchronized关键词修饰方法使它每次只能被单一的线程存取
ssynchronized关键词代表线程需要一把钥匙来存取被同步化过的线程
要保护数据,就把作用在数据上的方法给同步化
使用对象的锁
每个Java对象都有一把锁,每个锁只有一把钥匙
通常对象都没上锁,但如果对象有同步化的方法,则线程只能在取得钥匙的情况下进入线程。即没有其他线程已经进入的情况下才能进入
丢失更新问题
读取值不等于目前值
执行程序
B把A做过的动作覆盖掉,让A的更新好像没发生过一样
同步化的方法
用同步机制让increment()原子化
将方法同步化可解决丢失更新问题,它会让方法中的步骤组成不可分割的单元
一旦线程进入方法,必须确保在其他线程可以进入方法之前所有步骤都会完成
原则上只做最少量的同步化,同步化的规模可以小于方法的全部,可用synchronized来修饰数行指令而不必将整个方法同步化。
synchronized(this){ criticalStuff(); moreCriticalStuff();} 使用参数所指定的对象的锁来做同步化
thread B运行时会因为拿不到钥匙,而进入等待对象锁钥状态
死锁
同步化的死亡阴影
死锁会发生是因为两个线程互相持有对方正在等待的东西,没有方法可以脱离相和歌情况,所以两个线程只好一直等
要点
程序终版
客户端的最终版
新的SimpleChatClient
服务器程序
简单的聊天服务器程序
关于同步化的疑问
当对静态方法做同步化时,Java会使用类本身的锁,所以当同一个类有两个被同步化过的静态方法时,线程需要取得类的锁才能进入方法
可用线程优先级来影响执行性能,但不能依靠优先级来维持查询的正确性
程序料理
习题
Singleton模式
作用:限制对象实例的个数为1
private static 类 a = new 类(); 私有化构造函数