重温JavaSE19、20、21

多线程(偏重点)

多线程到底是什么?(理解)

多线程是指在一个程序中同时运行多个线程,每个线程都可以独立执行不同的任务。多线程可以提高程序的并发性,使得程序能够同时处理多个任务,提高系统的运行效率。
多线程通常用于需要同时处理多个任务的场景,比如网络通信、服务器端编程等。

程序、进程、线程的区别:(理解)
  1. 程序:是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。 (程序是静态的)
  2. 进程:正在运行的一个程序,进程作为系统进行资源分配和调度的一个独立单位,(进程是动态的)是一个动的过程,进程的生命周期:有它自身的产生、存在和消亡的过程
  3. 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。

区别:一个进程可以包括多个线程。两者的一个主要区别是:线程是操作系统进行资源调度和执行的基本单位。进程是操作系统分配资源的基本单位,进程有独立的地址空间和系统资源,进程之间相互隔离。

总结:若一个进程同一时间并行执行多个线程,就是支持多线程的。

(单核CPU只能单线程,而双核CPU可同时干活就是多线程,java中的多线程其实是假象)

并行和并发:(理解)

并行:多个CPU同时执行多个任务

​ 概念:并行是任务在同一时间运行,并行是同时执行计算

并发:一个CPU“同时”执行多个任务(采用时间片切换) ,可以降低资源的访问压力

​ 概念:并发是两个任务可以在重叠的时间段内启动、运行和完成,是独立执行过程的组合

线程的生命周期,从生到死,我这一生都经历了什么?(要会描述下面的!)
介绍一下线程的生命周期(以后写的这种表格都是自己整理的,重点,复习看这个即可
线程的生命周期是指线程从创建到销毁的整个过程,包括多个状态,如新建、就绪、运行、阻塞和终止等状态。
对于多线程编程,合理地控制线程的运行状态,能够确保线程的安全和稳定性。
用于任何涉及多线程编程的场景,比如并发编程、服务器端编程。
新建:可通过创建Thread对象或者实现Runnable接口来创建新线程
就绪:当线程被创建后,处于就绪状态,等待系统分配CPU时间片来执行线程。
运行:当线程获取到CPU时间片后,进入运行状态,开始执行线程的run()方法中的代码。
阻塞:线程在某些情况下会进入阻塞状态,如sleep()睡眠、join()等待其他线程执行完和wait()等待都会进入阻塞状态。
终止(死亡):线程执行完run()方法中的代码或者异常终止及调用stop()方法后就会进入终止状态,代表着线程的生命周期结束。

线程是存在生命周期的。线程的生命周期分为5中不同的状态,由始至终分别是:

在这里插入图片描述

新建状态

​ 处于新建状态中的线程对象,并不是一个独立的线程,无法运行,只有当被触发start方法时才会进入准备状态。新建状态是线程生命周期中的第一个状态,也是初始状态

准备状态

​ 处于新建状态的线程对象,被调用了start方法,将进入准备状态;处于准备状态的线程随时都可能被系统选中进入运行状态,从而执行线程;可能同时有多个线程处于准备状态,然而究竟哪一个线程将进入运行状态,这是不确定的;

​ 被阻塞的线程再次被唤醒时,并不会进入运行状态,而是进入准备状态,等待运行时机;

运行状态

​ 处于准备状态中的线程一旦被系统选中,获得了运行时机,就会进入运行状态;在运行状态中,程序将执行线程类中run方法中的语句块;处于运行状态的线程,可以随时被设置为阻塞状态;

​ 在单核CPU中,同一时刻只能有一个线程处于运行状态。在多核CPU中就可以多个线程同时处于运行状态,这也是多核CPU运行速度快的原因

等待(阻塞)状态

​ Java中提供了许多线程调度的方法,包括睡眠sleep()、阻塞block()、挂起suspend()和等待wait(),使用这些方法都会将处于运行状态的线程调度到阻塞(等待)状态。处于阻塞状态的线程被解除后,并不会直接进入运行状态,而是进入准备状态,等待运行时机

死亡状态

当程序的run方法执行过程中出现下面两种情况线程会进入死亡状态,这时线程不可能再进入就绪状态。

自然终止:正常运行run()方法后终止

异常终止:出现异常或调用stop()方法让一个线程终止运行

实现多线程(重点)

Thread()无参构造,例:线程类 thread = new Thread();void
Thread([线程类的对象])有参构造,例:Thread thread = new Thread([线程类的对象]);void
Thread.currentThread()返回对当前正在执行的线程对象的引用,后面可接.getName()读取名字Object
Thread.sleep(毫秒数)休眠,使该线程从运行状态进入阻塞状态,从而使程序中断运行。该方法是使正在运行的线程让出CPU最简单的方法之一void
Thread.yield()使当前正在运行的线程让出当前CPU,使线程由运行状态回到准备状态,让其他线程有进入运行状态的机会。然而将CPU让给哪一个线程是不确定的(让步)void
Thread实例化的对象的方法说明返回值
setName(String s)设置线程名,(继承Thread的类可通过有参构造器super(name);设置线程名)void
getName()获取线程名void
start()进入就绪状态。
启动多线程,执行线程类的run()方法,和其他线程抢占资源
void
run()进入运行状态。
不是启动线程,只是线程类的run()方法,程序继续向后执行
void
setPriority(1到10)设置线程的优先级,1低,10高,默认5(不是级别高CPU资源就多,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行)void
join()该线程抢占CPU执行权直到执行完run方法,其他线程会阻塞void
setDaemon(true);定义成伴随线程,主线程结束随之结束void
stop()结束线程,不推荐使用void
继承Thread类(较常用,但占用父类了)

继承Thread类的子类具备了多线程的能力,重写Thread类的run方法,当线程被启动的时候,run方法中的程序就成为了一条独立的执行线程

特点:

  1. 静态资源共享要设置static属性,且顺序会有问题
  2. 继承Thread的类不能继承其他父类了
  3. run不是抽象类,不提示重写
  4. 实例化的对象自带start方法,可实例化对象后自行开启
  5. 类继承Thread线程父类,Thread本身实现了Runnable接口

示例:

public static void main(String[] args) {
    Demo1 demo1 = new Demo1();
    demo1.start();//start才能开启新的路径
    for (int i = 1; i < 100; i+=2) {
        System.out.println("i="+i);
    }
    //        for (int i = 2; i < 100; i+=2) {//这样写的话,偶数的数字只有奇数执行完才能执行,而多线程是可以同时执行的,两条执行路径互抢
    //            System.out.println(i);
    //        }
}
}
public class Demo1 extends Thread {//通过继承Thread实现多线程,不常用(因为占用父类了)
//    线程start()方法操作的方法
    @Override
    public void run() {//快捷键:alt+insert->Override Methods
        for (int i = 0; i < 100; i+=2) {
            System.out.println("我是线程类Demo1的循环"+i);
        }
        super.run();
    }
}
实现Runnable接口

