源码版本:jdk8

其中的部分论证和示例代码:Java_Concurrency


类声明:

Thread本身实现了Runnable接口

Runnable:任务,《java编程思想》中表示该命名不好,或许叫Task更好;

Thread:线程,执行任务的载体;

public class Thread implements Runnable

**构造方法: **

构造时,可以指定线程组,线程运行任务Runnable对象,线程名和栈大小

线程的所有构造方法,都是通过init()实现

/**
* Thread的所有public构造方法传入的inheritThreadLocals均为true
* 只有一个权限为default的构造方法传入为false
* 所有的构造方法实现均是调用了该方法
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 构造方法中传入的线程名不能为null
// 默认线程名:"Thread-" + nextThreadNum()
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 被创建出来的线程是创建线程的子线程
Thread parent = currentThread();
// 新建线程的线程组
// 如果构造方法传入的线程组为null,则通过这个流程来决定其线程组,通常,新建线程的线程组为其创建线程的线程组
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
// 子线程默认拥有父线程的优先级和daemon属性
this.daemon = parent.isDaemon();
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);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
// 产生新的tid并设置
tid = nextThreadID();
}
public long getId() {
return tid;
}

上面的源码中有一个stackSize的参数,这个一般不会使用,其具体请参考javadoc.

默认线程名:"Thread-" + nextThreadNum():

/* For autonumbering anonymous threads. */
private static int threadInitNumber;
// 同步方法,保证tid的唯一性和连续性
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
// 线程名可以被修改
public final synchronized void setName(String name) {
checkAccess();
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
if (threadStatus != 0) {
setNativeName(name);
}
}
public final String getName() {
return name;
}

ThreadGroup:

《java编程思想》有云:把ThreadGroup当做是一次不成功的尝试即可,不用理会

示例:ThreadGroupTest.java

public final ThreadGroup getThreadGroup() {
return group;
}

Thread类默认的toString()中有使用其ThreadGroup:

public String toString() {
ThreadGroup group = getThreadGroup();
if (group != null) {
return "Thread[" + getName() + "," + getPriority() + "," + group.getName() + "]";
} else {
return "Thread[" + getName() + "," + getPriority() + "," + "" + "]";
}
}

守护线程:

具体参考:Java 守护线程概述

示例:Daemon.java

/**
* Marks this thread as either a {@linkplain #isDaemon daemon} thread
* or a user thread. The Java Virtual Machine exits when the only
* threads running are all daemon threads.
* <p> This method must be invoked before the thread is started.
*/
public final void setDaemon(boolean on) {
checkAccess();
// 只有在线程开始前设置才有效
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
public final boolean isDaemon() {
return daemon;
}
// 返回当前线程是否还活着
// start()后且还没有死亡的线程均视为活着的线程
public final native boolean isAlive();

线程优先级和线程状态:

Java 线程状态与优先级相关的知识题目

示例:ThreadState.java

Priority.java

构造时如果不指定,默认线程优先级就是NORM_PRIORITY

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10; public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
// 优先级范围 1~10
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
// 设置优先级
setPriority0(priority = newPriority);
}
} public final int getPriority() {
return priority;
}
private native void setPriority0(int newPriority);
/**
* A thread state. A thread can be in one of the following states:
* NEW:A thread that has not yet started is in this state.
* RUNNABLE:A thread executing in the Java virtual machine is in this state.
* BLOCKED:A thread that is blocked waiting for a monitor lock is in this state.
* WAITING:A thread that is waiting indefinitely for another thread to
* perform a particular action is in this state.
* TIMED_WAITING:A thread that is waiting for another thread to perform an action
* for up to a specified waiting time is in this state.
* TERMINATED:A thread that has exited is in this state.
* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
* @since 1.5
*/
public enum State {
// 创建后,但是没有start(),调用了start()后,线程才算准备就绪,可以运行(RUNNABLE)
NEW,
// 正在运行或正在等待操作系统调度
RUNNABLE,
// 线程正在等待监视器锁
// 正在synchronized块/方法上等待获取锁,或者调用了Object.wait(),等待重新获得锁进入同步块
BLOCKED,
// 调用Object.wait(),Thread.join()或LockSupport.park()会进入该状态,注意这里的调用均为没有设置超时,
// 线程正在等待其他线程进行特定操作,比如,调用了Object.wait()的线程在另一个线程调用Object.notify()/Object.notifyAll()
// 调用了Thread.join()的线程在等待指定线程停止,join()的内部实现方式也是Object.wait(),只不过其Object就是线程对象本身
WAITING,
// 调用Thread.sleep(),Object.wait(long),Thread.join(long),
// LockSupport.parkNanos(long),LockSupport.parkUntil(long)会进入该状态,
// 注意,这里的调用均设置了超时
TIMED_WAITING,
// 线程执行完成,退出
TERMINATED;
}
// @since 1.5
public State getState() {
// get current thread state
return sun.misc.VM.toThreadState(threadStatus);
}

