17 网络与线程

网络联机


前言:Java程序可以向外拓展并与其他程序产生联系。所有网络运行的底层细节已经由java.net函数库处理掉了,传送与接收数据只不过是链接上使用不同链接串流的输入/输出而已。使用socket连接世界。


聊天程序概述


客户端必须要认识服务器。服务器必须认识所有的客户端

工作方式

1.客户端连接到服务器

2.服务器建立连接并把客户端加到来宾清单中

3.其他用户连接

4.用户A送出信息到服务器

5.服务器将信息分发给所有客户端


客户端连接,传送和接收

1.用户通过建立socket连接来连接服务器

连接196.164.1.103的5000端口号

2.传送

writer.printIn(aMessage)

3.接收

String s=reader.readline();


Socket连接


创建该连接须知道服务器的两类信息:其一IP地址(服务器地址),其二端口号(哪个端口接发数据)


socket代表两台机器之间网络连接的对象


底层细节实在底层的网络设备(一种让运行在JVM上的程序能找到方法并通过实际硬件在机器之间传送数据的机制)中处理的。


socket连接的建立代表两台机器之间存有对方的信息,包括网络地址和TCP端口号


TCP端口号


16位,用于识别服务器上特定程序的数字


HTTP(网页服务器)的端口号是80,Telnet服务器的端口号是23,0-1023被保留给已知程序的特定服务,端口号代表在服务器上应用程序的逻辑识别


使用BufferReader从socket读取数据


用串流通过socket连接来沟通,不用在意链接串流的上游是什么(来自何方)


1)建立服务器的socket连接

2)建立连接到socket上底层输入串流的InputStreamReader

3)建立BufferReader来读取


用PrintWriter写数据到socket上


每次写入一个string,所以用PrintWriter


1.建立socket连接

Socket chatSocket = new Socket("12.0.0.1",5000);


2.建立连接到socket的PrintWriter


PrintWriter writer = new PrintWriter(chatSocket.getOutputStream());


3.写入数据

writer.printIn("you are kind") //送出数据加上换行

writer.print("go")             //送出数据不加换行


编写简单的服务器应用程序


需要一对socket,一个等待用户请求(当用户创建socket时)的ServerSocket以及与用户通信的Socket


1)服务器应用程序对特定端口创建出ServerSocket(监听来自客户端的请求)


ServerSocket serverSock = new ServerSocket(4242);


ServerSocket会监听客户端对4242端口的请求


2)客户端对服务器应用程序建立Socket连接


Socket sock = new Socket("190.165.1.103",4242);


3)服务器创建出与客户端通信的新Socket


Socket sock = serverSock.accept();


accept方法会在等待用户的Socket连接时闲置,当用户连接后,该方法返回一个Socket(不同端口)以便与客户端通信。Socket与ServerSocket的端口不要,所以ServerSocket可以空出等待其他用户。


总结


1)建立BufferReader链接InputStreamReader与来自Socket的输入串流以读取服务器的文本数据。


2)InputStreamReader是个转换字节成字符的桥梁,主要用来链接BufferReaderSocket与底层的Socket输入串流。


3)建立直接链接Socket输出串流的PrintWriter请求print()方法来送出String给服务器。


线程


Thread是表示线程的类,Thread对象代表线程,需要启动新的线程,就建立Thread的实例


建立新的线程对象,启动新的线程


线程是独立的线程,每个线程都代表独立的执行空间,每个Java应用程序都会启动一个主线程(将main()放在执行空间的最开始处,JVM会负责主线程的启动以及垃圾收集的系统用线程)


当有超过一个以上的执行空间(多个线程),表面上是好几件事同时发生,实际只是执行动作在执行空间快速地来回交换。(Java也是在底层操作系统执行的进程,一旦轮到Java执行,JVM会执行执行空间中最上面的部分,在100毫秒内,目前执行程序代码会被切换到不同空间上的不同方法)


线程记录目前执行空间做到哪里。


启动新的线程


1)建立Runable对象(线程的任务)


Runable是个接口,要编写实现接口的类


2)建立thread对象(工人)并赋值Runable对象(任务)


告诉thread对象在执行空间执行何种方法


Thread myThread = new Thread(threadJob);


3)启动thread


当线程启动后,把Runable对象的方法放置到执行空间中


Runable对象带有会放在执行空间的第一项方法:run()


每个Thread对象需要一个任务执行,一个可以放在执行空间的任务,该任务是新线程空间上的第一个方法(run()是新线程所执行的第一项方法)


Runable接口只有一个方法且为public属性:public void run()


实现Runable接口来建立执行任务


public class MyRunable implements Runable

{

  public void run()

  {

   go();

   }

  public void go()

  {

   doMore();

   }

}


