线程同步TCP通信

1.1. 线程同步
1.1.1. 线程安全API与非线程安全API
之前学习的API中就有设计为线程安全与非线程安全的类:
StringBuffer 是同步的 synchronized append();
StringBuilder 不是同步的 append();
相对而言StringBuffer在处理上稍逊于StringBuilder,但是其是线程安全的。当不存在并发时首选应当使用StringBuilder。
同样的:
Vector 和 Hashtable 是线程安全的而ArrayList 和 HashMap则不是线程安全的。
对于集合而言,Collections提供了几个静态方法,可以将集合或Map转换为线程安全的:
例如:
Collections.synchronizedList() :获取线程安全的List集合
Collections.synchronizedMap():获取线程安全的Map
1.    …
2.    List list = new ArrayList();
3.    list.add(“A”);
4.    list.add(“B”);
5.    list.add(“C”);
6.    list = Collections.synchronizedList(list);//将ArrayList转换为线程安全的集合
7.    System.out.println(list);//[A,B,C] 可以看出,原集合中的元素也得以保留
8.    …
1.1.2. 使用ExecutorService实现线程池
当一个程序中若创建大量线程,并在任务结束后销毁,会给系统带来过度消耗资源,以及过度切换线程的危险,从而可能导致系统崩溃。为此我们应使用线程池来解决这个问题。
ExecutorService是java提供的用于管理线程池的类。
线程池有两个主要作用:
1.控制线程数量
2.重用线程
线程池的概念:首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务
线程池有以下几种实现策略:
Executors.newCachedThreadPool()
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
Executors.newFixedThreadPool(int nThreads)
创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。
Executors.newScheduledThreadPool(int corePoolSize)
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
Executors.newSingleThreadExecutor()
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
可以根据实际需求来使用某种线程池。例如,创建一个有固定线程数量的线程池:
1.    …
2.    ExecutorService threadPool
3.        = Executors.newFixedThreadPool(30);//创建具有30个线程的线程池
4.    Runnable r1 = new Runable(){
5.        public void run(){
6.            //线程体
7.        }
8.    };
9.    threadPool.execute(r1);//将任务交给线程池,其会分配空闲线程来运行这个任务。
10.    …
1.1.3. 使用BlockingQueue
BlockingQueue是双缓冲队列。
在多线程并发时,若需要使用队列,我们可以使用Queue,但是要解决一个问题就是同步,但同步操作会降低并发对Queue操作的效率。
BlockingQueue内部使用两条队列,可允许两个线程同时向队列一个做存储,一个做取出操作。在保证并发安全的同时提高了队列的存取效率。
双缓冲队列有一下几种实现:
ArrayBlockingDeque:规定大小的BlockingDeque,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的。
LinkedBlockingDeque:大小不定的BlockingDeque,若其构造函数带一个规定大小的参数,生成的 BlockingDeque有大小限制,若不带大小参数,所生成的BlockingDeque的大小由Integer.MAX_VALUE来决定.其所含 的对象是以FIFO(先入先出)顺序排序的。
PriorityBlockingDeque:类似于LinkedBlockDeque,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序。
SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。
例如:
1.    public static void main(String[] args) {
2.        BlockingQueue queue
3.            = new LinkedBlockingDeque();
4.        
5.        try {
6.            //queue.offer(“A”);//立即向队列末尾追加元素
7.            
8.            /*
9.             * 向队列末尾追加元素,指定可延迟5秒。
10.             * 若5秒钟内成功将元素加入队列返回true
11.             * 若超时后元素仍然没有加入队列则返回flase
12.             */
13.            queue.offer(“A”,5,TimeUnit.SECONDS);
14.        } catch (InterruptedException e) {
15.            e.printStackTrace();
16.        }
17.        System.out.println(queue.poll());
18.    }
2. TCP通信
2.1. Socket原理
2.1.1. Socket简介
socket通常称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。
应用程序通常通过“套接字”向网络发出请求或者应答网络请求。Socket和ServerSocket类库位于java .net包中。ServerSocket用于服务端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操 作这个实例,完成所需的会话。
2.1.2. 获取本地地址和端口号
java.net.Socket为套接字类,其提供了很多方法,其中我们可以通过Socket获取本地的地址以及端口号。
1.        int getLocalPort()
该方法用于获取本地使用的端口号
1.        InetAddress getLocalAddress()
该方法用于获取套接字绑定的本地地址
使用InetAddress获取本地的地址方法:
1.        String getCanonicalHostName()
获取此 IP 地址的完全限定域名。
1.        String getHostAddress()
返回 IP 地址字符串(以文本表现形式)。
代码如下:
1.        public void testSocket()throws Exception {
2.            Socket socket = new Socket(“localhost”,8088);
3.            InetAddress add = socket.getLocalAddress();//获取本地地址信息
4.            System.out.println(add.getCanonicalHostName());
5.            System.out.println(add.getHostAddress());
6.            System.out.println(socket.getLocalPort());
7.        }
2.1.3. 获取远端地址和端口号
Socket也提供了获取远端的地址以及端口号的方法:
1.        int getPort()
该方法用于获取远端使用的端口号 。
1.        InetAddress .getInetAddress()
该方法用于获取套接字绑定的远端地址 。
代码如下:
1.        public void testSocket()throws Exception {
2.            Socket socket = new Socket(“localhost”,8088);
3.             InetAddress inetAdd = socket.getInetAddress();
4.            System.out.println(inetAdd.getCanonicalHostName());
5.            System.out.println(inetAdd.getHostAddress());
6.            System.out.println(socket.getPort());
7.        }
2.1.4. 获取网络输入流和网络输出流
通过Socket获取输入流与输出流,这两个方法是使用Socket通讯的关键方法。封装了TCP协议的Socket是基于流进行通讯的,所以我们在创建了双方连接后,只需要获取相应的输入与输出流即可实现通讯。
1.    InputStream getInputStream()
该方法用于返回此套接字的输入流。
1.    OutputStream .getOutputStream()
该方法用于返回此套接字的输出流。
代码如下:
1.    public void testSocket()throws Exception {
2.        Socket socket = new Socket(“localhost”,8088);
3.        InputStream in = socket.getInputStream();
4.        OutputStream out = socket.getOutputStream();
5.    }
2.1.5. close方法
当使用Socket进行通讯完毕后,要关闭Socket以释放系统资源。
1.    void close()
当关闭了该套接字后也会同时关闭由此获取的输入流与输出流。
2.2. Socket通讯模型
2.2.1. Server端ServerSocket监听
java.net.ServerSocket是运行于服务端应用程序中。通常创建ServerSocket需要指定服务端口号,之后监听Socket的连接。监听方法为:
1.        Socket accept()
该方法是一个阻塞方法,直到一个客户端通过Socket连接后,accept会封装一个Socket,该Socket封装与表示该客户端的有关的信息。通过这个Socket与该客户端进行通信。
代码如下:
1.        …
2.        //创建ServerSocket并申请服务端口8088
3.        ServerSocket server = new ServerSocket(8088);
4.        /方法会产生阻塞,直到某个Socket连接,并返回请求连接的Socket/
5.        Socket socket = server.accept();
6.        …
2.2.2. Client端Socket连接
通过上一节我们已经知道,当服务端ServerSocket调用accept方法阻塞等待客户端连接后,我们可以通过在客户端应用程序中创建Socket来向服务端发起连接。
需要注意的是,创建Socket的同时就发起连接,若连接异常会抛出异常。 我们通常创建Socket时会传入服务端的地址以及端口号。
代码如下:
1.    //参数1:服务端的IP地址,参数2:服务端的服务端口
2.    Socket socket = new Socket(“localhost”,8088);
3.    …
2.2.3. C-S端通信模型
C-S的全称为(Client-Server):客户端-服务器端
客户端与服务端通信模型如下:

