第25天多线程、网络编程

多线程、网络编程

  1. JDK5的锁和等待唤醒

  1. Lock接口介绍

    在JDK5之前,同步和锁绑定在一起,同时锁和等待唤醒也绑定在一起。在前学习同步代码块或同步方法的时候,线程要进入同步首先需要隐式的获取同步上的锁对象。只有持有锁对象的线程才能进入到同步中。线程出同步的时候会隐式释放锁。锁的获取和释放我们并无法操作。

     

    到JDK5之后,将同步上的锁的获取和释放修改为由编程人员自己控制。升级后将锁单独的封装在Lock接口中。

     

     

    JDK5中的Lock接口,它其实是在代替JDK5之前的同步方法或同步代码块。

     

    将JDK5之前的隐式获取锁,修改为手动获取:

    同时将隐式释放锁,也修改为手动操作:

 

    如果使用JDK5的Lock接口,要求必须使用下面的模版代码:

    

手动调用lock方法获取锁

try{

        书写的被同步的代码

}finally{

    手动的调用unlock方法释放锁

}

  1. Lock接口使用

/*

* 演示使用JDK5的Lock接口完成线程的同步

*/

class Ticket implements Runnable{

    

    private int num = 100;

    

    // 定义Lock接口的对象

    private Lock loc = new ReentrantLock();

    public void run(){

        while(true){

            // 手动获取锁

            loc.lock(); // 从这里开始的代码就会被同步

            try{

                if( num > 0 ){

                    System.out.println(Thread.currentThread().getName()+"....."+num);

                    num--;

                }

            }finally{

                // 手动释放锁

                loc.unlock(); // 同步结束

            }

        }

    }

}

public class ThreadTest {

    public static void main(String[] args) {

        

        Ticket task= new Ticket();

        

        Thread t = new Thread( task );

        Thread t2 = new Thread( task );

        

        t.start();

        t2.start();

    }

}

  1. Condition接口介绍

 

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

 

wait方法被下面方法代替

 

notify 被下面方法代替

 

notifyAll:

  1. Condition接口使用

/*

* 单生产单消费 使用JDK5中的Lock接口和Condition接口替换

*/

// 专门定义一个负责描述保存和取出线程的操作的资源类

class Resource {

    

    // 定义成员变量,充当保存和取出的容器

    private Object[] objs = new Object[1];

    

    // 定义JDK5中的Lock接口对象

    private Lock loc = new ReentrantLock();

    

    // 定义监视保存线程的等待和唤醒对象

    private Condition pro_con = loc.newCondition();

    // 定义监视取消线程的等待和唤醒对象

    private Condition con_con = loc.newCondition();

    // 提供一个保存数据的方法

    public void save( Object obj ) {

        loc.lock();

        try{

            // 判断能否操作容器

            if( objs[0] != null ){

                // 判断成立 说明保存的线程不能保存,需要等待

                try { pro_con.await(); } catch( InterruptedException e ){ }

            }

            // 将数据保存到容器中

            objs[0] = obj;

            System.out.println(Thread.currentThread().getName()+" 正在保存的数据是:"+objs[0]);

            con_con.signal();

        }finally{

            loc.unlock();

        }

        

    }

    

    // 提供一个取出数据的方法

    public void get(){

        loc.lock();

        try{

            if( objs[0] == null ){

                // 判断成立,说明没有数据,不能取出,需要等待

                try { con_con.await(); } catch( InterruptedException e ){ }

            }

            // 打印取出的数据

            System.out.println(Thread.currentThread().getName() + " 正在取出的数据::::" +objs[0]);

            objs[0] = null;

            // 消费结束,需要通知生产者线程

            pro_con.signal();

        }finally{

            loc.unlock();

        }

    }

}

 

// 书写保存数据线程的任务

class Productor implements Runnable {

 

    private Resource r ;

    // 书写构造方法的目的是在明确线程的任务的时候,

    // 可以通过构造方法告诉线程的任务它们操作的资源对象

    public Productor( Resource r){

        this.r = r ;

    }

    public void run() {

        // 给资源中保存数据

        while(true){

            r.save( "苹果" );

        }

    }

}

 

// 书写取出数据的线程任务

class Consumer implements Runnable {

    

    private Resource r ;

    // 书写构造方法的目的是在明确线程的任务的时候,

    // 可以通过构造方法告诉线程的任务它们操作的资源对象

    public Consumer( Resource r){

        this.r = r ;

    }

    public void run() {

        // 从资源中取出数据

        while(true){

            r.get();

        }

    }

}

 

// 测试类

public class ThreadTest {

