Java网络编程从入门到精通(18):Socket类的getter和setter方法(2)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java网络编程从入门到精通(18):Socket类的getter和setter方法(2)相关的知识,希望对你有一定的参考价值。

参考技术A

   二 用于获得和设置Socket选项的getter和setter方法

  Socket选择可以指定Socket类发送和接受数据的方式 在JDK *** 有 个Socket选择可以设置 这 个选项都定义在 SocketOptions接口中 定义如下

       public final static int TCP_NODELAY =  x ;    public final static int SO_REUSEADDR =  x ;    public final static int SO_LINGER =  x ;    public final static int SO_TIMEOUT =  x ;    public final static int SO_SNDBUF =  x ;    public final static int SO_RCVBUF =  x ;    public final static int SO_KEEPALIVE =  x ;    public final static int SO_OOBINLINE =  x ;

  有趣的是 这 个选项除了第一个没在SO前缀外 其他 个选项都以SO作为前缀 其实这个SO就是Socket Option的缩写 因此 在Java中约定所有以SO为前缀的常量都表示Socket选项 当然 也有例外 如TCP_NODELAY 在Socket类中为每一个选项提供了一对get和set方法 分别用来获得和设置这些选项

   TCP_NODELAY

   public boolean getTcpNoDelay() throws SocketExceptionpublic void setTcpNoDelay(boolean on) throws SocketException

  在默认情况下 客户端向服务器发送数据时 会根据数据包的大小决定是否立即发送 当数据包中的数据很少时 如只有 个字节 而数据包的头却有几十个字节(IP头+TCP头)时 系统会在发送之前先将较小的包合并到软大的包后 一起将数据发送出去 在发送下一个数据包时 系统会等待服务器对前一个数据包的响应 当收到服务器的响应后 再发送下一个数据包 这就是所谓的Nagle算法 在默认情况下 Nagle算法是开启的

  这种算法虽然可以有效地改善网络传输的效率 但对于网络速度比较慢 而且对实现性的要求比较高的情况下(如游戏 Telnet等) 使用这种方式传输数据会使得客户端有明显的停顿现象 因此 最好的解决方案就是需要Nagle算法时就使用它 不需要时就关闭它 而使用setTcpToDelay正好可以满足这个需求 当使用setTcpNoDelay(true)将Nagle算法关闭后 客户端每发送一次数据 无论数据包的大小都会将这些数据发送出去

     SO_REUSEADDR

   public boolean getReuseAddress() throws SocketException           public void setReuseAddress(boolean on) throws SocketException

  通过这个选项 可以使多个Socket对象绑定在同一个端口上 其实这样做并没有多大意义 但当使用close方法关闭Socket连接后 Socket对象所绑定的端口并不一定马上释放 系统有时在Socket连接关闭才会再确认一下是否有因为延迟面未到达的数据包 这完全是在底层处理的 也就是说对用户是透明的 因此 在使用Socket类时完全不会感觉到

  这种处理机制对于随机绑定端口的Socket对象没有什么影响 但对于绑定在固定端口的Socket对象就可能会抛出 Address already in use JVM_Bind 例外 因此 使用这个选项可以避免个例外的发生

   package mynet;import  *;import java io *;public class Test    public static void main(String[] args)            Socket socket  = new Socket();        Socket socket  = new Socket();        try                    socket setReuseAddress(true);            socket bind(new InetSocketAddress(   ));            System out println( socket getReuseAddress():                     + socket getReuseAddress());            socket bind(new InetSocketAddress(   ));                catch (Exception e)                    System out println( error:  + e getMessage());            try                            socket setReuseAddress(true);                socket bind(new InetSocketAddress(   ));                System out println( socket getReuseAddress():                         + socket getReuseAddress());                System out println( 端口 第二次绑定成功! );                        catch (Exception e )                            System out println(e getMessage());                        

  上面的代码的运行结果如下

   socket getReuseAddress():trueerror:Address already in use: JVM_Bindsocket getReuseAddress():true端口 第二次绑定成功!

  使用SO_REUSEADDR选项时有两点需要注意

     必须在调用bind方法之前使用setReuseAddress方法来打开SO_REUSEADDR选项 因此 要想使用SO_REUSEADDR选项 就不能通过Socket类的构造方法来绑定端口

     必须将绑定同一个端口的所有的Socket对象的SO_REUSEADDR选项都打开才能起作用 如在例程 中 socket 和socket 都使用了setReuseAddress方法打开了各自的SO_REUSEADDR选项

     SO_LINGER

   public int getSoLinger() throws SocketExceptionpublic void setSoLinger(boolean on  int linger) throws SocketException

  这个Socket选项可以影响close方法的行为 在默认情况下 当调用close方法后 将立即返回 如果这时仍然有未被送出的数据包 那么这些数据包将被丢弃 如果将linger参数设为一个正整数n时(n的值最大是 ) 在调用close方法后 将最多被阻塞n秒 在这n秒内 系统将尽量将未送出的数据包发送出去 如果超过了n秒 如果还有未发送的数据包 这些数据包将全部被丢弃 而close方法会立即返回 如果将linger设为 和关闭SO_LINGER选项的作用是一样的

  如果底层的Socket实现不支持SO_LINGER都会抛出SocketException例外 当给linger参数传递负数值时 setSoLinger还会抛出一个IllegalArgumentException例外 可以通过getSoLinger方法得到延迟关闭的时间 如果返回 则表明SO_LINGER是关闭的 例如 下面的代码将延迟关闭的时间设为 分钟

   if(socket getSoLinger() ==  ) socket setSoLinger(true   );

     SO_TIMEOUT

   public int getSoTimeout() throws SocketExceptionpublic void setSoTimeout(int timeout) throws SocketException

  这个Socket选项在前面已经讨论过 可以通过这个选项来设置读取数据超时 当输入流的read方法被阻塞时 如果设置timeout(timeout的单位是毫秒) 那么系统在等待了timeout毫秒后会抛出一个InterruptedIOException例外 在抛出例外后 输入流并未关闭 你可以继续通过read方法读取数据

  如果将timeout设为 就意味着read将会无限等待下去 直到服务端程序关闭这个Socket 这也是timeout的默认值 如下面的语句将读取数据超时设为 秒

   socket setSoTimeout(  *  );

  当底层的Socket实现不支持SO_TIMEOUT选项时 这两个方法将抛出SocketException例外 不能将timeout设为负数 否则setSoTimeout方法将抛出IllegalArgumentException例外

     SO_SNDBUF

   public int getSendBufferSize() throws SocketExceptionpublic void setSendBufferSize(int size) throws SocketException

  在默认情况下 输出流的发送缓冲区是 个字节( K) 这个值是Java所建议的输出缓冲区的大小 如果这个默认值不能满足要求 可以用setSendBufferSize方法来重新设置缓冲区的大小 但最好不要将输出缓冲区设得太小 否则会导致传输数据过于频繁 从而降低网络传输的效率

  如果底层的Socket实现不支持SO_SENDBUF选项 这两个方法将会抛出SocketException例外 必须将size设为正整数 否则setSendBufferedSize方法将抛出IllegalArgumentException例外

     SO_RCVBUF

   public int getReceiveBufferSize() throws SocketExceptionpublic void setReceiveBufferSize(int size) throws SocketException

  在默认情况下 输入流的接收缓冲区是 个字节( K) 这个值是Java所建议的输入缓冲区的大小 如果这个默认值不能满足要求 可以用setReceiveBufferSize方法来重新设置缓冲区的大小 但最好不要将输入缓冲区设得太小 否则会导致传输数据过于频繁 从而降低网络传输的效率

  如果底层的Socket实现不支持SO_RCVBUF选项 这两个方法将会抛出SocketException例外 必须将size设为正整数 否则setReceiveBufferSize方法将抛出IllegalArgumentException例外

     SO_KEEPALIVE

   public boolean getKeepAlive() throws SocketExceptionpublic void setKeepAlive(boolean on) throws SocketException

  如果将这个Socket选项打开 客户端Socket每隔段的时间(大约两个小时)就会利用空闲的连接向服务器发送一个数据包 这个数据包并没有其它的作用 只是为了检测一下服务器是否仍处于活动状态 如果服务器未响应这个数据包 在大约 分钟后 客户端Socket再发送一个数据包 如果在 分钟内 服务器还没响应 那么客户端Socket将关闭 如果将Socket选项关闭 客户端Socket在服务器无效的情况下可能会长时间不会关闭 SO_KEEPALIVE选项在默认情况下是关闭的 可以使用如下的语句将这个SO_KEEPALIVE选项打开

   socket setKeepAlive(true);

     SO_OOBINLINE

    public boolean getOOBInline() throws SocketException public void setOOBInline(boolean on) throws SocketException

  如果这个Socket选项打开 可以通过Socket类的sendUrgentData方法向服务器发送一个单字节的数据 这个单字节数据并不经过输出缓冲区 而是立即发出 虽然在客户端并不是使用OutputStream向服务器发送数据 但在服务端程序中这个单字节的数据是和其它的普通数据混在一起的 因此 在服务端程序中并不知道由客户端发过来的数据是由OutputStream还是由sendUrgentData发过来的 下面是sendUrgentData方法的声明

   public void sendUrgentData(int data) throws IOException

  虽然sendUrgentData的参数data是int类型 但只有这个int类型的低字节被发送 其它的三个字节被忽略 下面的代码演示了如何使用SO_OOBINLINE选项来发送单字节数据

   package mynet;import  *;import java io *;class Server    public static void main(String[] args) throws Exception            ServerSocket serverSocket = new ServerSocket( );        System out println( 服务器已经启动 端口号 );        while (true)                    Socket socket = serverSocket accept();            socket setOOBInline(true);            InputStream in = socket getInputStream();            InputStreamReader inReader = new InputStreamReader(in);            BufferedReader bReader = new BufferedReader(inReader);            System out println(bReader readLine());            System out println(bReader readLine());            socket close();            public class Client    public static void main(String[] args) throws Exception            Socket socket = new Socket(   );        socket setOOBInline(true);        OutputStream out = socket getOutputStream();        OutputStreamWriter outWriter = new OutputStreamWriter(out);        outWriter write( );              // 向服务器发送字符 C         outWriter write( hello world\\r\\n );        socket sendUrgentData( );        // 向服务器发送字符 A         socket sendUrgentData( );        // 向服务器发送字符 B         outWriter flush();        socket sendUrgentData( );       // 向服务器发送汉字 中         socket sendUrgentData( );        socket sendUrgentData( );       // 向服务器发送汉字 国         socket sendUrgentData( );        socket close();    

  由于运行上面的代码需要一个服务器类 因此 在加了一个类名为Server的服务器类 关于服务端套接字的使用方法将会在后面的文章中详细讨论 在类Server类中只使用了ServerSocket类的accept方法接收客户端的请求 并从客户端传来的数据中读取两行字符串 并显示在控制台上

   测试

  由于本例使用了 因Server和Client类必须在同一台机器上运行

  运行Server

   java mynet Server

  运行Client

   java mynet Client

  在服务端控制台的输出结果

   服务器已经启动 端口号 ABChello world中国

  在ClienT类中使用了sendUrgentData方法向服务器发送了字符 A ( )和 B ( ) 但发送 B 时实际发送的是 由于sendUrgentData只发送整型数的低字节 因此 实际发送的是 十进制整型 的二进制形式如图 所示

