从源码的角度再学「Thread」
前言
Java
中的线程是使用Thread
类实现的,Thread
在初学Java
的时候就学过了,也在实践中用过,不过一直没从源码的角度去看过它的实现,今天从源码的角度出发,再次学习Java Thread
,愿此后对Thread
的实践更加得心应手。
从注释开始
相信阅读过JDK
源码的同学都能感受到JDK
源码中有非常详尽的注释,阅读某个类的源码应当先看看注释对它的介绍,注释原文就不贴了,以下是我对它的总结:
Thread
是程序中执行的线程,Java
虚拟机允许应用程序同时允许多个执行线程每个线程都有优先级的概念,具有较高优先级的线程优先于优先级较低的线程执行
每个线程都可以被设置为守护线程
当在某个线程中运行的代码创建一个新的
Thread
对象时,新的线程优先级跟创建线程一致当
Java
虚拟机启动的时候都会启动一个叫做main
的线程,它没有守护线程,main
线程会继续执行,直到以下情况发送Runtime
类的退出方法exit
被调用并且安全管理器允许进行退出操作- 所有非守护线程均已死亡,或者
run
方法执行结束正常返回结果,或者run
方法抛出异常
创建线程第一种方式:继承
Thread
类,重写run
方法1//定义线程类
2class PrimeThread extends Thread {
3 long minPrime;
4 PrimeThread(long minPrime) {
5 this.minPrime = minPrime;
6 }
7 public void run() {
8 // compute primes larger than minPrime
9 . . .
10 }
11 }
12//启动线程
13PrimeThread p = new PrimeThread(143);
14p.start();创建线程第二种方式:实现
Runnable
接口,重写run
方法,因为Java
的单继承限制,通常使用这种方式创建线程更加灵活1//定义线程
2 class PrimeRun implements Runnable {
3 long minPrime;
4 PrimeRun(long minPrime) {
5 this.minPrime = minPrime;
6 }
7 public void run() {
8 // compute primes larger than minPrime
9 . . .
10 }
11 }
12//启动线程
13PrimeRun p = new PrimeRun(143);
14new Thread(p).start();创建线程时可以给线程指定名字,如果没有指定,会自动为它生成名字
除非另有说明,否则将
null
参数传递给Thread
类中的构造函数或方法将导致抛出NullPointerException
Thread 常用属性
阅读一个Java
类,先从它拥有哪些属性入手:
1//线程名称,创建线程时可以指定线程的名称
2private volatile String name;
3
4//线程优先级,可以设置线程的优先级
5private int priority;
6
7//可以配置线程是否为守护线程,默认为false
8private boolean daemon = false;
9
10//最终执行线程任务的`Runnable`
11private Runnable target;
12
13//描述线程组的类
14private ThreadGroup group;
15
16//此线程的上下文ClassLoader
17private ClassLoader contextClassLoader;
18
19//所有初始化线程的数目,用于自动编号匿名线程,当没有指定线程名称时,会自动为其编号
20private static int threadInitNumber;
21
22//此线程请求的堆栈大小,如果创建者没有指定堆栈大小,则为0。, 虚拟机可以用这个数字做任何喜欢的事情。, 一些虚拟机会忽略它。
23private long stackSize;
24
25//线程id
26private long tid;
27
28//用于生成线程ID
29private static long threadSeqNumber;
30
31//线程状态
32private volatile int threadStatus = 0;
33
34//线程可以拥有的最低优先级
35public final static int MIN_PRIORITY = 1;
36
37//分配给线程的默认优先级。
38public final static int NORM_PRIORITY = 5;
39
40//线程可以拥有的最大优先级
41public final static int MAX_PRIORITY = 10;
所有的属性命名都很语义化,其实已看名称基本就猜到它是干嘛的了,难度不大~~
Thread 构造方法
了解了属性之后,看看Thread
实例是怎么构造的?先预览下它大致有多少个构造方法:
查看每个构造方法内部源码,发现均调用的是名为init
的私有方法,再看init
方法有两个重载,而其核心方法如下:
1 /**
2 * Initializes a Thread.
3 *
4 * @param g 线程组
5 * @param target 最终执行任务的 `run()` 方法的对象
6 * @param name 新线程的名称
7 * @param stackSize 新线程所需的堆栈大小,或者 0 表示要忽略此参数
8 * @param acc 要继承的AccessControlContext,如果为null,则为 AccessController.getContext()
9 * @param inheritThreadLocals 如果为 true,从构造线程继承可继承的线程局部的初始值
10 */
11 private void init(ThreadGroup g, Runnable target, String name,
12 long stackSize, AccessControlContext acc,
13 boolean inheritThreadLocals) {
14 //线程名称为空,直接抛出空指针异常
15 if (name == null) {
16 throw new NullPointerException("name cannot be null");
17 }
18 //初始化当前线程对象的线程名称
19 this.name = name;
20 //获取当前正在执行的线程为父线程
21 Thread parent = currentThread();
22 //获取系统安全管理器
23 SecurityManager security = System.getSecurityManager();
24 //如果线程组为空
25 if (g == null) {
26 //如果安全管理器不为空
27 if (security != null) {
28 //获取SecurityManager中的线程组
29 g = security.getThreadGroup();
30 }
31 //如果获取的线程组还是为空
32 if (g == null) {
33 //则使用父线程的线程组
34 g = parent.getThreadGroup();
35 }
36 }
37
38 //检查安全权限
39 g.checkAccess();
40
41 //使用安全管理器检查是否有权限
42 if (security != null) {
43 if (isCCLOverridden(getClass())) {
44 security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
45 }
46 }
47
48 //线程组中标记未启动的线程数+1,这里方法是同步的,防止出现线程安全问题
49 g.addUnstarted();
50
51 //初始化当前线程对象的线程组
52 this.group = g;
53 //初始化当前线程对象的是否守护线程属性,注意到这里初始化时跟父线程一致
54 this.daemon = parent.isDaemon();
55 //初始化当前线程对象的线程优先级属性,注意到这里初始化时跟父线程一致
56 this.priority = parent.getPriority();
57 //这里初始化类加载器
58 if (security == null || isCCLOverridden(parent.getClass()))
59 this.contextClassLoader = parent.getContextClassLoader();
60 else
61 this.contextClassLoader = parent.contextClassLoader;
62 this.inheritedAccessControlContext =
63 acc != null ? acc : AccessController.getContext();
64 //初始化当前线程对象的最终执行任务对象
65 this.target = target;
66 //这里再对线程的优先级字段进行处理
67 setPriority(priority);
68 if (inheritThreadLocals && parent.inheritableThreadLocals != null)
69 this.inheritableThreadLocals =
70 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
71 //初始化当前线程对象的堆栈大小
72 this.stackSize = stackSize;
73
74 //初始化当前线程对象的线程ID,该方法是同步的,内部实际上是threadSeqNumber++
75 tid = nextThreadID();
76 }
另一个重载init
私有方法如下,实际上内部调用的是上述init
方法:
1private void init(ThreadGroup g, Runnable target, String name,
2 long stackSize) {
3 init(g, target, name, stackSize, null, true);
4 }
接下来看看所有构造方法:
空构造方法
1 public Thread() {
2 init(null, null, "Thread-" + nextThreadNum(), 0);
3 }内部调用的是
init
第二个重载方法,参数基本都是默认值,线程名称写死为"Thread-" + nextThreadNum()
格式,nextThreadNum()
为一个同步方法,内部维护一个静态属性表示线程的初始化数量+1:1 private static int threadInitNumber;
2 private static synchronized int nextThreadNum() {
3 return threadInitNumber++;
4 }自定义执行任务
Runnable
对象的构造方法1public Thread(Runnable target) {
2 init(null, target, "Thread-" + nextThreadNum(), 0);
3}与第一个构造方法区别在于可以自定义
Runnable
对象自定义执行任务
Runnable
对象和AccessControlContext
对象的构造方法1 Thread(Runnable target, AccessControlContext acc) {
2 init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
3}自定义线程组
ThreadGroup
和执行任务Runnable
对象的构造方法1public Thread(ThreadGroup group, Runnable target) {
2 init(group, target, "Thread-" + nextThreadNum(), 0);
3}自定义线程名称
name
的构造方法1 public Thread(String name) {
2 init(null, null, name, 0);
3}自定义线程组
ThreadGroup
和线程名称name
的构造方法1 public Thread(ThreadGroup group, String name) {
2 init(group, null, name, 0);
3}自定义执行任务
Runnable
对象和线程名称name
的构造方法1 public Thread(Runnable target, String name) {
2 init(null, target, name, 0);
3}自定义线程组
ThreadGroup
和线程名称name
和执行任务Runnable
对象的构造方法1 public Thread(ThreadGroup group, Runnable target, String name) {
2 init(group, target, name, 0);
3}全部属性都是自定义的构造方法
1 public Thread(ThreadGroup group, Runnable target, String name,
2 long stackSize) {
3 init(group, target, name, stackSize);
4}
Thread
提供了非常灵活的重载构造方法,方便开发者自定义各种参数的Thread
对象。
常用方法
这里记录一些比较常见的方法吧,对于Thread
中存在的一些本地方法,我们暂且不用管它~
设置线程名称
设置线程名称,该方法为同步方法,为了防止出现线程安全问题,可以手动调用Thread
的实例方法设置名称,也可以在构造Thread
时在构造方法中传入线程名称,我们通常都是在构造参数时设置
1 public final synchronized void setName(String name) {
2 //检查安全权限
3 checkAccess();
4 //如果形参为空,抛出空指针异常
5 if (name == null) {
6 throw new NullPointerException("name cannot be null");
7 }
8 //给当前线程对象设置名称
9 this.name = name;
10 if (threadStatus != 0) {
11 setNativeName(name);
12 }
13 }
获取线程名称
内部直接返回当前线程对象的名称属性
1 public final String getName() {
2 return name;
3 }
启动线程
1public synchronized void start() {
2 //如果不是刚创建的线程,抛出异常
3 if (threadStatus != 0)
4 throw new IllegalThreadStateException();
5
6 //通知线程组,当前线程即将启动,线程组当前启动线程数+1,未启动线程数-1
7 group.add(this);
8
9 //启动标识
10 boolean started = false;
11 try {
12 //直接调用本地方法启动线程
13 start0();
14 //设置启动标识为启动成功
15 started = true;
16 } finally {
17 try {
18 //如果启动呢失败
19 if (!started) {
20 //线程组内部移除当前启动的线程数量-1,同时启动失败的线程数量+1
21 group.threadStartFailed(this);
22 }
23 } catch (Throwable ignore) {
24 /* do nothing. If start0 threw a Throwable then
25 it will be passed up the call stack */
26 }
27 }
28 }
我们正常的启动线程都是调用Thread
的start()
方法,然后Java
虚拟机内部会去调用Thred
的run
方法,可以看到Thread
类也是实现Runnable
接口,重写了run
方法的:
1 @Override
2 public void run() {
3 //当前执行任务的Runnable对象不为空,则调用其run方法
4 if (target != null) {
5 target.run();
6 }
7 }
Thread
的两种使用方式:
- 继承
Thread
类,重写run
方法,那么此时是直接执行run
方法的逻辑,不会使用target.run();
- 实现
Runnable
接口,重写run
方法,因为Java
的单继承限制,通常使用这种方式创建线程更加灵活,这里真正的执行逻辑就会交给自定义Runnable
去实现
设置守护线程
本质操作是设置daemon
属性
1public final void setDaemon(boolean on) {
2 //检查是否有安全权限
3 checkAccess();
4 //本地方法,测试此线程是否存活。, 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态
5 if (isAlive()) {
6 //如果线程先启动后再设置守护线程,将抛出异常
7 throw new IllegalThreadStateException();
8 }
9 //设置当前守护线程属性
10 daemon = on;
11 }
判断线程是否为守护线程
1 public final boolean isDaemon() {
2 //直接返回当前对象的守护线程属性
3 return daemon;
4 }
线程状态
先来个线程状态图:
获取线程状态:
1 public State getState() {
2 //由虚拟机实现,获取当前线程的状态
3 return sun.misc.VM.toThreadState(threadStatus);
4 }
线程状态主要由内部枚举类State
组成:
1 public enum State {
2
3 NEW,
4
5
6 RUNNABLE,
7
8
9 BLOCKED,
10
11
12 WAITING,
13
14
15 TIMED_WAITING,
16
17
18 TERMINATED;
19 }
- NEW:刚刚创建,尚未启动的线程处于此状态
- RUNNABLE:在Java虚拟机中执行的线程处于此状态
- BLOCKED:被阻塞等待监视器锁的线程处于此状态,比如线程在执行过程中遇到
synchronized
同步块,就会进入此状态,此时线程暂停执行,直到获得请求的锁 - WAITING:无限期等待另一个线程执行特定操作的线程处于此状态
- 通过 wait() 方法等待的线程在等待 notify() 方法
- 通过 join() 方法等待的线程则会等待目标线程的终止
- TIMED_WAITING:正在等待另一个线程执行动作,直到指定等待时间的线程处于此状态
- 通过 wait() 方法,携带超时时间,等待的线程在等待 notify() 方法
- 通过 join() 方法,携带超时时间,等待的线程则会等待目标线程的终止
- TERMINATED:已退出的线程处于此状态,此时线程无法再回到 RUNNABLE 状态
线程休眠
这是一个静态的本地方法,使当前执行的线程休眠暂停执行 millis
毫秒,当休眠被中断时会抛出InterruptedException
中断异常
1 /**
2 * Causes the currently executing thread to sleep (temporarily cease
3 * execution) for the specified number of milliseconds, subject to
4 * the precision and accuracy of system timers and schedulers. The thread
5 * does not lose ownership of any monitors.
6 *
7 * @param millis
8 * the length of time to sleep in milliseconds
9 *
10 * @throws IllegalArgumentException
11 * if the value of {@code millis} is negative
12 *
13 * @throws InterruptedException
14 * if any thread has interrupted the current thread. The
15 * <i>interrupted status</i> of the current thread is
16 * cleared when this exception is thrown.
17 */
18 public static native void sleep(long millis) throws InterruptedException;
检查线程是否存活
本地方法,测试此线程是否存活。 如果一个线程已经启动并且尚未死亡,则该线程处于活动状态。
1 /**
2 * Tests if this thread is alive. A thread is alive if it has
3 * been started and has not yet died.
4 *
5 * @return <code>true</code> if this thread is alive;
6 * <code>false</code> otherwise.
7 */
8 public final native boolean isAlive();
线程优先级
- 设置线程优先级
1 /**
2 * Changes the priority of this thread.
3 * <p>
4 * First the <code>checkAccess</code> method of this thread is called
5 * with no arguments. This may result in throwing a
6 * <code>SecurityException</code>.
7 * <p>
8 * Otherwise, the priority of this thread is set to the smaller of
9 * the specified <code>newPriority</code> and the maximum permitted
10 * priority of the thread's thread group.
11 *
12 * @param newPriority priority to set this thread to
13 * @exception IllegalArgumentException If the priority is not in the
14 * range <code>MIN_PRIORITY</code> to
15 * <code>MAX_PRIORITY</code>.
16 * @exception SecurityException if the current thread cannot modify
17 * this thread.
18 * @see #getPriority
19 * @see #checkAccess()
20 * @see #getThreadGroup()
21 * @see #MAX_PRIORITY
22 * @see #MIN_PRIORITY
23 * @see ThreadGroup#getMaxPriority()
24 */
25 public final void setPriority(int newPriority) {
26 //线程组
27 ThreadGroup g;
28 //检查安全权限
29 checkAccess();
30 //检查优先级形参范围
31 if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
32 throw new IllegalArgumentException();
33 }
34 if((g = getThreadGroup()) != null) {
35 //如果优先级形参大于线程组最大线程最大优先级
36 if (newPriority > g.getMaxPriority()) {
37 //则使用线程组的优先级数据
38 newPriority = g.getMaxPriority();
39 }
40 //调用本地设置线程优先级方法
41 setPriority0(priority = newPriority);
42 }
43 }
线程中断
有一个stop()
实例方法可以强制终止线程,不过这个方法因为太过于暴力,已经被标记为过时方法,不建议程序员再使用,因为强制终止线程会导致数据不一致的问题。
这里关于线程中断的方法涉及三个:
1//实例方法,通知线程中断,设置标志位
2 public void interrupt(){}
3 //静态方法,检查当前线程的中断状态,同时会清除当前线程的中断标志位状态
4 public static boolean interrupted(){}
5 //实例方法,检查当前线程是否被中断,其实是检查中断标志位
6 public boolean isInterrupted(){}
interrupt() 方法解析
1/**
2 * Interrupts this thread.
3 *
4 * <p> Unless the current thread is interrupting itself, which is
5 * always permitted, the {@link #checkAccess() checkAccess} method
6 * of this thread is invoked, which may cause a {@link
7 * SecurityException} to be thrown.
8 *
9 * <p> If this thread is blocked in an invocation of the {@link
10 * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
11 * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
12 * class, or of the {@link #join()}, {@link #join(long)}, {@link
13 * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
14 * methods of this class, then its interrupt status will be cleared and it
15 * will receive an {@link InterruptedException}.
16 *
17 * <p> If this thread is blocked in an I/O operation upon an {@link
18 * java.nio.channels.InterruptibleChannel InterruptibleChannel}
19 * then the channel will be closed, the thread's interrupt
20 * status will be set, and the thread will receive a {@link
21 * java.nio.channels.ClosedByInterruptException}.
22 *
23 * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
24 * then the thread's interrupt status will be set and it will return
25 * immediately from the selection operation, possibly with a non-zero
26 * value, just as if the selector's {@link
27 * java.nio.channels.Selector#wakeup wakeup} method were invoked.
28 *
29 * <p> If none of the previous conditions hold then this thread's interrupt
30 * status will be set. </p>
31 *
32 * <p> Interrupting a thread that is not alive need not have any effect.
33 *
34 * @throws SecurityException
35 * if the current thread cannot modify this thread
36 *
37 * @revised 6.0
38 * @spec JSR-51
39 */
40 public void interrupt() {
41 //检查是否是自身调用
42 if (this != Thread.currentThread())
43 //检查安全权限,这可能导致抛出{@link * SecurityException}。
44 checkAccess();
45
46 //同步代码块
47 synchronized (blockerLock) {
48 Interruptible b = blocker;
49 //检查是否是阻塞线程调用
50 if (b != null) {
51 //设置线程中断标志位
52 interrupt0();
53 //此时抛出异常,将中断标志位设置为false,此时我们正常会捕获该异常,重新设置中断标志位
54 b.interrupt(this);
55 return;
56 }
57 }
58 //如无意外,则正常设置中断标志位
59 interrupt0();
60 }
- 线程中断方法不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦~
- 只能由自身调用,否则可能会抛出
SecurityException
- 调用中断方法是由目标线程自己决定是否中断,而如果同时调用了
wait
,join
,sleep
等方法,会使当前线程进入阻塞状态,此时有可能发生InterruptedException
异常 - 被阻塞的线程再调用中断方法是不合理的
- 中断不活动的线程不会产生任何影响
检查线程是否被中断:
1 /**
2 * Tests whether this thread has been interrupted. The <i>interrupted
3 * status</i> of the thread is unaffected by this method.
4
5 测试此线程是否已被中断。, 线程的<i>中断*状态</ i>不受此方法的影响。
6 *
7 * <p>A thread interruption ignored because a thread was not alive
8 * at the time of the interrupt will be reflected by this method
9 * returning false.
10 *
11 * @return <code>true</code> if this thread has been interrupted;
12 * <code>false</code> otherwise.
13 * @see #interrupted()
14 * @revised 6.0
15 */
16 public boolean isInterrupted() {
17 return isInterrupted(false);
18 }
静态方法,会清空当前线程的中断标志位:
1 /**
2 *测试当前线程是否已被中断。, 此方法清除线程的* <i>中断状态</ i>。, 换句话说,如果要连续两次调用此方法,则* second调用将返回false(除非当前线程再次被中断,在第一次调用已清除其中断的*状态 之后且在第二次调用已检查之前), 它)
3 *
4 * <p>A thread interruption ignored because a thread was not alive
5 * at the time of the interrupt will be reflected by this method
6 * returning false.
7 *
8 * @return <code>true</code> if the current thread has been interrupted;
9 * <code>false</code> otherwise.
10 * @see #isInterrupted()
11 * @revised 6.0
12 */
13 public static boolean interrupted() {
14 return currentThread().isInterrupted(true);
15 }
总结
记录自己阅读Thread
类源码的一些思考,不过对于其中用到的很多本地方法只能望而却步,还有一些代码没有看明白,暂且先这样吧,如果有不足之处,请留言告知我,谢谢!后续会在实践中对Thread
做出更多总结记录。
最后
由于篇幅较长,暂且先记录这些吧,后续会不定期更新原创文章,欢迎关注公众号 「张少林同学」!
从源码的角度再学「Thread」的更多相关文章
- 从源码的角度再看 React JS 中的 setState
在这一篇文章中,我们从源码的角度再次理解下 setState 的更新机制,供深入研究学习之用. 在上一篇手记「深入理解 React JS 中的 setState」中,我们简单地理解了 React 中 ...
- 从源码的角度看 React JS 中批量更新 State 的策略(上)
在之前的文章「深入理解 React JS 中的 setState」与 「从源码的角度再看 React JS 中的 setState」 中,我们分别看到了 React JS 中 setState 的异步 ...
- 从源码的角度看 React JS 中批量更新 State 的策略(下)
这篇文章我们继续从源码的角度学习 React JS 中的批量更新 State 的策略,供我们继续深入学习研究 React 之用. 前置文章列表 深入理解 React JS 中的 setState 从源 ...
- Android AsyncTask完全解析,带你从源码的角度彻底理解
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11711405 我们都知道,Android UI是线程不安全的,如果想要在子线程里进 ...
- [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...
- 从源码的角度分析ViewGruop的事件分发
从源码的角度分析ViewGruop的事件分发. 首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View ...
- 从源码的角度解析View的事件分发
有好多朋友问过我各种问题,比如:onTouch和onTouchEvent有什么区别,又该如何使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图 ...
- 从源码的角度看Activity是如何启动的
欢迎访问我的个人博客,原文链接:http://wensibo.top/2017/07/03/Binder/ ,未经允许不得转载! 大家好,今天想与大家一起分享的是Activity.我们平时接触的最多的 ...
- 何时调用getView?——从源码的角度给出解答
先来看ListView类中的makeAndAddView方法: 没有数据变化:从mRecycler中取得可视的view 数据有变化:obtainView /** * 获取视图填充到列表的item中去, ...
随机推荐
- set 续3
-------siwuxie095 set 技巧高级篇: 1.利用 set /a 进行赋值 在开启变量延迟情况下,要判断数组 S!n! 的值的情况, 不 ...
- IE6789浏览器使用console.log类似的方法输出调试内容但又不影响页面正常运行
问题来源:外网IE下,触发js报错.经检测,未清除console造成.清除console后,解决. 问题原因:console.log 原先是 Firefox 的“专利”,严格说是安装了 Firebug ...
- 职责链模式c#(处理车)
using System;using System.Collections.Generic;using System.Linq;using System.Text; namespace 职责链模式{ ...
- Android JIN简单单步调试
ADTr20已经比较完美支持NDK开发了.可以集成ndk编译,只需在项目右键Add Native Support,就能自动生成jni文件,并部署编译环境(注意这个过程是不可逆的,手动删除jni文件后, ...
- 用Swift实现一款天气预报APP(二)
这个系列的目录: 用Swift实现一款天气预报APP(一) 用Swift实现一款天气预报APP(二) 用Swift实现一款天气预报APP(三) 上篇中主要讲了界面的一些内容,这篇主要讨论网络请求,获得 ...
- plsql导入导出表数据
导出表结构: Tools(工具)-->Export User Objects(导出用户对象) -->选择要导出的表(包括Sequence等)-->.sql文件,导出的都为sql文件 ...
- Oracle EBS应用笔记整理 (转自IT++ flyingkite)
***************************************************** Author: Flyingkite Blog: http://space.itpub. ...
- (译)C#参数传递
前言 菜鸟去重复之Sql的问题还没有得到满意的答案.如果哪位大哥有相关的资料解释,能够分享给我,那就太谢谢了. 接触C#一年了,感觉很多东西还是很模糊,像C#中的委托和事件 有些东西看多了不用也还是不 ...
- tomcat-java_opts设置说明
The JAVA_OPTS environment variable can be used to specify additional arguments to the JVM JBoss will ...
- docker容器怎么设置开机启动
https://my.oschina.net/lwenhao/blog/1923003 docker服务器.以及容器设置自动启动 一.docker服务设置自动启动 说明:适用于yum安装的各种服务 查 ...