这里关于线程状态需要特别注意,在网上有很多博客和书籍对线程状态进行讲解,其中很大一部分是错误的,对于BLOCKED,WAITING和TIMED_WAITING有所误解,认为一个线程在被从Object.wait()中被唤醒时,会立即进入Runnable状态,其实不是的:

一个线程在被从Object.wait()中被唤醒时,会立即进入BLOCKED状态,这时其并没有获得锁,只是被唤醒了,再次开始对Object的监视器锁进行竞争;只有在其竞争获得锁之后才会进入RUNNABLE状态.

在理解java线程状态时,建议直接看Thread.State的注释,就看英文版本,最贴切,没有杂质,也最正确,其他的所有书上的讲解似乎都有些偏颇

线程状态示例:ThreadState.java

线程优先级示例:Priority.java

关于wait()/notify()具体相关,请参考其他相关博客;

run():

如果是构造Thread对象的时候,传入了该对象预期执行的任务----Runnable对象时,执行该任务,否则,什么都不做,当然,可以通过集成Thread类,重写run(),来修改其行为:

/* What will be run. */
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}

线程启动:

调用start()与调用run()的区别:懒得说

/* Java thread status for tools,
* initialized to indicate thread 'not yet started'
*/
private volatile int threadStatus = 0;
// 启动线程,JVM会调用当前Thread对象的run()
// 同步方法
public synchronized void start() {
// A zero status value corresponds to state "NEW".
// 如果调用时不是在线程状态不是NEW,则抛出IllegalThreadStateException
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
// 通过start0()来实现线程启动
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
} private native void start0();

线程打断:

示例:Interrupted.java

/* The object in which this thread is blocked in an interruptible I/O
* operation, if any. The blocker's interrupt method should be invoked
* after setting this thread's interrupt status.
*/
private volatile Interruptible blocker;
private final Object blockerLock = new Object(); /* Set the blocker field; invoked via sun.misc.SharedSecrets from java.nio code
*/
void blockedOn(Interruptible b) {
synchronized (blockerLock) {
blocker = b;
}
}
/**
* 打断当前执行线程
* 如果当前线程阻塞在Object.wait(),Thread.join(),Thread.sleep()上,
* 那么该线程会收到InterruptedException,且线程的打断标志会被清除;
* 如果当前线程阻塞在InterruptibleChannel上,那么该InterruptibleChannel
* 会被关闭,线程的打断标志会被置位,且当前线程会收到ClosedByInterruptException;
* 如果当前线程阻塞在Selector上,那么该Selector的selection操作将会立即返回一个非0的结果,
* 且Selector.wakeup()会被调用,线程的打断标志会被置位,
* 如果上述情况均不存在,将当前线程的打断标志置位
* 打断一个isAlive()返回false的线程没有效果,isInterrupted()仍然会返回false;
*/
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
// 在Interruptible上阻塞
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
private native void interrupt0(); /**
* 返回线程是否被打断(打断标志是否被置位)
* 传入的参数决定是否该方法是否会清除终端标志位
*/
private native boolean isInterrupted(boolean ClearInterrupted); public static boolean interrupted() {
return currentThread().isInterrupted(true);
} public boolean isInterrupted() {
return isInterrupted(false);
}