图   十进制整型 的二进制形式

  从图 可以看出 虽然 分布在了两个字节上 但它的低字节仍然是

  在Client类中使用flush将缓冲区中的数据发送到服务器 我们可以从输出结果发现一个问题 在Client类中先后向服务器发送了 C hello world r n A B 而在服务端程序的控制台上显示的却是ABChello world 这种现象说明使用sendUrgentData方法发送数据后 系统会立即将这些数据发送出去 而使用write发送数据 必须要使用flush方法才会真正发送数据

  在Client类中向服务器发送 中国 字符串 由于 中 是由 和 两个字节组成的 而 国 是由 和 两个字节组成的 因此 可分别发送这四个字节来传送 中国 字符串

lishixinzhi/Article/program/Java/hx/201311/26387

Java并发编程从入门到精通 - 第6章:线程池

1、什么是线程池(为什么使用线程池):
2、Executor框架介绍:
  Java 5中引入的,其内部使用了线程池机制,在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭(使用该框架来创建线程池),可以简化并发编程的操作;
  Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等;
  ExecutorService接口继承自Executor接口,提供了更丰富的实现多线程的方法,一般用该接口来实现和管理多线程;
  ExecutorService的生命周期包括三种状态:运行、关闭、终止;创建后便进入运行状态;当调用了shutdown()方法时,便进入关闭状态,此时意味着      ExecutorService不再接受新的任务,但它还在执行已经提交了的任务;当所有已经提交了的任务执行完后,便到达终止状态;如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可;
  更详细描述看:http://blog.csdn.net/ns_code/article/details/17465497

