Java 学习 day25: 多线程下的懒汉模式,生产者消费者,网络通信
1.多线程下的单例模式
- public class Sinleton {
-
- private Sinleton(){
-
- }
- //volatile : 防止指令重排
- //指令重排:
- //创建对象的过程是:加载类,然后载入main方法,在堆内存中创建内存,把内存地址给调用处
- //但是也可能先把地址给你再创建内存空间,这时候就是指令重排了,如果在多线程情况下容易出错
- private volatile static Sinleton s = null;
- public static Sinleton getInstance(){
- //双重验证
- if(s==null){
- synchronized(Sinleton.class){
- //双重验证,比如两个线程在同时判断为空,一个进入创建,另一个等待,
- //创建的那个创建完走了,等待的进来还是得创建
- //所以里面再加个判断,这样创建完的那个走了,等待的这个在判断就不为空了
- if(s==null){
- return s = new Sinleton();
- }
- }
- }
- return s ;
- }
- }
2.生产者消费者问题
2.1 业务类
- class SynStack{
- //缓冲区存放6个char类型数据
- char [] chars = new char [6] ;
- //记录当前有几个数据,也是下标
- int count = 0 ;
- public synchronized void producer(char c) {
-
- //如果当前数据量满了那么就挂起
- if(count >= chars.length){
- try {
- this.wait();
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- //唤醒消费者购买,因为接下来要生产了,就可以先通知消费者
- this.notifyAll();
- //生产一个数据
- chars [count] = c ;
- //库存加一
- count ++ ;
- System.out.println("我生产了"+c+"我的库存有"+count+"个");
- }
- public synchronized void consumer() {
-
- //判断库存
- if(count==0){
- //没有库存了消费者就要挂起
- try {
- this.wait();
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- //当库存不满就要唤醒生产者生产
- this.notifyAll();
- //有库存就消费
- //库存减一,这里之所以先减一是因为为了实现下标,下标比个数小一
- count -- ;
- char c = chars[count] ;
- System.out.println("我购买了"+c+"你的库存有"+count+"个");
- }
- }
2.2 生产者线程
- class Producer implements Runnable{
- SynStack s ;
- @Override
- public void run() {
-
- for(int i = 0 ;i<40;i++){
- char c =(char) (i+'a');
- s.producer(c);
- }
-
- }
- public Producer(SynStack s) {
- super();
- this.s = s;
- }
-
-
- }
2.3 消费者线程
- class Consumer implements Runnable{
- SynStack s ;
- @Override
- public void run() {
- for(int i = 0 ;i<40 ;i++){
- s.consumer();
- }
- }
-
- public Consumer (SynStack s) {
- super();
- this.s = s;
- }
-
-
- }
2.4 测试类
- public class Thread_01_ProducerConsumer {
-
- public static void main(String[] args) {
- SynStack s = new SynStack();
- Thread t1 = new Thread(new Producer(s)) ;
- Thread t2 = new Thread(new Consumer(s)) ;
- t1.start();
- t2.start();
- }
-
- }
3.网络通信
3.1 概述
Java是 Internet 上的语言,它从语言级上提供了对网络应用程 序的支持,程序员能够很容易开发常见的网络应用程序。
Java提供的网络类库,可以实现无痛的网络连接,联网的底层 细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并 且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一 的网络编程环境。
3.2 网络基础
计算机网络:
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规 模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、 共享硬件、软件、数据信息等资源。
网络编程的目的:
直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
网络编程中有两个主要的问题:
如何准确地定位网络上一台或多台主机;定位主机上的特定的应用
找到主机后如何可靠高效地进行数据传输
3.3 网络通信
通信双方地址
1.IP
2.端口号
一定的规则
1.osi参考模型,模型过于理想化,未能在因特网上进行广泛推广
2.TCP/IP参考模型
3.4 IP
- IP 地址:InetAddress
- 唯一的标识 Internet 上的计算机(通信实体)
- 本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost
- IP地址分类方式1:IPV4 和 IPV6
- IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
-
-
- IPV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示, 数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
- IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168. 开头的就是私有址址,范围即为192.168.0.0--192.168.255.255,专门为组织机 构内部使用
- 特点:不易记忆
-
3.5 端口号
- 端口号标识正在计算机上运行的进程(程序)
- 不同的进程有不同的端口号
- 被规定为一个 16 位的整数 0~65535。
- 端口分类:
- 公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
-
-
- 注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
-
-
-
- 动态/私有端口:49152~65535。
-
- 端口号与IP地址的组合得出一个网络套接字:Socket。
3.6 TCP协议
3..6.1 概述
- 传输层协议中有两个非常重要的协议:
- 传输控制协议TCP(Transmission Control Protocol)
- 用户数据报协议UDP(User Datagram Protocol)。
- TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
- IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。
- TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层。
3.6.2 服务端
- public static void main(String[] args) throws Exception {
- // 1 创建对象 开启端口
- ServerSocket ss = new ServerSocket(10001);
- System.out.println("服务器已启动,等待连接...");
- // 执行到这里就会进入等待,等待客户端连接
- // 返回客户端对象
- Socket skt = ss.accept();
- System.out.println("客户端已连接");
- // 给客户端响应信息
- // 获取输出流
- OutputStream os = skt.getOutputStream();
- PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"));
- // 获取输入流
- InputStream is = skt.getInputStream();
- // 转换为字符输入
- InputStreamReader isr = new InputStreamReader(is, "gbk");
- // 转换为字符输入缓冲流
- BufferedReader br = new BufferedReader(isr);
- String temp = null;
- while ((temp = br.readLine()) != null) {
- // 得到客户端数据
- System.out.println("客户端发来消息 : "+temp);
- // 向客户端发送数据
- pw.println("您的反馈 '"+temp+"' 我们已收到,尽快给您答复.");
- pw.flush();
- }
-
- // 先开启的,后关闭
- pw.close();
- os.close();
- skt.close();
- ss.close();
- System.out.println("服务器已关闭");
- }
-
- public static void test() throws Exception {
-
- // 1 创建对象 开启端口
- ServerSocket ss = new ServerSocket(10001);
- System.out.println("服务器已启动,等待连接...");
- // 执行到这里就会进入等待,等待客户端连接
- // 返回客户端对象
- Socket skt = ss.accept();
- System.out.println("客户端已连接");
- // 给客户端响应信息
- // 获取输出流
- OutputStream os = skt.getOutputStream();
- PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"));
- pw.println("你好吗?");
- pw.println("吃了吗");
- pw.flush();
-
- // 先开启的,后关闭
- pw.close();
- os.close();
- skt.close();
- ss.close();
- System.out.println("服务器已关闭");
- }
- }
3.6.3 客户端
- public class TCPClient {
-
- public static void main(String[] args) throws Exception {
- // 创建对象,绑定IP和端口
- Socket skt = new Socket("192.168.2.62", 10001);
- // 获取服务端传递的信息
- // 获取输入流
- InputStream is =skt.getInputStream();
- // 转换为字符输入
- InputStreamReader isr = new InputStreamReader(is,"gbk");
- // 转换为字符输入缓冲流
- BufferedReader br = new BufferedReader(isr);
-
- // 给服务端响应信息
- // 获取输出流
- OutputStream os = skt.getOutputStream();
- PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "gbk"));
- String temp = null;
- Scanner scanner = new Scanner(System.in);
- while (!(temp = scanner.nextLine()).equals("退出")) {
- pw.println(temp);
- pw.flush();
- System.out.println(br.readLine());
- }
-
- br.close();
- isr.close();
- is.close();
- skt.close();
- }
- }
3.7 UDP协议
3.7.1 概述
- 类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
- UDP数据报通过数据报套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
- DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP 地址和端口号以及接收端的IP地址和端口号。
- UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和 接收方的连接。如同发快递包裹一样。
3.7.2 客户端
- public class UDPClient {
-
- public static void main(String[] args) throws Exception{
- Scanner scanner = new Scanner(System.in);
- String string = scanner.nextLine();
- // 1 字节数组流 --> 数据流写出到字节数组流 --> 字节数组
- // 2 字节数组 --> 转换为字节数组流 --> 数据流读取字节数组流中的数据
- while (string != null && !string.equalsIgnoreCase("exit")) {
- // 把数据转换为字节数组流
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- // 转换为数据流,用于数据传递
- DataOutputStream dos = new DataOutputStream(bos);
- // 把字符串写进数据流
- dos.writeUTF(string);
- // 转换为字节数组
- byte[] bytes = bos.toByteArray();
- // 服务端地址和端口 就是数据包发送到哪里去
- InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 10001);
- // 数据传递,打包
- DatagramPacket dp = new DatagramPacket(bytes, bytes.length,inetSocketAddress);
- // 传输 需要开启客户端端口
- DatagramSocket ds = new DatagramSocket(9999);
- // DatagramPacket : 数据包
- // DatagramSocket : 进行数据通信
- // 发送
- ds.send(dp);
- ds.close();
- System.out.println("请输入传递的信息");
- string = scanner.nextLine();
- }
- }
- }
3.7.3 服务端
- public class UDPServer {
-
- public static void main(String[] args) throws Exception{
- // 1 打开UDP对象,并监听端口
- DatagramSocket ds = new DatagramSocket(10001);
- // 保存数据的字节数组
- byte[] bytes = new byte[1024];
- // 包接收器
- DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
- while (true) {
- // 通过开启的端口 接收数据
- ds.receive(dp);
- // 转换为字节数组流
- ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
- // 使用数据流读取数据
- DataInputStream bis = new DataInputStream(bais);
- // 读取数据
- String data = bis.readUTF();
- System.out.println(data);
- }
- }
- }
推荐阅读