实现Runnable接口,需要实现Runnable接口中提供的run方法,当线程被启动的时候,run方法中的程序就成为了一条独立的执行线程

注意:

当实例化的时候还要通过Thread thread = new Thread(线程类);通过thread调用start()方法

特点:

  1. 不需要static修饰就可以资源共享(需要Thread实例化同一个对象,否则也给static修饰),但是顺序有问题,且会有重复的
  2. 实现接口不影响原类的父类
  3. run是抽象方法会提示重写
  4. 启动方式不同,实例化的对象没有start方法,需要通过Thread有参构造转成该类型调用start
  5. 类只实现了Runnable接口

示例:

public class Test {
    public static void main(String[] args) {
        Demo3 demo3 = new Demo3();
        Thread thread = new Thread(demo3);
        thread.setName("哈哈哈");
        thread.start();
        for (int i = 0; i < 5000; i++) {
            System.out.println(i);
        }
    }
}
public class Demo3 implements Runnable{//通过实现Runnable接口的方式实现多线程
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"i="+i);//获取线程的名字
        }
    }
}

数据共享参考:(需要Thread实例化同一个对象,下面的demo2,否则也给static修饰)

public static void main(String[] args) {
    Demo2 demo2 = new Demo2();
    Thread thread = new Thread(demo2);
    Thread thread1 = new Thread(demo2);
    thread.setName("哈哈哈");
    thread.start();
    thread1.setName("嘿嘿");
    thread1.start();
}
public class Demo2 implements Runnable{//通过实现Runnable接口的方式实现多线程
    int f = 0;
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            f++;
            System.out.println(f+Thread.currentThread().getName()+"i="+i);//获取线程的名字
        }
    }
}
实现Callable<类型>接口

实现Callable接口,需要实现Callable接口中提供的call方法,当线程被启动的时候,call方法中的程序就成为了一条独立的执行线程

注意:

当实例化的时候还要通过FutureTask 转换器名 = new FutureTask(线程类);

Thread thread = new Thread(转换器名);通过thread调用start()方法

特点:

  1. call方法可以有返回值(需要在Callable<泛型>中定义类型),是抽象方法会提示重写
  2. 不需要static修饰就可以资源共享,但是顺序还是有问题
  3. 可以抛出异常
  4. 实现接口不影响原类的父类
  5. 创建麻烦,需要通过FutureTask转换器和Thread来调用start方法
  6. 类只实现了Callable接口,Thread没实现该接口所以要用转换器FutureTask

示例:

public class Test {
    public static void main(String[] args) {
        Demo3 demo3 = new Demo3();
        FutureTask task1 = new FutureTask(demo3);
        Thread thread = new Thread(task1);
        thread.start();
        for (int i = 0; i < 10000; i++) {
            System.out.println(i);
        }
    }
}
public class Demo3 implements Callable/*这里可写call返回值类型*/ {
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+"i="+i);
        }
        return null;
    }
}
启动线程的其他问题

电脑快的可能看不出多线程的不同,可设置循环次数大一些能看出不同。

Java中对于线程后,可以保证的只是让每个线程都启动,并且会执行结果,但是无法决定多个线程中哪个先执行,哪个后执行。

控制线程(理解)

抢占资源的方法:join()

当线程处于任何状态,调用该线程的join方法将其他线程进入阻塞状态,自己的线程单独执行。

public static void main(String[] args) throws Exception {//join抢占,sleep休眠
    for (int i = 0; i < 20; i++) {//最开始是main自己抢资源
        System.out.println(i);
        if (i == 10){
            Demo5 demo5 = new Demo5("线程一");
            demo5.start();
            demo5.join();//线程demo5抢占资源,其他资源阻塞
        }
    }
}//结果:先输出0~10再输出线程里的1~9,在输出11到19
public class Demo6 extends Thread {
    public Demo6(String name) {
        super(name);
    }
    public Demo6() {
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"的i="+i);
        }
    }
}

休眠的方法:sleep(毫秒数)

当线程处于运行状态时,调用Thread.sleep方法将使线程从运行状态进入阻塞状态。该方法是使正在运行的线程让出CPU最简单的方法之一

示例:

public class Test {
    public static void main(String[] args) throws Exception {//sleep休眠
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
            Thread.sleep(600);//谁调用这个方法谁休眠,(毫秒)
        }
    }
}

让步的方法:在run方法里写yield();

yield方法可以使当前正在运行的线程让出当前CPU,使线程由运行状态回到准备状态,让其他线程有进入运行状态的机会;然而将CPU让给哪一个线程是不确定的

示例:

public class YieldThread extends Thread { 
    public YieldThread(String name) { 
        super(name); 
    } 
    @Override 
    public void run() { 
        for (int i = 1; i <= 50; i++) { 
            System.out.println("" + this.getName() + "-----" + i); 
            // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行) 
            if (i == 30) { 
                yield(); 
            } 
        } 
    }  
} 
public static void main(String[] args) {
    YieldThread yt1 = new YieldThread("张三");
    YieldThread yt2 = new YieldThread("李四");
    yt1.start();
    yt2.start();
}

第一种情况:李四线程当执行到第30时会让出CPU时间,这时张三线程会抢到CPU时间并执行

第二种情况:李四线程执行到第30时会让出CPU时间,又重新抢到了CPU时间

总之李四30下一个肯定是张三,张三30下一个肯定是李四。

控制线程(了解)

设置优先级的方法:setPriority(1到10)

设置线程的优先级,1低,10高,默认5(不是级别高CPU资源就多)

示例:

public class Demo5 implements Callable<String> {//Callable<T>  T泛型决定了call方法的返回值类型
    int ticket = 100;
    @Override
    public String call() throws Exception {
        while (ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"售出一张票,剩余"+ticket+"张");
        }
        return "null";
    }
}
public class Test {
    public static void main(String[] args) throws Exception {
        FutureTask task1 = new FutureTask(new Demo5());
        FutureTask task2 = new FutureTask(new Demo5());
        Thread thread1 = new Thread(task1);
        thread1.setName("第一个线程");
        thread1.setPriority(1);//设置优先级(低)
        Thread thread2 = new Thread(task2);
        thread2.setName("2线程");
        thread2.setPriority(10);//设置优先级(高)
        thread1.start();
        thread2.start();
    }
}

伴随线程:setDaemon(true)