技术分享图片
 1 /**
 2  * Executor执行Runnable任务
 3  */
 4 package thread05;
 5 
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class ExecutorsTest01
10 {
11     public static void main(String[] args)
12     {
13         // ExecutorService executorService = Executors.newCachedThreadPool();
14         // ExecutorService executorService = Executors.newFixedThreadPool(4);
15         ExecutorService executorService = Executors.newSingleThreadExecutor();
16         
17         // 创建5个任务并执行
18         for(int i=0;i<5;i++)
19         {
20             executorService.execute(new RunnableTest2());
21         }
22         
23         // 启动一次顺序关闭,执行以前提交的任务,但不接受新任务
24         executorService.shutdown();
25     }
26 }
27 
28 class RunnableTest2 implements Runnable
29 {
30     // 具体的业务逻辑;一旦RunnableTest2实例对象传给ExecutorService的execute方法, 则该方法(任务)自动在一个线程上执行
31     @Override
32     public void run()
33     {
34         System.out.println(Thread.currentThread().getName() + "线程被调用了");
35     }
36     
37 }
Executor执行Runnable任务
技术分享图片
 1 /**
 2  * Executor执行Callable任务
 3  */
 4 package thread05;
 5 
 6 import java.util.ArrayList;
 7 import java.util.List;
 8 import java.util.concurrent.Callable;
 9 import java.util.concurrent.ExecutionException;
