我的编程空间,编程开发者的网络收藏夹
学习永远不晚

java线程的基础实例解析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

java线程的基础实例解析

一、线程初步认识

1、什么是线程

操作系统运行一个程序会为其启动一个进程。例如,启动一个Java程序会创建一个Java进程。现代操作系统调度的最小单元是线程,线程也称为轻量级进程(Light Weight Process),一个进程中可以创建一个到多个线程,线程拥有自己的计数器、堆栈和局部变量等属性,并且能访问共享的内存变量。处理器会通过快速切换这些线程,来执行程序。

2、Java本身就是多线程

示例代码


package com.lizba.p2;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Arrays;

public class MultiThread {
    public static void main(String[] args) {
        // 获取Java线程管理MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 获取线程和线程堆栈信息;
        // boolean lockedMonitors = false  不需要获取同步的monitor信息;
        // boolean lockedSynchronizers = false  不需要获取同步的synchronizer信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 打印线程ID和线程name
        Arrays.stream(threadInfos).forEach(threadInfo -> {
            System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
        });
    }
}

输出结果(不一定一致):

[6]Monitor Ctrl-Break // idea中特有的线程(不用管)

[5]Attach Listener // JVM进程间的通信线程

[4]Signal Dispatcher // 分发处理发送给JVM信号的线程

[3]Finalizer // 调用对象的finalizer线程

[2]Reference Handler // 清楚Reference的线程

[1]main // main线程,用户程序入口

总结:从输出结果不难看出,Java程序本身就是多线程的。它不仅仅只有一个main线程在运行,而是main线程和其他多个线程在同时运行。

3、为什么要使用多线程

使用多线程的好处如下:

1.更多处理器核心

计算机处理器核心数增多,由以前的高主频向多核心技术发展,现在的计算机更擅长于并行计算,因此如何充分利用多核心处理器是现在的主要问题。线程是操作系统调度的最小单元,一个程序作为一个进程来运行,它会创建多个线程,而一个线程在同一时刻只能运行在一个处理器上。因此一个进程如果能使用多线程计算,将其计算逻辑分配到多个处理器核心上,那么相比单线程运行将会有更显著的性能提升。

2.更快响应时间

在复杂业务场景中,我们可以将非强一致性关联的业务派发给其他线程处理(或者使用消息队列)。这样可以减少应用响应用户请求的时间

3.更好的编程模型

合理使用Java的提供的多线程编程模型,能使得程序员更好的解决问题,而不需要过于复杂的考虑如何将其多线程化。 ​

4、线程的优先级

现代操作系统基本采用的是时间片分配的方式来调度线程,也就是操作系统将CPU的运行分为一个个时间片,线程会分配的若干时间片,当线程时间片用完了,就会发生线程调度等待下次时间片的分配。线程在一次CPU调度中能执行多久,取决于所分时间片的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。

在Java线程中,线程的优先级的可设置范围是1-10,默认优先级是5,理论上优先级高的线程分配时间片数量要优先于低的线程(部分操作系统这个设置是不生效的);

示例代码


package com.lizba.p2;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class Priority {
    
    private static volatile boolean notStart = true;
    
    private static volatile boolean notEnd = true;
    public static void main(String[] args) throws InterruptedException {
        List<Job> jobs = new ArrayList<>();
        // 设置5个优先级为1的线程,设置5个优先级为10的线程
        for (int i = 0; i < 10; i++) {
            int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
            Job job = new Job(priority);
            jobs.add(job);
            Thread thread = new Thread(job, "Thread:" + i);
            thread.setPriority(priority);
            thread.start();
        }
        notStart = false;
        TimeUnit.SECONDS.sleep(10);
        notEnd = false;
        jobs.forEach(
                job -> System.out.println("Job priority : " + job.priority + ", Count : " + job.jobCount)
        );
    }
    
    static class Job implements Runnable {
        private int priority;
        private long jobCount;
        public Job(int priority) {
            this.priority = priority;
        }
        @Override
        public void run() {
            while (notStart) {
                // 让出CPU时间片,等待下次调度
                Thread.yield();
            }
            while (notEnd) {
                // 让出CPU时间片,等待下次调度
                Thread.yield();
                jobCount++;
            }
        }
    }
}

执行结果:

在这里插入图片描述

从输出结果上来看,优先级为1的线程和优先级为10的线程执行的次数非常相近,因此这表明程序正确性是不能依赖线程的优先级高低的。

5、线程的状态

线程的生命周期如下:

