day16

一:死锁问题

1.同步的弊端:

如果出现了嵌套锁,可能产生死锁(我等着你,你等着我,但都不会释放各自所持有的锁)

2.死锁问题:

是指两个以上的线程在执行过程中,相互争夺资源而卡死的状态

3.解决方式

  1. 1. 从加锁顺序的角度:
  2. 1. 让多线程嵌套加锁的顺序一致即可
  3. 2. 从部分持有的角度解决
  4. 1. 让一个线程要么一次持有它所需要的所有锁,要么一把锁都不持有(原子操作)
  5. - 将所有加锁操作放在一个同步代码快中

二:生产者消费者问题

1.线程间通信api:

  1. - wait() 阻止自己
  2. - public final void wait()
  3. - 在其他线程调用此对象的notify()方法或notityall()方法前,导致当前线程等待
  4. - 唤醒条件:在同一个对象上调用其notify()或notifyAll()
  5. - 运行条件:当前线程必须拥有此对象监视器(即我们只能在当前线程所持有的synchronize代码块中调用wait方法)
  6. - 监视器:指synchronize代码块中的锁对象
  7. - 执行特征:
  8. 1. 该线程发布(释放)对此监视器的所有权
  9. 1. 等待(阻塞)
  10. 1. 如果此时被唤醒,不是立刻执行而是等到 再持有锁再继续执行
  11. - notify() 通知别人
  12. - public final void notify()
  13. - 唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程
  14. - notifyall() 通知别人
  15. - public final void notifyall()
  16. - 唤醒在此对象监视器上等待的所有线程

2.生产者消费者实例

  1. 1. 定义生产者(用一个线程来模拟)
  2. 1. 定义消费者(用一个线程来模拟)
  3. 1. 定义蒸笼(共享缓冲区)
  4. 1. 定义类描述包子
  5. 1. edition2
  6. 1. 实现生产者和消费者之间的线程同步
  7. 1. 实现生产者和消费者之间的通信(实现线程间的通信)
  8. - 为什么多个生产者线程和多消费者线程的时候使用notify方法,此时会出现卡死的情况
  9. - notify唤醒了本来不该唤醒的线程
  10. 2. 虚假唤醒:
  11. - 一个本不应该被唤醒的线程被唤醒了,这就叫虚假唤醒
  12. - 所以为了保证在虚假唤醒的情况下,我们的生产者或消费者线程,仍然能正确执行,我们必须保证在每次执行之前,先判断蒸笼状态(先判断执行条件)

3.Thread.sleep和Object.wait()

  1. 1. 所属不同
  2. 1. 唤醒条件不同
  3. 1. 是否释放锁

4.生产者消费者问题(包子与蒸笼实例)

  1. //消费者任务
  2. public class ConsumerTask implements Runnable {
  3. private Container container;
  4. public ConsumerTask(Container container) {
  5. this.container = container;
  6. }
  7. @Override
  8. public void run() {
  9. while (true) {
  10. container.eatFood();
  11. }
  12. }
  13. }
  14. //生产者任务
  15. public class ProducerTask implements Runnable {
  16. private Container container;
  17. private Food[] foodsBill = {new Food("牛肉包", 3.0), new Food("杭州小笼包", 1.0),
  18. new Food("蟹黄包", 5.0), new Food("灌汤包", 4.0)};
  19. private Random random;
  20. public ProducerTask(Container container) {
  21. this.container = container;
  22. random = new Random();
  23. }
  24. @Override
  25. public void run() {
  26. while (true) {
  27. // 做包子放入蒸笼
  28. int foodIndex = random.nextInt(foodsBill.length);
  29. container.setFood(foodsBill[foodIndex]);
  30. }
  31. }
  32. }
  33. //蒸笼
  34. public class Container {
  35. private Food food;
  36. public void setFood(Food newFood) {
  37. food = newFood;
  38. }
  39. public void eatFood() {
  40. food = null;
  41. }
  42. }
  43. class Food {
  44. String name;
  45. double price;
  46. public Food(String name, double price) {
  47. this.name = name;
  48. this.price = price;
  49. }
  50. @Override
  51. public String toString() {
  52. return "Food{" +
  53. "name='" + name + '\'' +
  54. ", price=" + price +
  55. '}';
  56. }
  57. }