10 import java.util.concurrent.ExecutorService;
11 import java.util.concurrent.Executors;
12 import java.util.concurrent.Future;
13 
14 public class ExecutorsTest02
15 {
16     public static void main(String[] args)
17     {
18         ExecutorService executorService = Executors.newCachedThreadPool();
19         List<Future<String>> futureList = new ArrayList<Future<String>>();
20         
21         // 创建10个任务并执行
22         for(int i=0;i<10;i++)
23         {
24             // 使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
25             Future<String> future = executorService.submit(new CallableTest2(i + ""));
26             // 将任务执行结果存储到List中
27             futureList.add(future);
28         }
29         
30         // 遍历任务的结果 
31         for(Future<String> f : futureList)
32         {
33             try
34             {
35                 // Future返回如果没有完成,则一直循环等待,直到Future返回完成
36                 while(!f.isDone());
37                 // 打印各个线程(任务)执行的结果 
38                 System.out.println(f.get());
39             }
40             catch (InterruptedException | ExecutionException e)
41             {
42                 e.printStackTrace();
43             }
44             finally
45             {
46                 // 启动一次顺序关闭,执行以前提交的任务,但不接受新任务
47                 executorService.shutdown();
48             }
49         }
50         
51     }
52 }
53 
54 class CallableTest2 implements Callable<String>
55 {
56     private String id;
57     
58     public CallableTest2(String id)
59     {
60         this.id = id;
61     }
62 
63     // 具体的业务逻辑;一旦CallableTest2实例对象传给ExecutorService的submit方法, 则该方法(任务)自动在一个线程上执行
64     @Override
65     public String call() throws Exception
66     {
67         System.out.println("call()方法被自动调用,当前线程名称" + Thread.currentThread().getName());
68         return "call()方法被自动调用,任务的返回值:任务id:" + id + ",执行任务的线程名称:" + Thread.currentThread().getName();
69     }
70     
71 }
Executor执行Callable任务

