多线程2&网络编程1

原子性

强调一组操作的整体性,这一组操作不可分割,要么同时成功,要么同时失败。不能让其他操作打断。

volatile关键字不能保证数据操作的原子性的,但是加同步锁可以保证原子性,相对应的性能就会降低。

原子类底层实现

CAS算法(内存值,读取的旧值,要替换的新值)

该算法会判断旧值是否和内存值相等,如果相等则使用新值替换内存值。如果旧值和内存值不等,说明我们在操作的过程中,有其他线程插入做了数据的修改操作,此时我们的操作应该是要放弃的。我们只能从头再操作一遍。获取内存中的最新值,作为旧值存储,然后再继续计算出新值,然后再用CAS算法判断一次....

悲观锁和乐观锁

悲观锁和乐观锁是看待多线程问题解决时候的两种不同的解决思想。

悲观锁:每一次操作都悲观的认为操作肯定会被其他线程干扰,所以一定要在操作语句块中添加锁,同一时间只能一个线程执行。所以效率比较低。

乐观锁:每一次操作都乐段的认为操作不会被其他线程干扰,所以不添加任何锁的机制。而是在最终更新数据前做一次校验,校验成功则直接执行完毕;校验不成功则重新执行一次。

HashTable

是HashMap的改造版本,主要功能没有任何区别。只不过把所有的方法变成了同步方法。所以是线程安全的。

ConcurrentHashMap

HashTable也是线程安全的,但是它的同步是用syncronnized关键字把所有方法统一进行了加锁。在多线程模型下,HashTable所有操作使用同一把锁,锁住整张hash表,效率极其低。

JDK7及之前:

ConcurrentHashMap使用了分段锁的思想,首先创建一个数组作为第一次hash的查表数组。该数组中存的不是元素的值,而是另一张hash表,元素需要二次哈希,把自己使用和之前HashMap算法一致的算法进行存储。外部的大数组,每个元素都是一个单独的锁。当操作某个元素所对应的小hash表的时候,仅仅锁住当前的小表。其他的hash表依然可以并发被操作。

JDK8及以后

ConcurrentHashMap放弃了分段数组,还是只使用一个数组作为hash表。

分为以下两种情况:

存元素的时候该位置没有值,此时使用乐观锁CAS算法添加第一个节点。

如果存元素的时候该位置已经有值了,此时使用悲观锁syncronized代码块使用头结点当做锁锁住当前链表或者红黑树。

网络编程

三要素

IP地址:在网络中唯一确定一台机器的地址。

ip:一组数字,用于唯一表示一个机器的地址。

ipv4(主流)

ipv6

域名:用人类可以方便记忆的字符串,代替ip的数字,便于使用。域名和ip的对应关系,会绑定在DNS(域名解析服务器)上。

端口号:在一台机器中,唯一确定一个应用程序的数字。

0-65535,其中1024以内作为保留端口,一般不要使用。

协议:就是我们数据传输的方式和规则。UDP和TCP

命令:

ipconfig 查看当前IP地址

ping 域名或ip 计算机会模拟一些数据发送给指定的目标地址,如果能发送过去并接收到对方的回应,则代表网络畅通。

本机回环地址:127.0.0.1 无论当前计算机的ip是什么,我们访问该地址都是访问本机。

netstat -ano | findstr 端口号 查找某个端口被哪个进程占用了,获取到的是一个pid,也就是进程id。我们可以使用任务管理器强制关闭它。

InetAddress

IP地址对应的对象。

协议

UDP和TCP

UDP是无连接协议速度比较快,但是不安全,数据可能丢失;TCP是有连接的协议,速度略慢,但是安全,数据不会丢失,数据没有大小限制。

UDP协议通信

DatagramSocket

使用UDP协议发送或接收数据的核心类。

如果是接收的话,构造方法不要使用空参的。必须手动指定某个端口,从此端口接收。

send(DatagramPacket dp)方法,发送指定的数据包

close() 释放资源

DatagramPacket(byte[] buf, int length, InetAddress address, int port)

数据包,内部封装了要发送的数据、数据的长度、目标的IP地址和端口号

发送端 

//1.找码头
DatagramSocket ds = new DatagramSocket();
//2.打包礼物
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
String s = "送给村长老丈人的礼物";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);


//3.由码头发送包裹
ds.send(dp);

//4.付钱走羊
ds.close();

接收端