关于isInterrupted()和interrupted()的区别,上述源码表现得很明显

而且,也体现了NIO的可中断中断实现方式

注意:interrupt()不能中断执行阻塞IO操作的线程.

线程的礼让—— yield() sleep() join():

关于Thread.sleep()和Object.wait()的区别:参考链接

示例:Yield.java

Join.java

/**
* 暗示调度器让出当前线程的执行时间片,调度器可以选择忽略该暗示;
* 该方法在用来调试和测试时可能很有用,可以用来重现需要特殊条件才能复现的bug;
* 也可以用来进行并发优化等;
*/
public static native void yield(); /**
* 当前执行线程休眠指定毫秒在休眠期间,不释放任何当前线程持有的锁;
*/
public static native void sleep(long millis) throws InterruptedException; /**
* 当前执行线程休眠指定毫秒在休眠期间,不释放任何当前线程持有的锁;
* 如果当前被打断(该方法调用前或该方法调用时),抛出InterruptedException,同时将打断标志清掉
*/
public static void sleep(long millis, int nanos) throws InterruptedException {
// 取值范围检查
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
// 纳秒最后还是转换成了毫秒233333
// 可能是考虑都有些
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
/**
* 当前执行线程等待指定线程(也就是该调用发生的Thread对象)死后再继续执行;
* 可以设置超时,如果设置超时为0,则为不设置超时;
* 线程结束时(terminate),将会调用自身的notifyAll(),唤醒在该Thread对象上wait()的方法;
* 如果该线程被打断,该方法将抛出InterruptedException,并将打断标志位清除
*/
// 同步方法,同步当前Thread对象,所以才能在其内部调用wait()
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
// 使用isAlive()和wait()的循环实现
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
} public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
public final void join() throws InterruptedException {
join(0);
}
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}

被遗弃的方法——suspend() resume() stop():

示例:Deprecated.java

关于这些方法被遗弃的原因,具体参考:Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?

/**
* 挂起当前线程
* 弃用原因:容易导致死锁
*/
@Deprecated
public final void suspend() {
checkAccess();
suspend0();
}
/**
* 从suspend()中恢复线程运行
* 弃用原因:容易导致死锁
*/
@Deprecated
public final void resume() {
checkAccess();
resume0();
}
/**
* 强制线程停止执行;
* 通过抛出一个ThreadDeath的方式来停止线程;
* 废弃原因:stop()会师范所有已持有的锁的监视器,如果存在之前被这些监视器保护的对象处于一个不连续
* 的状态(inconsistent state),这些被损坏的对象将会对其他线程可见,出现不可预期的行为;
*/
@Deprecated
public final void stop() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
checkAccess();
if (this != Thread.currentThread()) {
security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
}
}
// A zero status value corresponds to "NEW", it can't change to
// not-NEW because we hold the lock.
if (threadStatus != 0) {
resume(); // Wake up thread if it was suspended; no-op otherwise
}
// The VM can handle all thread states
stop0(new ThreadDeath());
}

ThreadLocal:

在Thread类中有两个与ThreadLocal相关的成员变量

具体有关ThreadLocal,请参考:

一个故事讲明白线程的私家领地:ThreadLocal

ThreadLocal源码解读

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ClassLoader:

关于ClassLoader,我暂时知道的也不多,先知道这个,以后有机会专门研究一下ClassLoader:

/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;
/**
* 当前线程的ClassLoader,默认创建时是父线程的ClassLoader
* @return the context ClassLoader for this Thread, or {@code null}
* indicating the system class loader (or, failing that, the
* bootstrap class loader)
* @since 1.2
*/
@CallerSensitive
public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,Reflection.getCallerClass());
}
return contextClassLoader;
}
/**
* @param cl
* the context ClassLoader for this Thread, or null indicating the
* system class loader (or, failing that, the bootstrap class loader)
* @since 1.2
*/
public void setContextClassLoader(ClassLoader cl) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
contextClassLoader = cl;
}