public static void main(String[] args) throws Exception {
    Demo7 demo5 = new Demo7();
    demo5.setDaemon(true);
    demo5.start();
    for (int i = 0; i < 5000; i++) {
        System.out.println(i);
    }
}
public class Demo7 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+"的i="+i);
        }
    }
}
综合案例:每秒刷新
public class Test {
    public static void main(String[] args) throws InterruptedException {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        while (true){
            Date d = new Date();
            System.out.println(df.format(d));
            Thread.sleep(1000);
        }
    }
}
综合案例:常去健身房
public class FeiYuqing extends Thread {
    @Override
    public void run() {
        int race = 100;
        while(race > 0){
            try {
                int x = (int) (Math.random()*20);
                if(race - x >= 0){
                    race = race - x;
                    sleep(1*1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("常还有"+race+"米就逃跑成功啦");
        }
        System.out.println("**********常逃跑成功");
    }
}
public class Chimpanzee extends Thread {
    @Override
    public void run() {
        int race = 100;
        while(race > 0){
            try {
                int x = (int) (Math.random()*20);
                if(race - x >= 0){
                    race = race - x;
                    sleep(1*1000);
               }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("!大猩猩还有"+race+"米就抓到常啦");
        }
        System.out.println("**********大猩猩抓到了常");
    }
}
public class Gym {
    public static void main(String[] args) {
        FeiYuqing fei = new FeiYuqing();
        fei.start(); //常跑
        Chimpanzee chi = new Chimpanzee();
        chi.start(); //大猩猩追
    }
}

线程安全问题=实现线程同步(偏重点)

解决多个线程在争抢资源的过程中,导致共享的资源出现问题(输出重复、顺序不对等问题)。实现了线程的同步

例如下面代码:

public class Demo4 implements Callable<String> {//Callable<T>  T泛型决定了call方法的返回值类型
    int ticket = 10000;
    @Override
    public String call() throws Exception {
        while (ticket>0){
            ticket--;
            System.out.println(Thread.currentThread().getName()+"售出一张票,剩余"+ticket+"张");
        }
        return null;
    }
}
public class Test {
    public static void main(String[] args) {
        Demo4 demo4 = new Demo4();
        FutureTask task1 = new FutureTask(demo4);
        FutureTask task2 = new FutureTask(demo4);
        FutureTask task3 = new FutureTask(demo4);
        Thread thread1 = new Thread(task1);
        thread1.setName("窗口一");
        Thread thread2 = new Thread(task2);
        thread2.setName("2窗口");
        Thread thread3 = new Thread(task3);
        thread3.setName("窗|||");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

解决方法3种:解决安全问题,效率就会降低。

同步代码块 synchronized

总结1:认识同步监视器(锁子) synchronized(同步监视器){ }

介绍一下synchronized关键字
synchronized是Java中用于实现线程同步的关键字,它可以修饰方法或代码块。确保在同一时刻只有一个线程可以执行被synchronized修饰的代码,保证了共享资源的安全访问。
当多个线程同时访问一个共享资源时,如果没有同步控制,可能会导致共享的资源出现问题,(如输出重复、顺序不对等问题)。synchronized关键字可以帮助我们解决这些问题,确保程序的正确性和稳定性。
通常用于涉及多个线程同时访问共享资源的场景,比如生产者-消费者模式。合理地使用synchronized关键字可以保证共享资源的安全访问。
修饰方法:将synchronized关键字直接修饰在方法上,表示整个方法都是同步的,多个线程不能同时访问该方法。
修饰代码块:将synchronized关键字修饰在代码块中,通过指定对象来控制代码块的同步执行,确保同一时刻只有一个线程可以执行该代码块。
synchronized(this):在方法内部(也是代码块中)或者代码块中使用synchronized(this)来锁定当前对象,确保同一时刻只有一个线程可以访问该对象的synchronized方法或者代码块。
synchronized(对象):和this不同的是来锁定指定对象,其他没区别。

把有安全隐患的代码锁住即可,如果锁了会降低效率(必须执行完代码块里的语句其他资源才能继续抢占)

继承Thread的方式只能用synchronized(类名.class)注意锁的参数(监视器)要用相同的

public class Demo9 implements Callable<String> {//Callable<T>  T泛型决定了call方法的返回值类型
    int ticket = 1000;
    @Override
    public String call(){
        while (true){
            synchronized (this) {//把具有安全隐患的代码锁住即可,如果锁了就会效率低(继承Thread不行)
//            synchronized (Demo9.class) {//也可以,(继承Thread也可以)
                if (ticket > 0) {
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "售出一张票,剩余" + ticket + "张");
                }
            }
            if (ticket == 0){
                break;
            }
        }
        return null;
    }
}
public class Test {
    public static void main(String[] args) {
        Demo9 demo4 = new Demo9();
        FutureTask task1 = new FutureTask(demo4);
        FutureTask task2 = new FutureTask(demo4);
        FutureTask task3 = new FutureTask(demo4);
        Thread thread1 = new Thread(task1);
        thread1.setName("窗口一");
        Thread thread2 = new Thread(task2);
        thread2.setName("2窗口");
        Thread thread3 = new Thread(task3);
        thread3.setName("窗|||");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
同步方法 synchronized方法名

synchronized修饰的自定义方法,里面是安全隐患的代码,比同步代码块效率低

继承Thread的方式只能用static synchronized void 方法名(){}

不要将run()定义为同步方法

非静态同步方法的同步监视器是this ,静态同步方法的同步监视器是 类名.class 字节码信息对象

public class Demo10 implements Callable {
    int ticket = 1000;
    @Override
    public Object call(){
        while (true){
            buyTicket();
            if (ticket == 0){
                break;
            }
        }
        return null;
    }
    public synchronized void buyTicket(){
        if (ticket > 0) {
            ticket--;
            System.out.println(Thread.currentThread().getName() + "售出一张票,剩余" + ticket + "张");
        }
    }
}
//测试类一样
Lock锁

lock可提供多种锁方案,更灵活

synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的呀。是虚拟机级别的。

但是Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。

public class Demo11 implements Callable {
    int ticket = 1000;
    //拿来一把锁:
    Lock lock = new ReentrantLock();//多态  接口=实现类  可以使用不同的实现类
    @Override
    public Object call(){
        while (true){
            lock.lock();//打开锁
            if (ticket > 0) {
                ticket--;
                System.out.println(Thread.currentThread().getName() + "售出一张票,剩余" + ticket + "张");
            }
            lock.unlock();//关闭锁
            if (ticket == 0){
                break;
            }
        }
        return null;
    }
}
//测试类一样
注意:

线程同步:线程安全,效率低

正常:线程不安全,效率高

死锁 (互相谦让资源):

>不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

>出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

死锁的解决方法:

减少同步资源的定义避免嵌套同步

根本解决方案:搭建集群

线程通信(了解)

在线程同步的基础上增加了一个线程通信的功能

效果对比(线程同步): (线程通信):(同步互斥)

​ 窗口一售票,剩余999张 窗口一售票,剩余999张

​ 窗口一售票,剩余998张 窗口二售票,剩余998张

​ 窗口二售票,剩余997张 窗口三售票,剩余997张

​ 窗口二售票,剩余996张 窗口一售票,剩余996张

增加了三个方法:wait方法、notify方法、notifyAll方法

综合案例:生产者和消费者问题

假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费

如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止

如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

总而言之就是存一个取一个,取一个存一个

/**
 * Product 商品类。 生产者生产,消费者购买
 */
public class Product {
    private String type;
    private String name;
    boolean flag = false;//想让生产者现生产,flag为真生产者等待,为假生产
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public synchronized void setProduct(String name,String type){
        if (flag){//为真,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        this.setName(name);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        this.setType(type);
        System.out.println("我生产完了:"+getName()+"--"+getType());
        flag = true;//设置回true
        notify();//唤醒消费者
    }
    public synchronized void getProduction(){
        if(!flag){//为假,消费者等待
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("我是消费者,我买走了"+getName()+"--"+getType());
        flag = false;
        notify();//唤醒生产者
    }
}
/**
 * ProductionThread生产者线程
 */
public class ProductionThread extends Thread{
    private Product product;
    public ProductionThread(Product product){
        this.product = product;
    }
    @Override
    public void run() {

        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0){
                product.setProduct("巧克力","德芙");
            }else {
                product.setProduct("啤酒","雪花");
            }
        }
    }
}
/**
 * ConsumptionThread消费者线
 */
public class ConsumptionThread extends Thread{
    private Product product;
    public ConsumptionThread(Product product){
        this.product = product;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            product.getProduction();
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Product product = new Product();
        ProductionThread pt = new ProductionThread(product);
        ConsumptionThread ct = new ConsumptionThread(product);
        pt.start();
        ct.start();
    }
}
总结:wait(),notify(),notifyAll()

首先要知道:

synchronized(this):在方法内部(也是代码块中)或者代码块中使用synchronized(this)来锁定当前对象,确保同一时刻只有一个线程可以访问该对象的synchronized方法或者代码块。

synchronized(对象):和this不同的是来锁定指定对象,其他没区别。

介绍一下wait、notify、notifyAll(线程通信的三个方法
三者都是Java中Object类中定义的用于实现线程之间同步和通信的方法
wait()方法使当前线程进入等待状态,并释放对象锁。
notify()方法用于唤醒等待在当前对象上的一个线程。
notifyAll()方法用于唤醒等待在当前对象上的所有线程。
可以实现线程之间进行同步和通信,控制它们的执行顺序和互斥访问共享资源。
通常用于多个线程共享同一个对象,并需要等待其他线程的信号来执行特定操作的场景,比如生产者-消费者模式。
使用wait()方法:在同步代码块或同步方法中调用对象的wait()方法,使当前线程进入等待状态,并释放对象锁。
使用notify()方法:在同步代码块或同步方法中调用对象的notify()方法,唤醒等待在当前对象上的一个线程。
使用notifyAll()方法:在同步代码块或同步方法中调用对象的notifyAll()方法,唤醒等待在当前对象上的所有线程。

锁池-------------------synchronized

等待池--------------------wait(),notify(),notifyAll()

wait方法让当前线程在对象上等待,直到notify唤醒单个线程或notifyAll唤醒全部线程,必须在同步块或同步方法中使用。`

如果一个线程调用了某个对象的wait方法,那么该线程进入到该对象的等待池中(并且已经将锁释放),如果未来的某一时刻,另外一个线程调用了相同对象的notify方法或者notifyAll方法 那么该等待池中的线程就会被唤起,然后进入到对象的锁池里面去获得该对象的锁,如果获得锁成功后,那么该线程就会沿着wait方法之后的路径继续执行。注意是沿着wait方法之后

注意:

wait方法、notify方法、notifyAll方法:(一般先让自己wait然后notify唤醒其他线程)

方法名作 用
final void wait()表示线程一直等待,直到其它线程通知才会继续执行下面代码
void wait(long timeout)线程等待指定毫秒参数的时间
final void wait(long timeout,int nanos)线程等待指定毫秒、微妙的时间
final void notify()唤醒一个处于等待状态的线程。注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
final void notifyAll()唤醒同一个对象上所有调用wait()方法的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争

是必须放在同步方法或者同步代码块中才生效的 (因为在同步的基础上进行线程的通信才是有效的)

sleep和wait的区别:

sleep进入阻塞状态没有释放锁,wait进入阻塞状态但是同时释放了锁

综合练习:打印12A34B56C….5152Z(同步锁里要用while循环判断)

上面题真假不用特殊考虑,但是这个不行:

编写两个线程,一个线程打印1-52的整数,另一个线程打印字母A-Z。打印顺序为12A34B56C….5152Z。即按照整数和字母的顺序从小到大打印,并且每打印两个整数后,打印一个字母,交替循环打印,直到打印到整数52和字母Z结束。

要求:

​ 编写打印类Printer,声明私有属性index,初始值为1,用来表示是第几次打印。

​ 在打印类Printer中编写打印数字的方法print(int i),3的倍数就使用wait()方法等待,否则就输出i,使用notifyAll()进行唤醒其它线程。

​ 在打印类Printer中编写打印字母的方法print(char c),不是3的倍数就等待,否则就打印输出字母c,使用notifyAll()进行唤醒其它线程。

​ 编写打印数字的线程NumberPrinter继承Thread类,声明私有属性private Printer p;在构造方法中进行赋值,实现父类的run方法,调用Printer类中的输出数字的方法。

​ 编写打印字母的线程LetterPrinter继承Thread类,声明私有属性private Printer p;在构造方法中进行赋值,实现父类的run方法,调用Printer类中的输出字母的方法。

​ 编写测试类Test,创建打印类对象,创建两个线程类对象,启动线程。

public class Printer {
    private int index=1;//设为1,方便计算3的倍数
    //打印数字的方法,每打印两个数字,等待打印一个字母
    public synchronized void print(int i){
        while(index%3==0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.print(""+i);
        index++;//++和notifyAll位置可以换
        notifyAll();

    }
    //打印字母,每打印一个字母,等待打印两个数字
    public synchronized void print(char c){
        while(index%3!=0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(""+c);
        notifyAll();
        index++;
    }
}
public class NumberPrinter extends Thread {
    private Printer p;
    public NumberPrinter(Printer p){
        this.p=p;
    }
    public void run(){
        for(int i=1;i<=52;i++){
            p.print(i);
        }
    }
}
public class LetterPrinter extends Thread {
    private Printer p;
    public LetterPrinter(Printer p){
        this.p=p;
    }
    public void run(){
        for(char c='A';c<='Z';c++){
            p.print(c);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Printer p=new Printer();     //创建打印机对象
        Thread t1=new NumberPrinter(p);  //创建线程对象
        Thread t2=new LetterPrinter(p); //创建线程对象
        t1.start();  //启动线程
        t2.start(); //启动线程
    }
}

自己练习:三个窗口交替售票

public class Printer {
    private int index = 1000;
    public int getIndex() {
        return index;
    }
    public void setIndex(int index) {
        this.index = index;
    }
    public synchronized void print1(String c) {//因为用wait和notifyAll要用锁
        while (index % 3 == 0 || index % 3 == 2 || index < 0){
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println(c+"卖出一张,剩余"+index);
        index--;
        notifyAll();
        print1(c);
    }
    public synchronized void print2(String c) {
        while (index % 3 == 0 || index % 3 == 1 || index < 0){
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println(c+"卖出一张,剩余"+index);
        index--;
        notifyAll();
        print2(c);
    }
    public synchronized void print3(String c) {
        while (index % 3 == 1 || index % 3 == 2 || index < 0){
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println(c+"卖出一张,剩余"+index);
        index--;
        notifyAll();
        print3(c);
    }
}
public class Number1Printer extends Thread{
    private Printer p;
    public Number1Printer(Printer p) {
        this.p = p;
    }
    public Number1Printer() {
    }

    @Override
    public void run() {
            p.print1("窗口一");
    }
}
public class Number2Printer extends Thread{
    private Printer p;
    public Number2Printer(Printer p) {
        this.p = p;
    }
    public Number2Printer() {
    }

    @Override
    public void run() {
        p.print2("窗口2");
    }
}
public class Number3Printer extends Thread{
    private Printer p;
    public Number3Printer(Printer p) {
        this.p = p;
    }
    public Number3Printer() {
    }

    @Override
    public void run() {
        p.print3("|||窗口");
    }
}
package com.jr.testother;
public class Test {
    public static void main(String[] args) {
        Printer printer = new Printer();
        Number1Printer number1Printer = new Number1Printer(printer);
        Number2Printer number2Printer = new Number2Printer(printer);
        Number3Printer number3Printer = new Number3Printer(printer);
        number1Printer.start();
        number2Printer.start();
        number3Printer.start();
    }
}

网络编程(了解)

网络编程:

把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。

IP、PORT

通信两个重要的要素:IP+PORT

通信协议:

设备之间进行传输的时候,必须遵照一定的规则 —》通信协议:

TCP协议:可靠的

可当做是微信视频

建立连接:三次握手

1.客户端告诉服务器有一个新的客户端

2.服务器告诉客户端我能收到你的信息

3.客户端告诉我能收到你的信息

释放连接:四次挥手

1.客户端告诉服务器我想断开连接

2.服务器告诉客户端我知道你想断开了

3.服务器断开测试是否断开

4.客户端测试是否断开

UDP协议:不可靠的

不管对方是否接收,就是传(跟广播一样)

InetAddress、InetSocketAddress(了解)

前情提要:File —》 封装盘符一个文件

InetAddress–>封装了IP
public class Test01 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) throws UnknownHostException {
        //封装IP:
        //InetAddress ia = new InetAddress();
//不能直接创建对象,因为InetAddress()被default修饰了。
        InetAddress ia = InetAddress.getByName("192.168.199.217");
        System.out.println(ia);

        InetAddress ia2 = InetAddress.getByName("localhost");
//localhost指代的是本机的ip地址
        System.out.println(ia2);

        InetAddress ia3 = InetAddress.getByName("127.0.0.1");
//127.0.0.1指代的是本机的ip地址
        System.out.println(ia3);

        InetAddress ia4 = InetAddress.getByName("LAPTOP-CRIVSRRU");
//封装计算机名
        System.out.println(ia4);

        InetAddress ia5 = InetAddress.getByName("www.mashibing.com");
//封装域名
        System.out.println(ia5);

        System.out.println(ia5.getHostName());//获取域名
        System.out.println(ia5.getHostAddress());//获取ip地址
    }
}
InetSocketAddress–>封装了IP,端口号
public class Test02 {
    //这是一个main方法,是程序的入口:
    public static void main(String[] args) {
        InetSocketAddress isa = new InetSocketAddress("192.168.199.217",8080);
        System.out.println(isa);
        System.out.println(isa.getHostName());
        System.out.println(isa.getPort());

        InetAddress ia = isa.getAddress();
        System.out.println(ia.getHostName());
        System.out.println(ia.getHostAddress());
    }
}

网络通信原理:套接字(了解)

基于TCP的网络编程
综合案例:客户端多次发送信息到服务器,服务器接收后进行回复:

要先启动服务器,再启动客户端

【服务器端】:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
 * 网络通信的服务器端server
 */
public class Server {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        System.out.println("服务器已启动,等待发送请求");
        ServerSocket serverSocket = new ServerSocket(6666);//服务器的端口
        Socket socket = serverSocket.accept();//accept阻塞方法,
        while (true) {
            InputStream is = socket.getInputStream();//拿输入流
            DataInputStream dis = new DataInputStream(is);
            String ss = dis.readUTF();
            System.out.println("收到客户端内容:\t"+ss);

            //可手动回复客户端:
            OutputStream os = socket.getOutputStream();
            DataOutputStream dos = new DataOutputStream(os);
//            s = sc.next();
//            dos.writeUTF(s);
            dos.writeUTF("你个弟弟!回复大哥退出程序");
            if (ss.equals("大哥")){
                dos.close();
                os.close();
                dis.close();
                is.close();
                socket.close();
                serverSocket.close();
                System.out.println("断开连接");
                break;
            }
        }

    }
}

【客户端】:

import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
 * 网络编程:Client客户端
 */
public class Client {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        System.out.println("客户端启动,请输入向客户端发送的信息:");
        //创建Socket对象,同时确定服务器的IP+端口号
        Socket socket = new Socket("127.0.0.1",6666);
        //获得管道对象
        DataInputStream dis = null;
        DataOutputStream dos = null;
        String s = " ";
        while (true) {
            OutputStream os = socket.getOutputStream();
            dos = new DataOutputStream(os);
            //手动写出内容
            String next = sc.next();
            dos.writeUTF(next);
            //接收服务器传来的信息
            InputStream is = socket.getInputStream();
            dis = new DataInputStream(is);
            s = dis.readUTF();
            System.out.println("服务器发来信息:"+s);
            if ("大哥".equals(next)){
                dis.close();//关闭管道对象资源
                is.close();
                dos.close();
                os.close();
                socket.close();
                System.out.println("断开连接");
                break;
            }
        }
    }
}

面试题(重点)

进程和线程有什么联系和区别?

1.定义:

  1. 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。
  2. 线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),一个线程可以创建和撤销另一个线程;

2.进程和线程的关系:

(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。

(3)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

(4)处理机分给线程,即真正在处理机上运行的是线程。

(5)线程是指进程内的一个执行单元,也是进程内的可调度实体。

3.线程与进程的区别:

(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。

(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。

(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。

(4)系统开销:在创建或撤销进程的时候,由于系统都要为之分配和回收资源,导致系统的明显大于创建或撤销线程时的开销。但进程有独立的地址空间,进程崩溃后,在保护模式下不会对其他的进程产生影响,而线程只是一个进程中的不同的执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但是在进程切换时,耗费的资源较大,效率要差些

创建线程的两多种方式分别是什么,优缺点是什么?

方式1:继承Java.lang.Thread类,并覆盖run() 方法。优势:编写简单;劣势:无法继承其它父类

public class ThreadDemo1 {
	public static void main(String args[]) {
		MyThread1 t = new MyThread1();
		t.start();
		while (true) {
			System.out.println("兔子领先了,别骄傲");
		}
	}
}
class MyThread1 extends Thread {
	public void run() {
		while (true) {
			System.out.println("乌龟领先了,加油");
		}
	}
}

方式2:实现Java.lang.Runnable接口,并实现run()方法。优势:可继承其它类,多线程可共享同一个Thread对象;劣势:编程方式稍微复杂,如需访问当前线程,需调用Thread.currentThread()方法

public class ThreadDemo2 {
	public static void main(String args[]) {
		MyThread2 mt = new MyThread2();
		Thread t = new Thread(mt);
		t.start();
		while (true) {
			System.out.println("兔子领先了,加油");
		}
	}
}
class MyThread2 implements Runnable {
	public void run() {
		while (true) {
			System.out.println("乌龟超过了,再接再厉");
		}
	}
}
继承Thread类和实现Runnable接口的区别继承Thread类会将线程的代码和线程本身耦合在一起,不够灵活,而实现Runnable接口可以将线程的代码和线程本身分开,使得代码更加清晰和可维护。
由于java是单继承的语言,如果继承了Thread类就无法继承其他类,而实现Runnable接口则不受此限制,可以继续继承其他类。
Java创建线程后,调用start()方法和run()的区别

两种方法的区别

  1. start:

    用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

  2. run:

    run()方法只是类的一个普通方法而已,如果直接调用run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待

    run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。

这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。

两种方式的比较 :

实际中往往采用实现Runable接口,一方面因为java只支持单继承,继承了Thread类就无法再继续继承其它类,而且Runable接口只有一个run方法;另一方面通过结果可以看出实现Runable接口才是真正的多线程。

线程中start()和run()的区别他们是Java中用于启动线程的两种方式。
start()方法会启动一个新的线程,并使得该线程处于就绪状态,等待CPU调度执行,调度执行时才会执行run()方法中的代码,此时线程进入执行状态
线程的生命周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

(1)生命周期的五种状态

新建(new Thread)

当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。

例如:Thread t1=new Thread();

就绪(runnable)

线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();

运行(running)

线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。

死亡(dead)

当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

自然终止:正常运行run()方法后终止

异常终止:调用stop()方法让一个线程终止运行

堵塞(blocked)

由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

正在等待:调用wait()方法。(调用motify()方法回到就绪状态)

被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)

什么是socket套接字(重点)
什么是socket套接字
Socket套接字是计算机网络编程中的一种抽象概念,是一种通信机制,用于客户端和服务器之间进行数据交互和通信。
Socket套接字通常用于网络编程中,涉及多个计算机之间进行通信和数据交换的场景。比如在Web开发中,可以使用Socket套接字实现客户端与服务器之间的数据传输,从而实现网页的交互功能。
首先:在客户端和服务器端分别创建Socket套接字对象
其次:客户端通过Socket套接字连接到服务器端的IP地址和端口号
然后:通过Socket套接字的输入输出流来进行数据的读写,实现客户端和服务器之间的数据交换。
最后:关闭流和Socket套接字连接,释放资源。

面试题(了解)

如何实现线程同步?

当多个线程访问同一个数据时,容易出现线程安全问题,需要某种方式来确保资源在某一时刻只被一个线程使用。需要让线程同步,保证数据安全

线程同步的实现方案:同步代码块和同步方法,均需要使用synchronized关键字

同步代码块:

public void makeWithdrawal(int amt) {
				synchronized (acct) {}
}

同步方法:

public synchronized void makeWithdrawal(int amt) {	}

线程同步的好处:解决了线程安全问题

线程同步的缺点:性能下降,可能会带来死锁

怎么实现线程同步(线程安全的一种方式)
线程同步是一种机制,是指在多个线程访问共享资源时确保同时只有一个线程能够访问共享资源。以避免出现竞争条件、数据不一致(如输出重复、顺序不对等问题)、死锁等情况。以确保数据的一致性和正确性
线程同步通常用于涉及多个线程同时访问共享资源的场景,比如生产者-消费者模式。在这些场景下,合理地使用线程同步可以保证程序的正确性和性能。
可以使用synchronized关键字或Lock锁把有安全隐患的代码块锁住,确保同时只有一个线程能够访问共享资源
也可以使用wait()、notify()、notifyAll()实现线程之间的等待和通知机制
关于同步锁的更多细节

Java中每个对象都有一个内置锁。

当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。

当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。

一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。

释放锁是指持锁线程退出了synchronized同步方法或代码块。

关于锁和同步,有一下几个要点:

1)、只能同步方法,而不能同步变量和类;

2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?

3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。

4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。

6)、线程睡眠时,它所持的任何锁都不会释放。

7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。

9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:

public int fix(int y) {
    synchronized (this) {
        x = x - y;
    }
    return x;
}

当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:

public synchronized int getX() {
    return x++;
}

public int getX() {
    synchronized (this) {
        return x;
    }
}

效果是完全一样的。

简述sleep( )和wait( )有什么区别?

sleep()是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,也就是说,在线程里面决定.好比如说,我要做的事情是 “点火->烧水->煮面”,而当我点完火之后我不立即烧水,我要休息一段时间再烧.对于运行的主动权是由我的流程来控制。

而wait(),首先,这是由某个确定的对象来调用的,将这个对象理解成一个传话的人,当这个人在某个线程里面说"暂停!“,也是 thisObj.wait(),这里的暂停是阻塞,还是"点火->烧水->煮饭”,thisObj就好比一个监督我的人站在我旁边,本来该线 程应该执行1后执行2,再执行3,而在2处被那个对象喊暂停,那么我就会一直等在这里而不执行3,但正个流程并没有结束,我一直想去煮饭,但还没被允许, 直到那个对象在某个地方说"通知暂停的线程启动!",也就是thisObj.notify()的时候,那么我就可以煮饭了,这个被暂停的线程就会从暂停处 继续执行。
其实两者都可以让线程暂停一段时间,但是本质的区别是一个线程的运行状态控制,一个是线程之间的通讯的问题

Java中实现线程通信的三个方法的作用是什么?

Java提供了3个方法解决线程之间的通信问题,均是java.lang.Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常。

方法名作 用
final void wait()表示线程一直等待,直到其它线程通知才会继续执行下面代码
void wait(long timeout)线程等待指定毫秒参数的时间
final void wait(long timeout,int nanos)线程等待指定毫秒、微妙的时间
final void notify()唤醒一个处于等待状态的线程。注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
final void notifyAll()唤醒同一个对象上所有调用wait()方法的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争
IP地址和端口号

• IP地址

– 用来标志网络中的一个通信实体的地址。通信实体可以是计算机,路由器等。

• IP地址分类

– IPV4:32位地址,以点分十进制表示,如192.168.0.1

– IPV6:128位(16个字节)写成8个16位的无符号整数,每个整数用四个十六进制位表示,数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984

• 特殊的IP地址

– 127.0.0.1 本机地址

– 192.168.0.0–192.168.255.255私有地址,属于非注册地址,专门为组织机构内部使用。

• 端口:port

– IP地址用来标志一台计算机,但是一台计算机上可能提供多种应用程序,使用端口来区分这些应用程序。

– 端口是虚拟的概念,并不是说在主机上真的有若干个端口。通过端口,可以在一个主机上运行多个网络应用程序。

– 端口范围0—65535,16位整数

• 端口分类

– 公认端口 0—1023 比如80端口分配给WWW,21端口分配给FTP,22端口分配给SSH,23端口分配给telnet,25端口分配给smtp

– 注册端口 1024—49151 分配给用户进程或应用程序

– 动态/私有端口 49152–65535

• 理解IP和端口的关系

– IP地址好比每个人的地址(门牌号),端口好比是房间号。必须同时指定IP地址和端口号才能够正确的发送数据

– IP地址好比为电话号码,而端口号就好比为分机号。

介绍OSI七层模型和TCP/IP模型
  1. OSI(Open System Interconnection),开放式系统互联参考模型 。是一个逻辑上的定义,一个规范,它把网络协议从逻辑上分为了7层。每一层都有相关、相对应的物理设备,比如常规的路由器是三层交换设备,常规的交换机是二层交换设备。OSI七层模型是一种框架性的设计方法,建立七层模型的主要目的是为解决异种网络互连时所遇到的兼容性问题,其最主要的功能就是帮助不同类型的主机实现数据传输。它的最大优点是将服务、接口和协议这三个概念明确地区分开来,通过七个层次化的结构模型使不同的系统不同的网络之间实现可靠的通讯。
  2. TCP/IP协议是Internet最基本的协议、Internet国际互联网络的基础,主要由网络层的IP协议和传输层的TCP协议组成。TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
  3. ISO制定的OSI参考模型的过于庞大、复杂招致了许多批评。伴随着互联网的流行,其本身所采用的TCP/IP协议栈获得了更为广泛的应用和认可。在TCP/IP参考模型中,去掉了OSI参考模型中的会话层和表示层(这两层的功能被合并到应用层实现)。同时将OSI参考模型中的数据链路层和物理层合并为主机到网络层。
介绍一下网络通信模型最常见的网络通信模型包括OSI开放式系统互联模型和TCP/IP传输控制协议/网际协议模型
OSI将网络通信分为七层分别为:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
TCP/IP将网络通信分为四层分别为:链路层、网络层、传输层、应用层。
链路层:负责在物理网络上发送和接收数据。
网络层:负责进行数据包的路由和转发。
传输层:负责在网络中的两个节点之间建立、维护和终止数据传输。
应用层:包含各种网络应用程序,为用户提供通信服务和应用功能,如HTTP等协议
  • 51
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值