class ThreadTester{

 public static void main(String[] args){

  Runable threadJob = new MyRunable();

  Thread myThread = new Thread(threadJob);

   myThread.start();

  }

}


主线程:myThread.start()以及main()


新建线程:doMore()以及go()


新建线程的三个状态


1)新建状态

Thread t = new Thread(r);

创建thread实例,但未启动


2)t.start();

启动线程,成为可执行状态,此时,该线程已经建立好执行空间,只要轮到它,即开始


3)执行中


靠JVM的线程调度机制决定,单处理器机器只能有一个执行中的线程


一旦线程进入可执行状态,会在可执行与执行中两种状态中切换,同时会有第三者状态:暂时不可执行(被阻塞状态)


JVM调度器会因为某原因把线程暂时一阵子,如线程执行到等待socket输入串流的程序处,但没有数据可供读取,或者线程调用某个locked的对象的方法,要等待lock该对象的线程放开此对象才能继续下去,诸如此类都会阻塞线程。


线程调度器


指定线程状态的切换特别是指定线程处于等待被执行状态或者是暂时阻塞。


无法控制及确定调度,至少不能依靠调度的特定行为来保持执行的正确性(如每个线程平均分配时间),不同JVM上调度器处理行为不同


Thread.sleep(2000);//最好包含在try/catch块中,可能出现InterruotedException


InterruotedException异常是用来支持线程间通信的机制,通过sleep方法你能保证该线程能进入可执行状态


潜在威胁


多个线程存取单一对象的数据


解决方法:配备一把锁及钥匙


synchronized关键词来修饰方法使其每次只能被单一线程存取,防止两个线程同时进入同一对象的同一方法。


synchronized关键词代表线程需要钥匙来存取被同步化过(synchronized)的线程


要保护数据就把作用在数据上的方法给同步化


使用对象的锁


每个对象都有单一的锁,单一的钥匙,明确锁是在对象上,而不是方法上,锁住的不是数据,而是存取数据的方法。对象有两个同步化方法,表示两个线程无法进入同一个方法,也表示两个线程无法进入不同的方法(同步化方法)。


对象就算有多个同步化的方法,也只有一个锁,锁住整个对象的同步化方法。


每个Java对象都有一把锁,每个锁只有一把钥匙,通常都没上锁


对象的锁只会在同步化的方法上起作用,当对象有一个以上的同步化方法时,线程只有取得对象的锁的钥匙(JVM决定)才能进入同步化的方法


当线程持有钥匙,没有其他线程可以进入该对象的同步化方法,因为每个对象只有一个钥匙。


丢失更新的问题


将方法同步化可以解决此问题,让方法内的步骤成为不同分割的单元,确保其他线程调用该方法之前所有的步骤完成。


同步化的代价


同步化程序会有查询钥匙等性能上的损耗,其次让程序因为同步并行的问题(强制线程排队等着执行方法)而慢下来,注意同步化的规模可以小于方法全部(修饰方法的部分指令而非全部)


public void go()

{

  do1();

  synchronized(this)

  {

  go();

  fight();

  }

}

不把整个方法设定为同步化,只会使用参数所指定的对象的锁来做同步化,通常以当前对象(this)来同步化


同步化的死亡阴影


使用同步化的程序注意死锁情况的出现,死锁是因为两个线程互相持有对方正在等待的东西,没有方法可脱离此情况。


只要两个线程和两个对象就可以引起死锁。


数据库有与同步化相似的上锁机制,但数据库交易管理系统有时能处理死锁,因为它们有事务回滚机制来复原不能完成的交易。


private static Accum a = new Accum()

创建Accum的静态实例


书海拾荒


1.Java API的网络功能包为java.net


2.程序中GUI码比输入/输出和网络码多不少


3.不同程序不可以共享端口


4.127.0.0.1代表本机


5.Thread对象不能重复使用,一旦线程的任务完成后,该线程不能再重新启动,thread对象可能还在堆上,一般还能接受某些方法的调用,但也仅仅剩下对象本身,失去线程的可执行性


6.可通过用Thread的子类来覆盖run()这个方法,再调用Thread的无参数构造函数来创建出新的线程


7.sleep()方法强制线程进入等待状态


8.setName()方法可以为线程命名


9.要让对象在线程上有足够的安全性,要判断出那些指令不能被分割执行


10.线程优先级可以对调度器产生影响,但没有绝对的保证


11.静态方法运行在类上,而不是实例上,类也有锁,当对静态方法同步化时,Java使用类本身的锁,若同一类有两个被同步化的静态方法,则线程需要取得类的锁,才能进入这些方法。






猜你喜欢

转载自blog.csdn.net/lwz45698752/article/details/79162146
17