线程栈轨迹:

// 特殊编程技巧
private static final StackTraceElement[] EMPTY_STACK_TRACE
= new StackTraceElement[0];
private native static StackTraceElement[][] dumpThreads(Thread[] threads);
private native static Thread[] getThreads(); // 打印当前线程的栈轨迹(StackTrace),通过新建一个异常的方式实现
// 注意:这是一个静态方法
public static void dumpStack() {
new Exception("Stack trace").printStackTrace();
}
/**
* 获得栈轨迹,返回的是一个数组
* 数组的第0个栈轨迹为最近调用的栈轨迹
* @since 1.5
*/
public StackTraceElement[] getStackTrace() {
if (this != Thread.currentThread()) {
// check for getStackTrace permission
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(SecurityConstants.GET_STACK_TRACE_PERMISSION);
}
// 不是活着的,返回的栈轨迹长度为0
if (!isAlive()) {
return EMPTY_STACK_TRACE;
}
StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this});
StackTraceElement[] stackTrace = stackTraceArray[0];
// a thread that was alive during the previous isAlive call may have
// since terminated, therefore not having a stacktrace.
if (stackTrace == null) {
// 这样就不会返回null,调用者也无需判断null了
stackTrace = EMPTY_STACK_TRACE;
}
return stackTrace;
} else {
// Don't need JVM help for current thread
return (new Exception()).getStackTrace();
}
}
/**
* 返回所有线程的栈轨迹
* @since 1.5
*/
public static Map<Thread, StackTraceElement[]> getAllStackTraces() {
// check for getStackTrace permission
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(
SecurityConstants.GET_STACK_TRACE_PERMISSION);
security.checkPermission(
SecurityConstants.MODIFY_THREADGROUP_PERMISSION);
} // Get a snapshot of the list of all threads
Thread[] threads = getThreads();
StackTraceElement[][] traces = dumpThreads(threads);
Map<Thread, StackTraceElement[]> m = new HashMap<>(threads.length);
for (int i = 0; i < threads.length; i++) {
StackTraceElement[] stackTrace = traces[i];
if (stackTrace != null) {
m.put(threads[i], stackTrace);
}
// else terminated so we don't put it in the map
}
return m;
}

UncaughtExceptionHandler:

示例:UncaughtExceptionHandlerEx.java

// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
// null unless explicitly set
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
/**
* 设置UncaughtExceptionHandler,该设置对所有线程有效
* 如果自身没有设置,则交给其线程组的UncaughtExceptionHandler处理,如果再没有,
* 则交给默认的的UncaughtExceptionHandler处理,也即这里设置的UncaughtExceptionHandler处理
* 注意这里的设置不应该设置为线程的线程组,这样的设置会造成死循环
* @since 1.5
*/
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setDefaultUncaughtExceptionHandler"));
}
defaultUncaughtExceptionHandler = eh;
}
/**
* 返回默认的UncaughtExceptionHandler,该UncaughtExceptionHandler对所有线程有效
* 这是一个静态方法
* @since 1.5
*/
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}
/**
* 返回该线程的UncaughtExceptionHandler,如果没有,返回该线程的线程组
* ThreadGroup本身实现了UncaughtExceptionHandler接口
* @since 1.5
*/
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
/**
* 设置该线程的UncaughtExceptionHandler
*/
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
/**
* uncaught exception 分发给UncaughtExceptionHandler
* 该方法被JVM调用
*/
private void dispatchUncaughtException(Throwable e) {
getUncaughtExceptionHandler().uncaughtException(this, e);
}

其他:

// 返回当前语句执行的线程
public static native Thread currentThread();
// 不能克隆线程
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
/**
* 返回该线程是否持有指定对象的监视器锁
* @since 1.4
*/
public static native boolean holdsLock(Object obj);