图- 1
1.服务端创建ServerSocket
2.通过调用ServerSocket的accept方法监听客户端的连接
3.客户端创建Socket并指定服务端的地址以及端口来建立与服务端的连接
4.当服务端accept发现客户端连接后,获取对应该客户端的Socket
5.双方通过Socket分别获取对应的输入与输出流进行数据通讯
6.通讯结束后关闭连接。
代码如下:
1.    /**
2.     *    Server端应用程序
3.     /
4.    public class Server {
5.    public static void main(String[] args) {
6.        ServerSocket server = null;
7.        try {
8.            //创建ServerSocket并申请服务端口为8088
9.            server = new ServerSocket(8088);
10.            
11.            //侦听客户端的连接
12.            Socket socket = server.accept();
13.            
14.            //客户端连接后,通过该Socket与客户端交互
15.            //获取输入流,用于读取客户端发送过来的消息
16.            InputStream in = socket.getInputStream();
17.            BufferedReader reader
18.                = new BufferedReader(
19.                    new InputStreamReader(
20.                        in,“UTF-8”
21.                    )
22.                );
23.            
24.            //获取输出流,用于向该客户端发送消息
25.            OutputStream out = socket.getOutputStream();
26.            PrintWriter writer
27.                = new PrintWriter(
28.                    new OutputStreamWriter(
29.                        out,“UTF-8”    
30.                    ),true
31.                );
32.            
33.            //读取客户端发送的消息
34.            String message = reader.readLine();
35.            System.out.println(“客户端说:”+message);
36.            
37.            //向客户端发送消息
38.            writer.println(“你好客户端!”);
39.        } catch (Exception e) {
40.            e.printStackTrace();
41.        } finally{
42.            if(server != null){
43.                try {
44.                    server.close();
45.                } catch (IOException e) {
46.                }
47.            }
48.        }
49.    }
50.}
51.
52./
*
53. * Client端应用程序
54. */
55.public class Client {
56.    public static void main(String[] args) {
57.        Socket socket = null;
58.        try {
59.            socket = new Socket(“localhost”,8088);
60.            //获取输入流,用于读取来自服务端的消息
61.            InputStream in = socket.getInputStream();
62.            BufferedReader reader
63.                = new BufferedReader(
64.                    new InputStreamReader(
65.                        in,“UTF-8”
66.                    )
67.                );
68.            
69.            //获取输出流,用于向服务端发送消息
70.            OutputStream out
71.                = socket.getOutputStream();
72.            OutputStreamWriter osw
73.                = new OutputStreamWriter(out,“UTF-8”);
74.            PrintWriter writer
75.                = new PrintWriter(osw,true);
76.            
77.            //向服务端发送一个字符串
78.            writer.println(“你好服务器!”);
79.            
80.            //读取来自客户端发送的消息
81.            String message = reader.readLine();
82.            System.out.println(“服务器说:”+message);
83.        } catch (Exception e) {
84.            e.printStackTrace();
85.        } finally{
86.            try {
87.                if(socket != null){
88.                    //关闭Socket
89.                    socket.close();
90.                }
91.            } catch (IOException e) {
92.                e.printStackTrace();
93.            }
94.        }
95.    }
96.}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值