网络编程:

一.网络编程概述

  1. - 应用层产生待传输的数据传给传输层
  2. 1. 通过IP端口发送(发送的是二进制字节数据)到接收端指定的端口,接收端传输层先接受
  3. 1. 传输层将数据转交给应用层,应用层解析接受到的数据
  4. 1. java语言中,已经定义好了来直接实现传输层的功能的工具类,只需关系实现传输层的类
  5. - ip:表示网络中的主句,即网络中的主机地址
  6. - 端口号:表示主机中进程的逻辑地址
  7. - 传输层协议:规定了数据传输的方式(TCP协议和UDP协议)
  8. - UDP协议:传输数据报包。
  9. - TCP协议:在传输端两端建立数据链接,利用流来进行数据传输
  10. - jdk中针对不同的传输层协议,实现了不同的类,按照不同的传输层协议定义了数据传输规则,实现跨进程数据传输。

二:基于UDP协议的网络传输

1.版本一

  1. - 发送端代码:
  2. 1. 建立UDPsocket对象
  3. 1. 将要发送的数据封装成**数据报包**
  4. 1. 通过UDPsocket对象,将数据包发送出
  5. 1. 释放资源
  6. - DatagramSocket
  7. 1. 构造方法:DatagramSocket(int port):创建数据报套接字并将其绑定到本地主机上的指定端口 //port就是我们指定的端口号
  8. - DatagramPacket 数据报包
  9. 2. 构造方法:DatagramPacket(byte[] buf,int offset,int lenght,InetAddress address,int port)
  10. - buf 包数据
  11. - offset 包数据偏移量
  12. - length 包数据长度
  13. - address 目的地址
  14. - port 目的端口号
  1. DatagramSocket socket = new DatagramSocket(10086); //创建UPD协议的套装子对象
  2. byte[] bytes = data.getBytes() //创建用于发送数据的数据报包 //data是应用层给的数据
  3. InetAddress targetIp = InetAddress.getByname("127.0.0.1");
  4. DatagramPacket sendPacket = new DatagramPacket(bytes,0,bytes.length,targerIp,1024);
  5. socket.send(sendPacket); //通过UDP的socket对象将数据报发出
  6. socket.close(); //关闭Socket并释放资源
  1. - 接收端代码:
  2. 1. 建立UDPsocket对象
  3. 1. 创建用于接受数据的数据报包,通过socket对象的receive方法接受数据
  4. 1. 通过数据包对象的功能来完成对接收到数据进行解析
  5. 1. 可以对资源进行释放
  6. - DatagramPacket(byte[] buf, int offset, int lenght)
  7. - 应用层:
  8. - 解析收到的数据:receivePacket.getData() //返回一个字节数组,该字节数组中就存储了接收到的字节数据
  9. - getOffset 返回实际接收到的字节数据,在字节数组的起始位置
  10. - getLength 本次实际接收到的字节数据的个数
  11. - 注意事项:
  12. - public void receive(DatagramPacket p)
  13. - 从此套数据接收数据报包,当此方法返回时,DatagramPacket的缓冲区填充了接收的数据
  14. - 数据报包也包含发送方的IP地址和发送方机器上的端口号
  15. - 此方法在接收到数据前一直阻塞
  16. - 在一台主机中,一个端口号只能绑定一个进程
  17. - java.net.BindException:Address already in use: Cannot bind
  1. DatagramSocket socket = new DatagramSocket(1024); //绑定本机IP和指定端口号的Socket对象
  2. byte[] byteBuf = new byte[1024];
  3. DatagramPacket receivePacket = new DatagramPacket(byteBuf, 0 , byteBuf.length);
  4. //创建用于接收数据的数据报包
  5. socket.receive(receivePacket); //通过socket对象的receive方法 接收数据
  6. socket.close(); //关闭socket对象,释放资源
  7. byte[] data = receivePacket.getData(); //返回一个字节数组,该自己数组中就存储了接收到的字节数据
  8. int offset = receivePacket.getOffset(); //本次实际接收到的字节数据,在字节数组的起始位置
  9. int length = receivePacket.getLenght(); //本次实际接收到的字节数据的个数