    public static void main(String[] args) {

 

        // 先要创建一个保存和取出操作的共同的资源对象

        Resource r = new Resource();

        

        // 创建线程的任务对象

        Productor pro = new Productor( r );

        Consumer con = new Consumer( r );

        

        // 创建线程对象 ,这个线程负责保存数据

        Thread t = new Thread( pro );

        

        // 创建线程对象 ,这个线程负责取出数据

        Thread t2 = new Thread( con );

        

        // 开启线程

        t.start();

        t2.start();

    }

}

  1. 多线程的细节

  1. wait和sleep区别

    wait:需要被唤醒,sleep时间到自然醒

    wait:它只能放在同步中,sleep可以在同步中,也可以不在。

    wait:可以指定时间,但大部分情况我们是不指定时间,而sleep必须指定时间

    wait:它让线程等待之后,线程会将锁是否,而sleep如果在同步中,它让线程休眠,这时线程是不会放锁。

  2. 同步能不能添加在run方法上

    按照语法规则,是可以在run方法上书写同步的。但是实际开发中是不可能在run方法上添加同步的。

    同步方法在线程执行的时候,每个线程要进入这个方法都需要获取同步锁,如果获取不到锁,线程就无法去执行这个方法。而我们知道run方法是线程要执行的任务方法,如果线程都进不去run方法,相当于线程根本就没有拿到自己的任务。

  3. 线程组

    线程组:将多个操作行为相同的线程,可以划分到一个组中,然后我们不要去面对每个线程,而只要通过这个组操作组里面的所有线程。

     

    如何将线程添加到线程组中:

    在创建Thread对象的时候,可以根据Thread的构造方法将线程添加到对应的线程组中。

  4. 线程优先级

    Thread类中的API:每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。

     

    我们在创建线程的时候,可以指定每个线程的优先级,如果没有指定的话,这个线程优先级默认和执行创建线程时所在的线程优先级相同。主线程的优先级默认为5.

     

    线程的优先级: 1 ~ 10 ,一般建议如果需要设计线程的优先级设置为 1 或 5 或 10。

    修改线程的优先级:

    获取线程的优先级:

    

    

    

    注意:高优先级的线程被CPU执行的概率会被低优先级的高,但是并不意味着低优先级的线程CPU不执行。

  1. 守护线程

    守护线程:每个线程都可以或不可以标记为一个守护程序。当且仅当创建线程是守护线程时,新线程才是守护程序。

    前台线程:从主程序开始执行的那个线程一定是前台线程。主线程属于前台线程(非守护线程)。

    守护线程也被称为后台线程,或者被称为用户线程,这些线程它们依然可以去运行线程的任务,但是如果程序中的前台线程全部结束,这时不管后台(守护)线程的任务是否结束,守护线程会自动停止运行。

     

     

/*

* 演示守护线程

*/

class Demo implements Runnable{

    

    public void run(){

        for( int i = 0 ; ; i++){

            System.out.println(Thread.currentThread().getName()+"...."+i);

        }

    }

}

 

public class TheradDemo {

    public static void main(String[] args) {

        // 执行main方法的线程,一定是前台线程

        

        Demo d = new Demo();

        

        // 下面创建的线程和主线程都属于前台线程

        Thread t = new Thread(d);

        Thread t2 = new Thread(d);

        

        // 人为的将创建出来的线程修改为后台(守护线程),一定要在开启线程之前修改

        t.setDaemon(true);

        t2.setDaemon(true);

        

        t.start();

        t2.start();

        

        for( int i = 0 ; i < 20 ; i++){

            System.out.println(Thread.currentThread().getName()+"-----"+i);

        }

    }

}

  1. 定时器介绍

    定时器:让设备可以定时的去(重复)执行某个功能。

    

定时器类:

    

    

    构造方法:

    

    

    下面的方法是给Timer(定时器)对象绑定任务

    

 

    /*

* 演示定时器

*/

public class TimerDemo {