3、三种生成常用线程池的静态工厂方法(三种自带的线程池):
  newSingleThreadExecutor(SingleThreadExecutor);
  newCachedThreadPool(CachedThreadPool);
  newFixedThreadPool(FixedThreadPool);
4、newSingleThreadExecutor的使用:
  创建一个单线程的线程池,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务;如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它;此线程池保证所有任务的执行顺序按照任务的提交顺序执行;
  适用场景:适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景;

技术分享图片
 1 /**
 2  * newSingleThreadExecutor的使用
 3  */
 4 package thread05;
 5 
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class NewSingleThreadExecutorTest01
10 {
11     public static void main(String[] args)
12     {
13         ExecutorService executorService = Executors.newSingleThreadExecutor();
14         
15         for(int i=0;i<5;i++)
16         {
17             final int no = i;
18             
19             // 生成一条任务
20             Runnable runnable = new Runnable()
21             {
22                 @Override
23                 public void run()
24                 {
25                     try
26                     {
27                         System.out.println("into:" + no);
28                         Thread.sleep(1000);
29                         System.out.println("end:" + no);
30                     } 
31                     catch (InterruptedException e)
32                     {
33                         e.printStackTrace();
34                     }
35                 }
36             };
37             
38             // 将任务放到线程池中进行执行
39             executorService.execute(runnable);
40         }
41         
42         // 所有任务执行完毕之后,关闭线程池
43         executorService.shutdown();
44         
45         System.out.println("Thread Main End!");
46     }
47 }
newSingleThreadExecutor的使用

5、newCachedThreadPool的使用:
  创建一个缓存池大小可根据需要伸缩的线程池,但是在以前构造的线程可用时可重用它们;对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能;调用execute将重用以前构造的线程(如果线程可用);如果现有线程没有可用的,则创建一个新线程并添加到池中;终止并从缓存中移除那些已有60s未被使用的线程;因此,长时间保持空闲的线程池不会使用任何资源;
  适用场景:是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;

技术分享图片
 1 /**
 2  * newCachedThreadPool的使用
 3  */
 4 package thread05;
 5 
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class NewCachedThreadPoolTest01
10 {
11     public static void main(String[] args)
12     {
13         ExecutorService executorService = Executors.newCachedThreadPool();
14         
15         for(int i=0;i<10;i++)
16         {
17             final int no = i;
18             
19             Runnable runnable = new Runnable()
20             {
21                 @Override
22                 public void run()
23                 {
24                     try
25                     {
26                         System.out.println("into:" + no);
27                         Thread.sleep(1000L);
28                         System.out.println("end:" + no);
29                     } 
30                     catch (InterruptedException e)
31                     {
32                         e.printStackTrace();
33                     }
34                 }
35             };
36             
37             executorService.execute(runnable);
38         }
39         
40         executorService.shutdown();
41         
42         System.out.println("Thread Main End!");
43     }
44 }
newCachedThreadPool的使用

6、newFixedThreadPool的使用:
  创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程;在任意点,在大多数nThreads线程会处于处理任务的活动状态;如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待;如果在关闭前的执行期间而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要);在某个线程被显式地关闭之前,池中的线程将一直存在;
  适用场景:适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景;适用于负载比较重的服务器;

技术分享图片
 1 /**
 2  * newFixedThreadPool的使用
 3  */
 4 package thread05;
 5 
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 
 9 public class NewFixedThreadPool