状态名称 说明
NEW 初始状态,线程被构建,并未调用start()方法
RUNNABLE 运行状态,Java线程将操作系统中的就绪和运行两种状态统称为“运行中”
BLOCKED 阻塞状态,线程阻塞于锁
WAITING 等待状态,线程进入等待状态,进入该状态表示当前线程需要等待其他线程作出一些特定动作(通知或中断)
TIME_WAITING 超时等待,先比WAITING可以在指定的时间内自行返回
TERMINATED 终止状态,表示当前线程已经执行完毕

通过代码来查看Java线程的状态

代码示例:


package com.lizba.p2;
import java.util.concurrent.TimeUnit;

public class SleepUtil {
    public static final void sleepSecond(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package com.lizba.p2;

public class ThreadStateDemo {
    public static void main(String[] args) {
        // TimeWaiting
        new Thread(new TimeWaiting(), "TimeWaitingThread").start();
        // Waiting
        new Thread(new Waiting(), "WaitingThread").start();
        // Blocked1和Blocked2一个获取锁成功,一个获取失败
        new Thread(new Blocked(), "Blocked1Thread").start();
        new Thread(new Blocked(), "Blocked2Thread").start();
    }
    // 线程不断的进行睡眠
    static class TimeWaiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                SleepUtil.sleepSecond(100);
            }
        }
    }
    // 线程等待在Waiting.class实例上
    static class Waiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    // 该线程Blocked.class实例上加锁,不会释放该锁
    static class Blocked implements Runnable {
        @Override
        public void run() {
            synchronized (Blocked.class) {
                while (true) {
                    SleepUtil.sleepSecond(100);
                }
            }
        }
    }
}

使用JPS查看Java进程:

在这里插入图片描述

查看示例代码ThreadStateDemo进程ID是2576,键入jstack 2576查看输出:

在这里插入图片描述

整理输出结果: 在这里插入图片描述

线程名称 线程状态
Blocked2Thread BLOCKED (on object monitor),阻塞在获取Blocked.class的锁上
Blocked1Thread TIMED_WAITING (sleeping)
WaitingThread WAITING (on object monitor)
TimeWaitingThread TIMED_WAITING (sleeping)

总结:

线程在自身生命周期中不是规定处于某一个状态,而是随着代码的执行在不同的状态之间进行切换。

Java线程的状态变化图如下:

在这里插入图片描述

Java线程状态变迁图

总结:

  • 线程创建后,调用start()方法开始运行
  • 线程执行wait()方法后,线程进入等待状态,进入等待的线程需要依靠其他线程才能够返回到运行状态
  • 超时等待相当于在等待状态的基础上增加了超时限制,达到设置的超时时间后返回到运行状态
  • 线程执行同步方法或代码块时,未获取到锁的线程,将会进入到阻塞状态。
  • 线程执行完Runnable的run()方法之后进入到终止状态
  • 阻塞在Java的concurrent包中Lock接口的线程是等待状态,因为Lock接口阻塞的实现使用的是Daemon线程

​6、Daemon线程

简介:

Daemon线程是一种支持型线程,它的主要作用是程序中后台调度和支持性工作。当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出。Daemon线程需要在启动之前设置,不能在启动之后设置。

设置方式:


Thread.setDaemon(true)

需要特别注意的点:

Daemon线程被用作支持性工作的完成,但是在Java虚拟机退出时Daemon线程的finally代码块不一定执行。

示例代码:


package com.lizba.p2;

public class DaemonRunner implements Runnable{
    @Override
    public void run() {
        try {
            SleepUtil.sleepSecond(100);
        } finally {
            System.out.println("DaemonRunner finally run ...");
        }
    }
}

测试:


package com.lizba.p2;

public class DaemonTest {
    public static void main(String[] args) {
        Thread t = new Thread(new DaemonRunner(), "DaemonRunner");
        t.setDaemon(true);
        t.start();
    }
}

输出结果:

在这里插入图片描述

总结:

不难发现,DaemonRunner的run方法的finally代码块并没有执行,这是因为,当Java虚拟机中已经没有非Daemon线程时,虚拟机会立即退出,虚拟机中的所以daemon线程需要立即终止,所以线程DaemonRunner会被立即终止,finally并未执行。​

二、线程启动和终止

1、构造线程

运行线程之前需要构造一个线程对象,线程对象在构造的时候需要设置一些线程的属性,这些属性包括线程组、线程的优先级、是否是daemon线程、线程名称等信息。

代码示例:

来自java.lang.Thread


  
    private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
		// 设置线程名称
        this.name = name;
		// 当前线程设置为该线程的父线程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess();
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        g.addUnstarted();
		// 设置线程组
        this.group = g;
        // 将daemon属性设置为父线程的对应的属性
        this.daemon = parent.isDaemon();
        // 将prority属性设置为父线程的对应的属性
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        // 复制父线程的InheritableThreadLocals属性
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        
        this.stackSize = stackSize;
       // 设置一个线程id
        tid = nextThreadID();
    }

总结:

在上述代码中,一个新构建的线程对象时由其parent线程来分配空间的,而child继承了parent是否为Daemon、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时会分配一个唯一的ID来标志线程。此时一个完整的能够运行的线程对象就初始化好了,在堆内存中等待运行。​

2、什么是线程中断

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以通过调用静态方法Thread.interrupted()对当前线程的中断标志位进行复位。

如下情况不能准确判断线程是否被中断过:

1.线程已经终止运行,即使被中断过,isInterrupted()方法也会返回false

2.方法抛出InterruptedException异常,即使被中断过,调用isInterrupted()方法将会返回false,这是因为抛出InterruptedException之前会清除中断标志。

示例代码:


package com.lizba.p2;

public class Interrupted {
    public static void main(String[] args) {
        // sleepThread不停的尝试睡眠
        Thread sleepThread = new Thread(new SleepRunner(), "sleepThread");
        sleepThread.setDaemon(true);
        // busyThread
        Thread busyThread = new Thread(new BusyRunner(), "busyThread");
        busyThread.setDaemon(true);
        // 启动两个线程
        sleepThread.start();
        busyThread.start();
        // 休眠5秒,让sleepThread和busyThread运行充分
        SleepUtil.sleepSecond(5);
        // 中断两个线程
        sleepThread.interrupt();
        busyThread.interrupt();
        System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
        System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
        // 睡眠主线程,防止daemon线程退出
        SleepUtil.sleepSecond(2);
    }
    static class SleepRunner implements Runnable {
        @Override
        public void run() {
            while (true) {
                SleepUtil.sleepSecond(10);
            }
        }
    }
    static class BusyRunner implements Runnable {
        @Override
        public void run() {
            while (true) {}
        }
    }
}

查看运行结果:

在这里插入图片描述

总结:

抛出InterruptedException的是sleepThread线程,虽然两者都被中断过,但是sleepThread线程的中断标志返回的是false,这是因为TimeUnit.SECONDS.sleep(seconds)会抛出InterruptedException异常,抛出异常之前,sleepThread线程的中断标志被清除了。但是,busyThread一直在运行没有抛出异常,中断位没有被清除。

3、suspend()、resume()和stop()

举例:线程这三个方法,相当于QQ音乐播放音乐时的暂停、恢复和停止操作。(注意这些方法已经过期了,不建议使用。) ​

示例代码:


package com.lizba.p2;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;

public class Deprecated {
    static DateFormat format = new SimpleDateFormat("HH:mm:ss");
    public static void main(String[] args) {
        Thread printThread = new Thread(new PrintThread(), "PrintThread");
        printThread.start();
        SleepUtil.sleepSecond(3);
        // 暂停printThread输出
        printThread.suspend();
        System.out.println("main suspend PrintThread at " + format.format(new Date()));
        SleepUtil.sleepSecond(3);
        // 恢复printThread输出
        printThread.resume();
        System.out.println("main resume PrintThread at " + format.format(new Date()));
        SleepUtil.sleepSecond(3);
        // 终止printThread输出
        printThread.stop();
        System.out.println("main stop PrintThread at " + format.format(new Date()));
        SleepUtil.sleepSecond(3);
    }
    static class PrintThread implements Runnable {
        @Override
        public void run() {
           while (true) {
               System.out.println(Thread.currentThread().getName() + "Run at "
               + format.format(new Date()));
               SleepUtil.sleepSecond(1);
           }
        }
    }
}

输出结果:

在这里插入图片描述

总结:

上述代码执行输出的结果,与API说明和我们的预期完成一致,但是看似正确的代码却隐藏这很多问题。​

存在问题:

  • suspend()方法调用后不会释放已占有的资源(比如锁),可能会导致死锁
  • stop()方法在终结一个线程时不能保证资源的正常释放,可能会导致程序处于不确定的工作状态​

4、正确的终止线程

  1. 调用线程的interrupt()方法
  2. 使用一个Boolean类型的变量来控制是否停止任务并终止线程

示例代码:


package com.lizba.p2;