    public static void main(String[] args) {

        

        // 创建定时器对象

        Timer t = new Timer();

        

        /*

         * 给定时器指定任务

         * schedule(TimerTask task, long delay, long period)

         * TimerTask task : 定时器要执行的任务

         * long delay : 当前时间往后延时多久,开始执行任务

         * long period : 到执行任务的时间点之后,每隔多久时间重复执行一次任务

         */

        final Random r = new Random();

        t.schedule( new TimerTask(){

            @Override

            public void run() {

                // 在run方法中书写具体Java代码完成定时器需要做事情

                System.out.println("你好");

                File file = new File("e:/abc/"+r.nextInt()+".txt");

                try {

                    file.createNewFile();

                } catch (IOException e) {

                    e.printStackTrace();

                }

            }

        }, 1000, 1000);        

    }

}

  1. 网络介绍

  1. 网络介绍(了解)

    网络:通过一些中间的设备,可以将终端设备连接在一起,并且这些终端设备之间可以进行数据的交互。

     

    中间设备:网线、交换机、路由器、无线设备、卫星等。

     

    终端设备:电脑、服务器、手机、智能家电等。

     

    网络分成:局域网、城域网、广域网。

     

  2. 网络模型介绍(了解)

    由于网络中的设备太多,于是将工作在网络中的不同设备功能(职责)进行划分,将网络化成七层(网络模型):

    OSI(Open System Interconnection开放系统互连)参考模型 。

    1.层物理层:主要定义物理设备标准,如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等。它的主要作用是传输比特流(就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0,也就是我们常说的数模转换与模数转换)。这一层的数据叫做比特。 2.层数据链路层:主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装。常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输。 3.层网络层:主要将从下层接收到的数据进行IP地址(例192.168.0.1)的封装与解封装。在这一层工作的设备是路由器,常把这一层的数据叫做数据包。 4.层传输层:定义了一些传输数据的协议和端口号(WWW端口80等),如:TCP(传输控制协议,传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据),UDP(用户数据报协议,与TCP特性恰恰相反,用于传输可靠性要求不高,数据量小的数据,如QQ聊天数据就是通过这种方式传输的)。 主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。常常把这一层数据叫做段。 5.会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名) 6.表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等(也就是把计算机能够识别的东西转换成人能够能识别的东西(如图片、声音等)。 7.应用层: 主要是一些终端的应用,比如说FTP(各种文件下载),WEB(IE浏览),QQ之类的(可以把它理解成我们在电脑屏幕上可以看到的东西.就是终端应用)。

  3. 网络三要素(重点掌握)

    1. IP介绍(重点)

    IP:它本质是连接在网络中那个终端设备的一个数字标识。如果我们需要进行网络通信,这时我们就必须知道通信对方的IP地址是多少,才能和对方进行通信。

     

    IP地址:

        它被分成5类:A、B、C、D、E。

     

    平时我们上网的时候,从来不手动设置IP地址,这时IP地址由宽带提供商提供。

     

    如何查询自己电脑的IP地址:

     

    IP地址:它是连接在网络中终端设备的唯一的数字标识。

    MAC地址:每个网卡都有一个网卡地址,这个地址也被称为物理地址。每个网卡在生产的时候就已经固定死(全球唯一)。

    网卡是设备的物理标识,它不容易记忆,也不容器操作。但是我们的确可以通过网卡地址找到需要通信的设备。但是实际应用中,程序不会根据网卡地址通信。

    IP地址,逻辑地址:它可以分配给任何一个连接在网络中的设备。

     

    网卡地址和IP地址:

        网卡地址:可以理解成手机的唯一编号(*#06#)。

        IP地址:可以理解成手机号码。

     

     

    了解的内容:其实每个网卡在出厂的时候已经分配了一个IP地址。

    这个地址:本地回环地址。127.0.0.1

    1. 域名解析(了解)

    IP地址是我们唯一访问网络中设备的路径。平时我们上网的时候根本没有使用IP地址访问任何网络中的信息。

    平时在浏览器的地址栏中输入域名(www.baidu.com),通过域名访问网络中的资源数据。

     

    当我们在浏览器的地址栏中输入域名之后,其实背后对应的将域名解析成对应的IP地址,最后还是在使用IP地址访问。

     

    关于域名解析成IP地址:域名解析

        1、先通过操作系统本地的hosts文件中查询当前的域名有没有对应的IP地址,如果有就会使用hosts文件中的IP,作为当前域名的对应的IP地址访问。

            C:\Windows\System32\drivers\etc\hosts

            

        2、如果第一步解析IP地址失败,这时系统会自动到DNS服务器上解析。DNS服务器上会有全球所有的网站对应的IP地址。查询到域名对应的ip地址,就会使用这个ip地址访问。如果DNS服务器解析失败,这时说明域名有问题。

    1. 端口介绍(重点)

    通过IP地址,可以访问到连接在网络中的某个终端设备。设备中肯定会运行很多的应用程序(软件),最终我们需要和设备中的某个应用软件进行数据交互。

     

    运行在终端设备中的任何应用软件,它们在这个设备中都有一个唯一的标识,这个标识我们称为软件在此设备中的端口号。

     

    端口号:从0~65535之间,一般0~1024之间的端口号,是给系统软件使用的时候,因此我们自己编程中,开发的程序需要绑定端口的时候,建议使用1025~65535之间的端口号。

     

    总结:

        IP:它本质是网络中的电脑的标识。

        端口:电脑中运行的软件的标识。

    1. 协议介绍(重点)

    协议:双方需要共同遵守的规则。在网络编程中,协议是在规范通信双方需要遵守的数据交互格式。

    我们学习的网络编程中的协议,主要是OSI模型中传输层协议(底层协议):

        

    UDP协议:用户数据报文包协议。

            通信双方不同关心对方是否在接收数据。发送方只管发送数据,接收方如果在,就可以接收到数据,如果不在,数据就会被丢弃。

            UDP协议,传输快,效率高,但不安全。不能发送大数据。

            UDP协议,一般用在实时聊天等程序底层。

     

        TCP协议:传输控制协议。

            通信的双方,必须经常三次握手,建立数据交互的通道。然后双方在这个通道中进行数据传递。如果有一方断开通道,这时通道就被破坏,无法在继续通信。

        TCP协议:它主要用在对数据安全性要求较高的软件底层。传递慢,效率低,但安全。

     

    总结:最终我们通过网络通信,需要上门介绍的三个要素:

    协议:双方通信的规则

    ip:对方的设备标识

    端口:对方设备中运行的软件标识

    1. 网络编程

  4. IP对象(理解+编码)

    连接在网络中的任何设备都有一个IP地址。因此Java也将IP地址封装成一个对象,如果我们在编程中需要操作IP地址,可以直接使用InetAddress对象完成。

    Java中的网络编程相关的类和接口都在java.net包下:

     

    Ipv4:它采用的4个数字作为ip地址。每个数字范围0~255之间。它也是IP的第四个版本。

    Ipv6:它是IP地址的第六个版本。它采用的是八个数字作为ip地址。首选形式为 x:x:x:x:x:x:x:x,其中这些 'x' 是八个 16 位地址段的十六进制值。

     

    InetAddress类它没有对外提供构造方法,只能通过其中的静态方法获取对象:

     

/*

* 演示InetAddress对象

*/

public class IPDemo {

    public static void main(String[] args) throws UnknownHostException {

        

        method2();

    }

 

    public static void method2() throws UnknownHostException {

        

        InetAddress[] ints = InetAddress.getAllByName("www.sina.com.cn");

        

        for (InetAddress inet : ints) {

            System.out.println(inet.getHostAddress());

        }

    }

    public static void method1() throws UnknownHostException {

        /*

         * 使用静态方法获取IP对象

         * getByName(String host)

         * String host: 表示是需要封装的IP地址,

         * 字符串可以书写:IP地址、 主机名、域名

         */

        InetAddress inet = InetAddress.getByName("www.baidu.com");

        

        // 获取IP地址 getHostAddress()

        String ip = inet.getHostAddress();

        /*

         * 获取主机名

         * getHostName() 有时无法解析出对方的主机名称时,这个值就会变成IP地址

         */

        String name = inet.getHostName();

        System.out.println(ip+"...."+name);

    }

}

  1. Socket对象(理解)

    Socket : 网络套接字。

     

  2. UDP协议编程

    1. DatagramSocket对象(理解)

DatagramSocketL:它是用来发送和接收数据报包的通信端点(终端设备)对象。

 

 

  1. DatagramPacket对象(理解)

DatagramPacket:它相当于打包或拆包的对象。

  1. UDP发送端(重点理解+编码)

 

/*

* 简单演示基于UDP协议发送方实现

*/

public class UdpSendDemo {

    public static void main(String[] args) throws Exception {

        

        // 创建发送方对象

        DatagramSocket ds = new DatagramSocket(9191);

        

        /*

         * 创建打包对象

         * DatagramPacket(byte[] buf, int length, InetAddress address, int port)

         * byte[] buf : 被发送的数据

         * int length : 发送的数据的长度

         * InetAddress address : 接收方的IP地址对象

         * int port : 接收方的端口

         */

        byte[] buf = "有人犯困啦!UDP肯定挂掉!!!".getBytes();

        int length = buf.length;

        InetAddress address = InetAddress.getByName("192.168.49.40");

        int port = 9292;

        DatagramPacket dp = new DatagramPacket( buf , length , address , port);

        

        // 发送数据

        ds.send(dp);

        

        // 关闭发送方

        ds.close();

    }

}

  1. UDP接收端(重点理解+编码)

/*

* 简单实现基于UDP协议的接收方

*/

public class UdpReceDemo {

    public static void main(String[] args) throws Exception {

        

        // 创建接收方对象

        DatagramSocket ds = new DatagramSocket(9292);

        

        /*

         * 创建拆包对象

         * DatagramPacket(byte[] buf, int length)

         * byte[] buf : 临时用于存储接收到的数据的容器

         * int length : 临时用于存储数据的场地大小

         */

        DatagramPacket dp = new DatagramPacket( new byte[1024] , 1024 );

        

        // 接收数据

        ds.receive(dp);

        

        // 获取接收到的数据

        byte[] buf = dp.getData();

        // 接收到的数据长度

        int length = dp.getLength();

        // 获取发送方的ip

        String ip = dp.getAddress().getHostAddress();

        // 获取发送方的端口号

        int port = dp.getPort();

        System.out.println(ip+".."+port + ", 数据是:"+ new String( buf , 0 , length ));

        

        // 关闭接收方

        ds.close();

    }

}

  1. UDP练习

    1. 需求:

    发送方键盘录入字符串数据,将字符串数据发送给接收方,接收方接收到之后,统计出其中字母字符的个数,然后将结果发送给发送方。

 

  1. 发送端(编码实战)

 

/*

* 发送方:

*     1、键盘录入字符串数据,

*     2、将数据发送给接收方

*     3、接收 接收方发来的结果数据

*/

public class UdpSendTest {

    public static void main(String[] args) throws IOException {

 

        // 创建发送方对象

        DatagramSocket ds = new DatagramSocket(9393);

        

        // 创建Scanner对象

        Scanner sc = new Scanner( System.in );

        System.out.println("请输入数据:");

        String line = sc.nextLine();

        

        // 创建打包对象

        DatagramPacket send_dp = new DatagramPacket(

                 line.getBytes(),

                 line.getBytes().length,

                 InetAddress.getByName("192.168.49.40"),

                 9494

                );

        // 发送数据

        ds.send(send_dp);

        ///////////////////数据已经发送给接收方,需要接收数据//////////////////////

        // 创建拆包对象

        DatagramPacket rece_dp = new DatagramPacket( new byte[1024] , 1024 );

        // 接收数据

        ds.receive(rece_dp);

        // 获取数据

        String s = new String( rece_dp.getData() , 0 , rece_dp.getLength() );

        System.out.println(s);

        

        // 关闭

        ds.close();

    }

}

  1. 接收端(编码实战)

/*

* 接收方:

*     1、接收数据

*     2、统计出其中的字母字符个数

*     3、将结果发送给发送方

*/

public class UdpReceTest {

    public static void main(String[] args) throws Exception {

        

        // 创建接收方对象

        DatagramSocket ds = new DatagramSocket( 9494 );

        

        // 创建拆包对象

        DatagramPacket rece_dp = new DatagramPacket( new byte[1024] , 1024 );

        // 接收数据

        ds.receive(rece_dp);

        // 获取数据

        String data = new String( rece_dp.getData() , 0 , rece_dp.getLength() ) ;

        System.out.println(rece_dp.getAddress().getHostAddress()+"接收方接收到的数据是:"+data);

        // 处理:统计字母字符个数

        int count = 0;

        // 遍历字符串

        for( int i = 0 ; i < data.length() ; i++ ){

            // 取出字符串中的每个字符

            char ch = data.charAt(i);

            // 判断是否是字母字符

            if( (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ){

                count++;

            }

        }

        // 统计结束之后,需要将结果发送给发送方

        String s = "统计的结果是:"+count;

        // 创建打包对象

        DatagramPacket send_dp = new DatagramPacket(

                s.getBytes(),

                s.getBytes().length,

                // 这里的IP地址是发送方的IP地址,可以通过上面的拆包对象获取

                rece_dp.getAddress(),

                // 接收方的端口号

                rece_dp.getPort()

                );

        /// 发送数据

        ds.send(send_dp);

        

        // 关闭

        ds.close();

    }

}

  1. TCP编程

    1. 回顾IO流

    

  1. TCP编程模型介绍(理解)

 

 

  1. TCP客户端(理解)

 

 

 

  1. TCP服务端(理解)

 

  1. TCP客户端实现(重点+编码)

 

 

 

  1. TCP服务端实现(重点+编码)

 

 

 

  1. TCP练习

    1. 需求:

需求:客户端键盘录入数据,发送给服务器,服务端将数据保存到磁盘中,服务端告诉客户端"微博发送成功"。

 

 

 

 

 

  1. 客户端实现

 

 

 

  1. 服务端实现

 

 

 

  1. 服务端开启多线程

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2017-01-10 21:17  beyondcj  阅读(236)  评论(0编辑  收藏  举报