Thread类源码解析的更多相关文章

  1. Thread类源码剖析

    目录 1.引子 2.JVM线程状态 3.Thread常用方法 4.拓展点 一.引子 说来也有些汗颜,搞了几年java,忽然发现竟然没拜读过java.lang.Thread类源码,这次特地拿出来晒一晒. ...

  2. java.lang.Void类源码解析_java - JAVA

    文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerEx ...

  3. Java集合---Array类源码解析

    Java集合---Array类源码解析              ---转自:牛奶.不加糖 一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Prim ...

  4. Java集合---Arrays类源码解析

    一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型: ...

  5. ArrayList类源码解析——ArrayList动态数组的实现细节(基于JDK8)

    一.基本概念 ArrayList是一个可以添加对象元素,并进行元素的修改查询删除等操作的容器类.ArrayList底层是由数组实现的,所以和数组一样可以根据索引对容器对象所包含的元素进行快速随机的查询 ...

  6. Dom4j工具类源码解析

    话不多说,上源码: package com.changeyd.utils;import java.io.File;import java.io.FileNotFoundException;import ...

  7. Spring-IOC MethodInvokingFactoryBean 类源码解析

    MethodInvokingFactoryBean MethodInvokingFactoryBean的作用是,通过定义类和它的方法,然后生成的bean是这个方法的返回值,即可以注入方法返回值. Me ...

  8. 机器学习:weka中Evaluation类源码解析及输出AUC及交叉验证介绍

    在机器学习分类结果的评估中,ROC曲线下的面积AOC是一个非常重要的指标.下面是调用weka类,输出AOC的源码: try { // 1.读入数据集 Instances data = new Inst ...

  9. 23.mixin类源码解析

    mixin类用于提供视图的基本操作行为,注意mixin类提供动作方法,而不是直接定义处理程序方法 例如.get() .post(),这允许更灵活的定义,mixin从rest_framework.mix ...

随机推荐

  1. Git初识学习

    初始化一个Git仓库,使用git init命令. 添加文件到Git仓库,分两步: 使用命令git add <file>,注意,可反复多次使用,添加多个文件: 使用命令git commit ...

  2. 【python 3】 字典方法操作汇总

    基础数据类型:tuple 1.1  新增 dic["key"] = value                            字典中没有key就添加,有key就覆盖 dic ...

  3. node+ejs模板引擎的应用

    前言: 最近在开发一个关于后台管理系统的基础开发平台,解释一下就是不管什么管理系统都有一些相同的功能,但是又有一些细节不一样,这个基础平台就是实现对于基础功能可以进行快速开发,主要有自定义的生成功能代 ...

  4. d3.event=null

    d3功能奇多, 已经模块化,(其实感觉和react差不多了). 所以默认打包的单个文件 <script src="https://d3js.org/d3.v5.min.js" ...

  5. MySQL 8.0.12 基于Windows 安装教程(超级详细)

    MySQL 8.0.12 基于Windows 安装教程(超级详细) (一步一步来,装不了你找我!) 本教程仅适用Windows系统,如果你原本装了没装上,一定要先删除原本的数据库,执行:mysqld ...

  6. [ Servlet / JSP ] J2EE Web Application 中的 JSESSIONID 是什么?

    JSESSIONID is a cookie in J2EE web application which is used in session tracking. Since HTTP is a st ...

  7. linux blast

    建库 减压后,改名为blast,并在blas目录在建立db文件1,建立数据库makeblastdb -in db.fasta -dbtype nucl(prot) -parse_seqids -has ...

  8. pycharm配置QtDesigner

    QtDesigner C:\Qt\Qt5.12.2\5.12.2\mingw73_64\bin\designer.exe $ProjectFileDir$ Pyuic C:\Anaconda3\pyt ...

  9. img标签和 background 属性的使用分析

    在网页布局中引入图片,最常用的两个就是 img 标签和 background 属性了.但何时使用 img 标签,何时使用 backround 背景图像呢? <img> 标签定义 HTML ...

  10. 常见程序入口点(OEP)特征

    delphi: 55            PUSH EBP  8BEC          MOV EBP,ESP  83C4 F0       ADD ESP,-10  B8 A86F4B00   ...