public class ShutDown {
    public static void main(String[] args) {
        Runner one = new Runner();
        Thread t = new Thread(one, "CountThread");
        t.start();
        SleepUtil.sleepSecond(1);
        t.interrupt();
        Runner two = new Runner();
        t = new Thread(two, "CountThread");
        t.start();
        SleepUtil.sleepSecond(1);
        two.cancel();
    }
    private static class Runner implements Runnable {
        private long i;
        private volatile boolean on = true;
        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()) {
                i++;
            }
            System.out.println("Count i = " +i);
        }
        
        public void cancel() {
            on = false;
        }
    }
}

输出结果:

在这里插入图片描述

总结:

main线程通过中断操作和cancel()方法均可使CountThread得以终止。这两种方法终止线程的好处是能让线程在终止时有机会去清理资源。做法更加安全和优雅。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

java线程的基础实例解析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java基础之线程锁的示例分析

这篇文章将为大家详细讲解有关Java基础之线程锁的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、 synchronized关键字1.对象锁a.当使用对象锁的时候,注意要是相同的对象,并且当有线
2023-06-20

怎样解析Java基础多线程

怎样解析Java基础多线程,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。 多线程是Java学习的非常重要的方面,是每个Java程序员必须掌握的基本技能。一、进程
2023-06-02

Java线程安全基础概念解析

Java线程安全初步了解。JAVA线程安全从总体上来说,是指Java对象在多线程运行环境下的一种特性,表现为常规(区别于特殊调用情况)情况下每次调用都能得到正确的逻辑结果。从本质上来说,将对象的方法行为加上了同步控制逻辑,而调用者无须做其他
2023-05-31

Java基础夯实之线程问题全面解析

操作系统支持多个应用程序并发执行,每个应用程序至少对应一个进程 。进程是资源分配的最小单位,而线程是CPU调度的最小单位。本文将带大家全面解析线程相关问题,感兴趣的可以了解一下
2022-11-13

夯实Java基础,一篇文章全解析线程问题

进程是资源分配的最小单位,而线程是CPU调度的最小单位。每一个进程中至少有一个线程,同一进程的所有线程共享该进程的所有资源,多个线程可以完成多个不同的任务,也就是我们常说的并发多线程。
Java基础CPU2024-12-01

Java基础泛型实例分析

这篇“Java基础泛型实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java基础泛型实例分析”文章吧。一、泛型概述:
2023-06-29

Java零基础学习多线程的示例

这篇文章给大家分享的是有关Java零基础学习多线程的示例的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。守护线程从线程分类上可以分为:用户线程(以上讲的都是用户线程),另一个是守护线程。守护线程是这样的,所有的用户
2023-06-06

Java基础的示例分析

小编给大家分享一下Java基础的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1、Java主要特点简单性、跨平台性、分布性、安全性、健壮性、平台独立与可移
2023-06-20

Java线程实例分析

今天小编给大家分享一下Java线程实例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。启动一个线程T1=new YourT
2023-06-03

Java基础之volatile应用实例分析

这篇文章主要介绍“Java基础之volatile应用实例分析”,在日常操作中,相信很多人在Java基础之volatile应用实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java基础之volatile
2023-07-02

java基础之this的示例分析

小编给大家分享一下java基础之this的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1、this关键字首先需要提醒的是,在整个Java之中,this是
2023-06-20

Java基础之Maven的示例分析

这篇文章将为大家详细讲解有关Java基础之Maven的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、Maven是什么?Maven是一个跨平台的项目管理工具。作为Apache组织的一个颇为成功的
2023-06-15

java数组基础的示例分析

这篇文章主要介绍java数组基础的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!数组数组(Array):相同类型数据的集合。Java 数组初始化的两种方法: 静态初始化: 程序员在初始化数组时为数组每个元素赋
2023-05-30

Java并发线程池实例分析讲解

这篇文章主要介绍了Java并发线程池实例,线程池——控制线程创建、释放,并通过某种策略尝试复用线程去执行任务的一个管理框架,从而实现线程资源与任务之间一种平衡
2023-02-02

Java多线程回调方法实例解析

所谓回调,就是客户程序C调用服务程序S中的某个方法A,然后S又在某个时候反过来调用C中的某个方法B,对于C来说,这个B便叫做回调方法。下面看一个实际例子来理解:本示例设置一个提问者,一个回答者,而回答者需要回答提问者一个很深奥的问题时,这时
2023-05-30

JAVA多线程编程实例分析

今天小编给大家分享一下JAVA多线程编程实例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.三个售票窗口同时出售20张
2023-06-27

Java并发编程之介绍线程安全基础的示例

这篇文章主要介绍了Java并发编程之介绍线程安全基础的示例,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。线程安全基础1.线程安全问题2.账户取款案例3.同步代码块synchr
2023-06-06

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录