2.版本二

day17

4.版本四(同时接收数据) //简易聊天室的实现 (1.19(day 17)-1th)

  1. - SendTask
  2. - 关闭Socket对象为问题:
  3. - 约定不管是oneperson还是anotherperson,都由ReceiveTask来关闭

三:基于TCP协议的网络传输

1.Socket类(String host,int port)

  1. - 创建一个流套接字并将其连接到指定主机上的指定端口号
  2. - Socket对象本身所绑定的ip地址和端口号:本机Ip 随机分配的一个端口号

2.创建过程

  1. 1. 发送端(基于Tcp的可靠的连接,无法单独运行客户端):
  2. 1. 创建Socket对象 //Socket socket = new Socket(String host,int port)
  3. 1. 从代表发送端的Socket对象中,获取用于发送数据的输出流 //OutputStream out = socket.getOutputSteam;
  4. 1. 利用获取到的输出流发送数据 //out.write("".getBytes());
  5. 1. 关闭Socket即可,Socket会负责关闭其中所持有的的流 //socket.close()
  6. 2. 接收端(服务器端)
  7. 1. 创建Serversocket对象,在指定端口监听客户连接请求 //Serversocket severSocket = new Serversocket(int port)
  8. 1. 收到客户端连接请求后,建立Socket连接 // Socket accept = serverSocket.**accept()**
  9. 1. 从代表本次连接中的服务器中的socket对象中,来获取输入流 // InputStream in = socket.getInputSteam()
  10. 1. 关闭socket //两个socket都要关闭 socket.close() severSocket.close()
  11. - 服务器发送反馈消息给客户端,利用服务器端Socket的输出流
  12. - OutputStream out = socket.getOutputStream()

3.文件上传(练习)

  1. //Client
  2. public static void main(String[] args) throws IOException {
  3. // 创建客户端Socket对象
  4. Socket socket = new Socket("127.0.0.1", 10086);
  5. // 从本次连接中,代表客户端Socket对象,获取需要的流对象
  6. OutputStream out = socket.getOutputStream();
  7. BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
  8. // 缓冲字符输入流,读取源文件内容
  9. BufferedReader br = new BufferedReader(new FileReader("a.txt"));
  10. String line;
  11. while ((line = br.readLine()) != null) {
  12. bw.write(line);
  13. bw.newLine();
  14. }
  15. bw.flush();
  16. // 关闭发送端的输出流
  17. socket.shutdownOutput();
  18. // 接收服务器端发送的反馈消息
  19. InputStream in = socket.getInputStream();
  20. byte[] bytes = new byte[1024];
  21. // 阻塞方法,当没有接收到服务器端发送的数据,会阻塞等待
  22. int len = in.read(bytes);
  23. System.out.println(new String(bytes, 0, len));
  24. socket.close();
  25. br.close();
  26. }
  27. //Server
  28. public static void main(String[] args) throws IOException {
  29. // 创建服务器端套接字对象
  30. ServerSocket serverSocket = new ServerSocket(10086);
  31. // 处理连接请求,并建立连接
  32. Socket socket = serverSocket.accept();
  33. InputStream in = socket.getInputStream();
  34. BufferedReader br = new BufferedReader(new InputStreamReader(in));
  35. FileWriter fileWriter = new FileWriter("c.txt");
  36. String line;
  37. // readLine方法因为底层使用socket.getInputStream()的read方法来读取
  38. while ((line = br.readLine()) != null) {
  39. // 第一种解决方案
  40. //if ("finish".equals(line)) {
  41. // break;
  42. //}
  43. fileWriter.write(line);
  44. fileWriter.write(System.lineSeparator());
  45. fileWriter.flush();
  46. }
  47. fileWriter.close();
  48. // 给客户端返送反馈消息,告诉客户端文件上传成功
  49. OutputStream out = socket.getOutputStream();
  50. out.write("文件上传成功".getBytes());
  51. socket.close();
  52. serverSocket.close();
  53. }