//1.找码头     ---- 表示接收端从10000端口接收数据的.
 DatagramSocket ds = new DatagramSocket(10000); 
//2,创建一个新的箱子
 byte [] bytes = new byte[1024 * 64];
// 期望接收的数据的大小,但是实际不一定是这么大 
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
 //3.接收礼物,把礼物放到新的箱子中
 System.out.println("-----------接收前----------");
 ds.receive(dp);
// 阻塞式方法
 System.out.println("------------接收后---------");
 //4.从新的箱子里面获取礼物//
 byte[] data = dp.getData();
 int length = dp.getLength();
// 实际传递过来的数据的长度
 System.out.println(new String(bytes,0,length)); 
//5.拿完走羊
 ds.close();

UDP的通信方式

单播:只有一个接收端。

组(多)播:有多个接收端,把接收端建立一个小组进行接收。

  1. 发送的时候的ip需要特殊指定,指定组播的ip发送。

    2. 接收端,核心对象不再是DatagramSocket,改为MulticastSocket
                    3. 接收端,MulticastSocket必须调用joinGroup(InetAddress ip),加入到指定的组播的ip中去。

广播:当前局域网内所有人。

相当于给所有人发送了一次单播,所以代码和单播的代码一致,只不过发送端的目标ip比较特殊,是广播ip--255.255.255.255

思考题:

使用组播的方式,完善我们的聊天室案例。

TCP

tcp协议要顺利完成通信,必须要保证客户端和服务器端能成功连接。

客户端

 
//1,创建一个Socket对象   要和目标服务器建立连接
Socket socket = new Socket("127.0.0.1",10000);
​//2.获取一个IO流开始写数据  这个流就是连接的客户端和服务器,通过网络传输数据
OutputStream os = socket.getOutputStream();
os.write("hello".getBytes());
//3.释放资源
os.close();
socket.close();

服务器端

//1. 创建Socket对象  监听10000端口,客户端就会连这个端口
ServerSocket ss = new ServerSocket(10000);
//2. 等待客户端连接,这是一个阻塞式的方法,如果获取不到客户端的连接,则一直在这等待。每次获取一个客户端的连接,该方法每调用一次就会接收一个客户端的连接
Socket accept = ss.accept();
//3.获得输入流对象,该输入流对接的就是客户端的输出流
InputStream is = accept.getInputStream();
int b;
while((b = is.read()) != -1){
    System.out.print((char) b);
}
//4.释放资源
is.close();
ss.close();

注意:在用完流和Socket之后,一定记得释放资源。也就是调用close方法。

三次握手

作用:保证客户端和服务器能成功建立连接。

  1. 客户端给服务器发送连接的请求

  2. 服务器返回给客户端可以允许它连接的响应

  3. 客户端给服务器发送表示已经收到服务器的许可,真正要建立连接了

四次挥手

作用;保证客户端和服务器能成功断开连接

  1. 客户端给服务器发送断开连接的请求

  2. 服务器返回给客户端要等一下,把最后的一点数据处理完毕后我通知你你再断开

  3. 服务器处理完毕后,通知客户端,说可以允许你断开了

  4. 客户端通知服务器表示收到断开的许可,真正断开连接

Socket

该对象可以通过指定的ip和端口建立连接。

该对象可以获取输入流或者输出流,在网络中传输数据。

注意:在Socket流操作完毕之后,要记得手动把流关闭一下。

思考:

编写案例,跟小组内其他成员配合进行文件传递。传递完成后,把“文件名+传送完毕”返回回来。

提示:可以使用对象操作流考虑实现。

UUID

randomUUID()

随机生成一个唯一不重复的字符串。

多线程优化

  1. 如果我们的服务器使用单线程模型运行,接收到一个客户端的连接之后,只能执行完它的所有任务才能接收下一个。如果任务执行的时间比较长,会导致客户端等待时间比较长。客户端之间不能并发,效率比较低。

  2. 要解决上述问题,我们引入多线程模型。让主线程仅仅用于循环接收跟客户端的连接,一旦确定连接。主线程不再执行任务,而是让新的线程执行。主线程继续获取连接。此时,客户端的任务之间是并发的。新的问题:每个任务都创建新的线程,会导致系统资源浪费严重,甚至导致系统崩溃。

  3. 要解决上述问题,我们引入线程池,把线程利用率提升上来,尽可能的循环利用线程执行逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值