10 {
11     public static void main(String[] args)
12     {
13         ExecutorService executorService = Executors.newFixedThreadPool(5);
14         
15         for(int i=0;i<20;i++)
16         {
17             final int no = i;
18             
19             Runnable runnable = new Runnable()
20             {
21                 @Override
22                 public void run()
23                 {
24                     try
25                     {
26                         System.out.println("into:" + no);
27                         Thread.sleep(1000L);
28                         System.out.println("end:" + no);
29                     } 
30                     catch (InterruptedException e)
31                     {
32                         e.printStackTrace();
33                     }
34                 }
35             };
36             
37             executorService.execute(runnable);
38         }
39         
40         executorService.shutdown();
41         
42         System.out.println("Thread Main End!");
43     }
44 }
newFixedThreadPool的使用

7、线程池的好处;
7.1、合理利用线程池能够带来4个好处:
(1)、降低资源消耗:通过重复利用已创建的线程,降低线程创建和销毁造成的消耗;
(2)、提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行;
(3)、提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控;
(4)、防止服务器过载,形成内存溢出,或者CPU耗尽;
7.2、线程池技术提高服务器程序的性能:
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力;但如果对多线程应用不当,会增加对单个任务的处理时间;
7.3、线程池的应用范围:
(1)、需要大量的线程来完成任务,且完成任务的时间比较短:Web服务器完成网页请求这样的任务,使用线程池技术是非常合适的;因为单个任务小,而任务数量巨大;
8、线程池的工作机制及其原理:
9、自定义线程池和ExecutorService:

技术分享图片
 1 /**
 2  * 自定义线程池
 3  */
 4 package thread05;
 5 
 6 import java.util.concurrent.ArrayBlockingQueue;
 7 import java.util.concurrent.BlockingQueue;
 8 import java.util.concurrent.ThreadPoolExecutor;
 9 import java.util.concurrent.TimeUnit;
10 
11 public class ThreadPoolTest01
12 {
13     public static void main(String[] args)
14     {
15         // 创建等待队列 
16         BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(20);
17         // 创建线程池,池中保存的线程数为3,允许的最大线程数为5  
18         ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 50, TimeUnit.MILLISECONDS,bq);
19         
20         // 创建七个任务
21         Runnable r1 = new RunnableTest3();
22         Runnable r2 = new RunnableTest3();
23         Runnable r3 = new RunnableTest3();
24         Runnable r4 = new RunnableTest3();
25         Runnable r5 = new RunnableTest3();
26         Runnable r6 = new RunnableTest3();
27         Runnable r7 = new RunnableTest3();
28         
29         // 每个任务会在一个线程上执行
30         pool.execute(r1);
31         pool.execute(r2);
32         pool.execute(r3);
33         pool.execute(r4);
34         pool.execute(r5);
35         pool.execute(r6);
36         pool.execute(r7);
37         
38         // 关闭线程池 
39         pool.shutdown();
40     }
41 }
42 
43 class RunnableTest3 implements Runnable
44 {
45     @Override
46     public void run()
47     {
48         System.out.println(Thread.currentThread().getName() + "正在执行...");
49         
50         try
51         {
52             Thread.sleep(1000);
53         }
54         catch (InterruptedException e)
55         {
56             e.printStackTrace();
57         }
58     }
59     
60 }
自定义线程池

10、线程池在工作中的错误使用:
(1)、分不清线程池是单例还是多对象:线程池一定要在合理的单例模式下才有效;就是说不要多次创建线程池;
(2)、线程池数量设置很大,请求过载:
(3)、注意死锁问题:































以上是关于Java网络编程从入门到精通(18):Socket类的getter和setter方法(2)的主要内容,如果未能解决你的问题,请参考以下文章

Java从入门到精通

Java网络编程从入门到精通:使用getCanonicalHostName方法获得主机名

Java并发编程从入门到精通-总纲

牛逼!Java 从入门到精通,超全汇总版

牛逼!Java 从入门到精通,超全汇总版

牛逼!Java 从入门到精通,超全汇总版