文档章节

第二章-对象及变量的并发访问-第一篇

大笨象会跳舞吧
 大笨象会跳舞吧
发布于 2018/08/20 20:36
字数 3238
阅读 2
收藏 0

方法内部的变量为线程安全变量

“非线程安全”问题存在于“共享变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的。

package chaprer3;

/**
 * @program: demo
 * @description: demo
 * @author: lee
 * @create: 2018-08-04
 **/
public class Service {
    public void addI(String username) {
        try {
            int num = 0;
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

package chaprer3;

/**
 * @program: demo
 * @description: demo
 * @author: lee
 * @create: 2018-08-04
 **/
public class Thread1 extends Thread{
    private Service service;

    public Thread1(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.addI("a");
    }
}

package chaprer3;

/**
 * @program: demo
 * @description: demo
 * @author: lee
 * @create: 2018-08-04
 **/
public class Thread2 extends Thread{
    private Service service;

    public Thread2(Service service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.addI("b");
    }
}
package chaprer3;


/**
 * @program: demo
 * @description: demo\
 * @author: lee
 * @create: 2018-08-04
 **/
public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        Thread1 thread1 = new Thread1(service);
        thread1.start();
        Thread2 thread2 = new Thread2(service);
        thread2.start();
    }

}

输出结果

a set over!
b set over!
b num=200
a num=100

共享变量非线程安全

多个线程同时访问一个对象的实例变量问题的时候会出现非线程安全问题

package chaprer3;

/**
 * @program: demo
 * @description: demo
 * @author: lee
 * @create: 2018-08-04
 **/
public class Service {
    int num = 0;
    public void addI(String username) {
        try {
           
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

输出结果

a set over!
b set over!
b num=200
a num=200

多个对象多个锁

多线程访问多个对象,则JVM会创建多个锁。

package chaprer3;

/**
 * @program: demo
 * @description: 用于测试的公共类
 * @author: lee
 * @create: 2018-08-13
 **/
public class PublicObject {
    private int num;

    public void addI(String username) {
        try {
            if ("a".equals(username)) {
                num = 100;
                Thread.sleep(3000);
                System.out.println("a set over");
            } else {
                num = 200;
                System.out.println("b set over");
            }
            System.out.println(username + " num= " +  num);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

}    

package chaprer3;

/**
 * @program: demo
 * @description: 测试的线程1
 * @author: lee
 * @create: 2018-08-13
 **/
public class TheadTest1 extends Thread {
    private PublicObject object;

    public TheadTest1(PublicObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        this.object.addI("a");
    }
}

package chaprer3;

/**
 * @program: demo
 * @description: 测试的线程2
 * @author: lee
 * @create: 2018-08-13
 **/
public class TheadTest2 extends Thread {
    private PublicObject object;

    public TheadTest2(PublicObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        this.object.addI("b");
    }
}    

package chaprer3;

import chapter2.Thread1;

/**
 * @program: demo
 * @description: 主线程
 * @author: lee
 * @create: 2018-08-13
 **/
public class MainRun {
    public static void main(String[] args) {
        PublicObject object = new PublicObject();
        PublicObject object1 = new PublicObject();
        TheadTest1 test1 = new TheadTest1(object);
        TheadTest2 test2 = new TheadTest2(object1);
        test1.start();
        test2.start();
    }
}    

输出结果

b set over
b num= 200
a set over
a num= 100

synchronized 方法与锁对象

synchronized占用的锁是对象锁

package chaprer3;

/**
 * @program: demo
 * @description: 测试的线程1
 * @author: lee
 * @create: 2018-08-13
 **/
public class TheadTest1 extends Thread {
    private PublicObject object;

    public TheadTest1(PublicObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        this.object.methodA();
    }
}

package chaprer3;

/**
 * @program: demo
 * @description: 测试的线程2
 * @author: lee
 * @create: 2018-08-13
 **/
public class TheadTest2 extends Thread {
    private PublicObject object;

    public TheadTest2(PublicObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        this.object.methodA();
    }
}    
package chaprer3;

/**
 * @program: demo
 * @description: 用于测试的公共类
 * @author: lee
 * @create: 2018-08-13
 **/
public class PublicObject {
    public void methodA() {
        try {
            System.out.println("begin methodA threadName="
                    + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}    

package chaprer3;

import chapter2.Thread1;

/**
 * @program: demo
 * @description: 主线程
 * @author: lee
 * @create: 2018-08-13
 **/
public class MainRun {
    public static void main(String[] args) {
        PublicObject object = new PublicObject();
        TheadTest1 test1 = new TheadTest1(object);
        TheadTest2 test2 = new TheadTest2(object);
        test1.start();
        test2.start();
    }
}    

输出结果

begin methodA threadName=Thread-0
begin methodA threadName=Thread-1
end
end

加上修饰以后

package chaprer3;

/**
 * @program: demo
 * @description: 用于测试的公共类
 * @author: lee
 * @create: 2018-08-13
 **/
public class PublicObject {
    synchronized public void methodA() {
        try {
            System.out.println("begin methodA threadName="
                    + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}    

输出结果

begin methodA threadName=Thread-0
end
begin methodA threadName=Thread-1
end

或许这并不能体现对象锁,别着急接着看

package chaprer3;

/**
 * @program: demo
 * @description: 用于测试的公共类
 * @author: lee
 * @create: 2018-08-13
 **/
public class PublicObject {
    synchronized public void methodA() {
        try {
            System.out.println("begin methodA threadName="
                    + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized public void methodB() {
        System.out.println("begin methodB threadName="
                + Thread.currentThread().getName());
        System.out.println("end");

    }
}    

package chaprer3;

/**
 * @program: demo
 * @description: 测试的线程1
 * @author: lee
 * @create: 2018-08-13
 **/
public class TheadTest1 extends Thread {
    private PublicObject object;

    public TheadTest1(PublicObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        this.object.methodA();
    }
}

package chaprer3;

/**
 * @program: demo
 * @description: 测试的线程2
 * @author: lee
 * @create: 2018-08-13
 **/
public class TheadTest2 extends Thread {
    private PublicObject object;

    public TheadTest2(PublicObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        this.object.methodB();
    }
}    

输出结果

begin methodA threadName=Thread-0
end
begin methodB threadName=Thread-1
end

此时可以看出当某个线程先获得方法同步方法以后整个对象就已经被锁住了,其他线程需要等待同步方法执行完成以后才可以获得锁继续执行。

线程先持有object 对象的Lock 锁,另外一个线程可以以异步的方式调用object 对象中的非synchronized 类型的方法。

脏读

public class PublicObject {
    public String username = "A";
    public String password = "AA";
    synchronized public void setValue(String username, String password) {
        try {
            this.username = username;
            Thread.sleep(5000);
            this.password = password;
            System.out.println("setValue method thread name="
                    + Thread.currentThread().getName() + " username="
                    + username + " password=" + password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void getValue() {
        System.out.println("getValue method thread name="
                + Thread.currentThread().getName() + " username=" + username
                + " password=" + password);
    }
}

public class TheadTest1 extends Thread {
    private PublicObject object;

    public TheadTest1(PublicObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        this.object.setValue("B", "BB");
    }
}

public class TheadTest2 extends Thread {
    private PublicObject object;

    public TheadTest2(PublicObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        this.object.setValue("A", "AA");
    }
}   

输出结果

getValue method thread name=main username=B password=AA
setValue method thread name=Thread-0 username=B password=BB

出现脏读是因为public void getValue() 方法并不是同步的,所以可以在任意时候进行调用。解决办法当然就是加上同步synchronized 关键字,代码如下:

//加入了对象锁
    synchronized public void getValue() {
        System.out.println("getValue method thread name="
                + Thread.currentThread().getName() + " username=" + username
                + " password=" + password);
    }

当A 线程调用anyObject 对象加入synchronized 关键字的X 方法时,A 线程就获得了X方法锁,更准确地讲,是获得了对象的锁,所以其他线程必须等A 线程执行完毕才可以调用X 方法,但B 线程可以随意调用其他的非 synchronized 同步方法。

当A 线程调用anyObject 对象加入synchronized 关键字的X 方法时,A 线程就获得了X 方法所在对象的锁,所以其他线程必须等A 线程执行完毕才可以调用X 方法,而B 线程如果调用声明了synchronized 关键字的非X 方法时,必须等A 线程将X 方法执行完,也就是释放对象锁后才可以调用。这时A 线程已经执行了一个完整的任务,也就是说username 和password 这两个实例变量已经同时被赋值,不存在脏读的基本环境。

脏读一定会出现操作共享变量的情况下,这就是不同线程“争抢”共享变量的结果

synchronized 锁重入

“可重入锁”的概念是:自己可以再次获取自己的内部锁。

public class PublicObject {
    synchronized public void service1() {
        System.out.println("service1");
        service2();
    }
    synchronized public void service2() {
        System.out.println("service2");
    }
}    

“可重入锁”的概念是:自己可以再次获取自己的内部锁。比如有1 条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。

public class MainRun {
    public int i = 10;
    synchronized public void operateIMainMethod() {
        try {
            i--;
            System.out.println("main print i=" + i);
            Thread.sleep(100);
            this.operateIMainMethod();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}  

出现异常,锁自动释放

当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

public class PublicObject {
    synchronized public void service1() {
       if (Thread.currentThread().getName() .equals("a")) {
           try {
               Thread.sleep(3000);
               //此处会发生异常
               Integer integer = Integer.parseInt("a");
               Thread.sleep(3000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       } else {
           System.out.println("thread current name is " + Thread.currentThread().getName());
       }
    }
}

public class TheadTest1 extends Thread {
    private PublicObject object;

    public TheadTest1(PublicObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        this.object.service1();
    }
}

public class TheadTest2 extends Thread {
    private PublicObject object;

    public TheadTest2(PublicObject object) {
        this.object = object;
    }

    @Override
    public void run() {
        this.object.service1();
    }
} 

public class MainRun {
    public static void main(String[] args) throws InterruptedException {
        PublicObject object = new PublicObject();
        TheadTest1 test1 = new TheadTest1(object);
        test1.setName("a");
        test1.start();
        Thread.sleep(1000);
        TheadTest1 test2 = new TheadTest1(object);
        test2.setName("b");
        test2.start();
    }
}    

输出结果

thread current name is b
Exception in thread "a" java.lang.NumberFormatException: For input string: "a"  at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:492)
	at java.lang.Integer.parseInt(Integer.java:527)
	at chaprer3.PublicObject.service1(PublicObject.java:14)
	at chaprer3.TheadTest1.run(TheadTest1.java:18)

实验的结论就是出现异常的锁被自动释放了。

同步不具有继承性

当同步方法进行继承的时候,如果重写了方法,将不再同步,如果未进行重写仍旧保持同步。

public class MainClass {
    synchronized public void printString() throws InterruptedException {
        System.out.println("我已经进入了----" + Thread.currentThread().getName());
        Thread.sleep(5000);
        System.out.println("我已经准备离开了----" + Thread.currentThread().getName());
    }
}

public class SubClass extends MainClass {

}

public class ThreadA extends   Thread {
    private SubClass subClass ;

    public ThreadA(SubClass subClass) {
        this.subClass = subClass;
    }

    @Override
    public void run() {
        try {
            subClass.printString();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadB extends   Thread {
    private SubClass subClass ;

    public ThreadB(SubClass subClass) {
        this.subClass = subClass;
    }

    @Override
    public void run() {
        try {
            subClass.printString();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果是

我已经进入了----Thread-0
我已经准备离开了----Thread-0
我已经进入了----Thread-1
我已经准备离开了----Thread-1

一旦进行重写

public class SubClass extends MainClass {
    @Override
    public void printString() throws InterruptedException {
        System.out.println("我已经进入了----" + Thread.currentThread().getName());
        Thread.sleep(5000);
        System.out.println("我已经准备离开了----" + Thread.currentThread().getName());
    }
}

输出结果

我已经进入了----Thread-0
我已经进入了----Thread-1
我已经准备离开了----Thread-1
我已经准备离开了----Thread-0

synchronized 同步语句块

当两个并发线程访问同一个对象中的synchronized(this)同步代码块的时候,,一段时间内只有一个线程被执行,另一个线程必须等待当前线程执行完毕再执行。

public class MainClass {
    public void printString() throws InterruptedException {
        System.out.println("我已经进入了----" + Thread.currentThread().getName());
        synchronized (this) {
            Thread.sleep(5000);
        }
        System.out.println("我已经准备离开了----" + Thread.currentThread().getName());
    }
}  

package chapter2;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class RunClass {
    public static void main(String[] args) {
        MainClass mainClass = new MainClass();
        ThreadA threadA = new ThreadA(mainClass);
        threadA.start();
        ThreadB threadB =new ThreadB(mainClass);
        threadB.start();
    }
}    

package chapter2;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class ThreadA extends   Thread {
    private MainClass mainClass ;

    public ThreadA(MainClass mainClass) {
        this.mainClass = mainClass;
    }

    @Override
    public void run() {
        try {
            mainClass.printString();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package chapter2;

import sun.applet.Main;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class ThreadB extends   Thread {
    private MainClass mainClass ;

    public ThreadB(MainClass mainClass) {
        this.mainClass = mainClass;
    }

    @Override
    public void run() {
        try {
            mainClass.printString();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果

我已经进入了----Thread-0
我已经进入了----Thread-1
我已经准备离开了----Thread-0
我已经准备离开了----Thread-1

同步代码块数据出现脏读

package chapter2;

import java.util.Timer;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class MainClass {
    private String name1;
    private String name2;

    public void printString() throws InterruptedException {
        String a = Thread.currentThread().getName();
        String b = Thread.currentThread().getName() + "b";

        synchronized (this) {
            name1 = a;
            name2 = b;
        }
        //程序会出现脏读
        System.out.println(name1);
        System.out.println(name2);
    }
}    

package chapter2;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class ThreadA extends   Thread {
    private MainClass mainClass ;

    public ThreadA(MainClass mainClass) {
        this.mainClass = mainClass;
    }

    @Override
    public void run() {
        try {
            mainClass.printString();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package chapter2;

import sun.applet.Main;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class ThreadB extends   Thread {
    private MainClass mainClass ;

    public ThreadB(MainClass mainClass) {
        this.mainClass = mainClass;
    }

    @Override
    public void run() {
        try {
            mainClass.printString();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package chapter2;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class RunClass {
    public static void main(String[] args) {
        MainClass mainClass = new MainClass();
        ThreadA threadA = new ThreadA(mainClass);
        threadA.start();
        ThreadB threadB =new ThreadB(mainClass);
        threadB.start();
    }
}    

输出结果

Thread-0
Thread-0b
Thread-1
Thread-1b
#或者
Thread-1
Thread-1b
Thread-1
Thread-1b 
#等等

将任意对象作为对象监视器

多线程调用同一个对象中的不同名称的synchronized同步方法或者synchronized(this)同步代码块的时候,调用的效果就是按照顺序执行,也就是同步的,阻塞的。

synchronized同步方法

  • 对其他的synchronized同步方法或者同步代码块呈现阻塞状态。
  • 同一时刻只有一个线程可以执行synchronized同步方法中的代码。

同理synchronized(this)同步代码块也同样如此。

同步代码块锁住非this对象

package chapter2;

import java.util.Timer;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class MainClass {
    private String name1;
    private String name2;
    private String anyString = new String();
    
    public void printString() throws InterruptedException {
        String a = Thread.currentThread().getName();
        String b = Thread.currentThread().getName() + "b";

        synchronized (anyString) {
            name1 = a;
            name2 = b;
        }
        //程序会出现脏读
        System.out.println(name1);
        System.out.println(name2);
    }
}    

如果不想出现脏读

package chapter2;

import java.util.Timer;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class MainClass {
    private String name1;
    private String name2;
    private String anyString = new String();

    public void printString() throws InterruptedException {
        String a = Thread.currentThread().getName();
        String b = Thread.currentThread().getName() + "b";

        synchronized (anyString) {
            name1 = a;
            name2 = b;           
            System.out.println(name1);
            System.out.println(name2);
        }

    }
}    

锁住非this对象有一定的优点,与synchronized修饰的同步方法是异步的,不予synchronized(this)同步方法争抢this锁,可以大大提高效率。 当多个线程同时争抢synchronized(非this锁)的时候也是呈现同步效果。 同步代码块的另一种思考,其实要保证同步,只要保证同步同一个对象就行。如下所示

    package chapter2;

import java.util.Timer;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class MainClass {
    private String name1;
    private String name2;
    private String anyString = new String();

    public void printString(Object object) throws InterruptedException {


        synchronized (anyString) {
            String a = Thread.currentThread().getName();
            String b = Thread.currentThread().getName() + "b";
            Thread.sleep(3000);
            name1 = a;
            name2 = b;
            System.out.println(name1);
            System.out.println(name2);
        }



    }
}

package chapter2;

import sun.applet.Main;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class ThreadA extends Thread {
    private Object object;
    private MainClass mainClass;

    public ThreadA(MainClass mainClass, Object object) {
        this.object = object;
        this.mainClass = mainClass;
    }

    @Override
    public void run() {
        try {
            mainClass.printString(object);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package chapter2;

import sun.applet.Main;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class ThreadB extends Thread {
    private MainClass mainClass;
    private Object object;

    public ThreadB(MainClass mainClass, Object object) {
        this.object = object;
        this.mainClass = mainClass;
    }

    @Override
    public void run() {
        try {
            mainClass.printString(object);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package chapter2;

import sun.applet.Main;

/**
 * @program: demo
 * @description:
 * @author: lee
 * @create: 2018-08-20
 **/
public class RunClass {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        MainClass mainClass = new MainClass();
        ThreadA threadA = new ThreadA(mainClass,object);
        threadA.start();
        Thread.sleep(1000);
        ThreadB threadB =new ThreadB(mainClass,object);
        threadB.start();
    }
}    

© 著作权归作者所有

共有 人打赏支持
大笨象会跳舞吧
粉丝 31
博文 102
码字总数 169045
作品 0
济南
私信 提问
多线程基础必要知识点!看了学习多线程事半功倍

前言 不小心就鸽了几天没有更新了,这个星期回家咯。在学校的日子要努力一点才行! 只有光头才能变强 回顾前面: 多线程三分钟就可以入个门了! Thread源码剖析 本文章的知识主要参考《Java并...

Java3y
2018/04/23
0
0
《Effective Java》读书笔记

Effective Java读书笔记 第一章:引言 第二章:创建和销毁对象 第一条:创建对象的方式 1、构造器 2、 静态方法 3、构建器 第三条::强化singleton 1、三种方式实现单例 枚举实现(最佳方式)...

磊神Ray
2011/09/06
0
0
那些年,关于 Java 的那些事儿

版权声明:Follow your heart and intuition. https://blog.csdn.net/qq_35246620/article/details/78695893 温馨提示:本系列博文(含示例代码)已经同步到 GitHub,地址为「java-skills」,...

维C果糖
2017/12/02
0
0
微信小程序教学第二章(含视频):小程序中级实战教程之预备篇 - 提取util公用方法

iKcamp官网:www.ikcamp.com 访问官网更快阅读全部免费分享课程:《iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享》。 包含:文章、视频、源代码 第二章:...

iKcamp
2017/10/19
0
0

没有更多内容

加载失败,请刷新页面

加载更多

nginx日志自动切割

1.日志配置(Nginx 日志) access.log----记录哪些用户,哪些页面以及用户浏览器,IP等访问信息;error.log------记录服务器错误的日志 #配置日志存储路径:location / {      a...

em_aaron
昨天
1
0
java 反射

基本概念 RTTI,即Run-Time Type Identification,运行时类型识别。RTTI能在运行时就能够自动识别每个编译时已知的类型。   要想理解反射的原理,首先要了解什么是类型信息。Java让我们在运...

细节探索者
昨天
1
0
推荐转载连接

https://www.cnblogs.com/ysocean/p/7409779.html#_label0

小橙子的曼曼
昨天
3
0
雷军亲自打造的套餐了解下:用多少付多少

12月28日消息,小米科技创始人兼CEO雷军微博表示,小米移动任我行套餐方案,原则上就是明明白白消费,用多少付多少,不用不花钱!上网、电话和短信都是一毛钱,上网0.1元/M,电话0.1元/分钟,...

linuxCool
昨天
6
0
协议简史:如何学习网络协议?

大学时,学到网络协议的7层模型时,老师教了大家一个顺口溜:物数网传会表应。并说这是重点,年年必考,5分的题目摆在这里,你们爱背不背。 考试的时候,果然遇到这个问题,搜索枯肠,只能想...

Java干货分享
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部