多线程基础

如果我们打开一个资源管理器,这里跑了很多的进程,每一个他都是一个程序,在每一个进程当中,

他就会有若干个线程,可以通过工具每个进程有哪些线程,线程的id,使用线程的一个原因呢,是因为进程的切换,

包括进程的上下文切换呢,是非常非常重量级的一个操作,如果我们使用多进程去并行,并发数是不可能很高的,但是

线程是在进程的里面,它是进程里面更小的执行单元,更细微的一个执行单元,线程可以更为广泛的来做并发的一个

设计,而事实上如果我们有一个进程,他如果是多线程的话呢,我们可以把多线程的线程,把它指定分配到CPU上执行,

如果我的CPU很多,有32个CPU,否则默认的情况是这样,被均分到各个CPU上去执行,在JAVA当中线程的概念跟操作系统

的线程概念是非常相似的,它会把JAVA的线程映射到操作系统的线程去,如果你在JAVA当中建立一个线程,比如说在windows

上,等于调用了createThread方法,去把一个线程创建出来,这两者是等价的,如果你在JAVA当中创建一个线程的话,这个线程的

基本状态,在这张图里已经基本上显示出来了,如果一个线程你刚刚把它新建出来

那么这个线程的状态是new的状态,一个处于new状态的线程呢,他其实并没有开始工作,它是静态的分配一个实体在那边,

当你调用实例的start方法之后呢,线程才真正的被启动,在启动之后呢,线程就进入到Runnable的状态,runnable指的是说

这个线程呢,可以执行了,我一切准备就绪了,但是RUNNABLE的这个状态呢,并不表示说这个线程,一定在CPU上被执行,因为

他只是说,CPU在JAVA虚拟机的这个层面,我这个线程的所有的工作,包括他可能需要某些锁的申请,我都已经调度合理了,已经

到位了,但是如果你系统本身很繁忙,CPU还没有办法分出时间片执行,那这个时候呢,那也是不能够执行的,但是Runnable状态

呢,对于我们上层来讲,都可以认为他已经在执行了,但是他有没有具体真实在执行,取决于物理CPU的调度,如果一个线程他

所有的工作都做完了,那么他自然就会终止掉,进入TERMINATED状态,表示这个线程的任务结束,有时候这个线程的执行过程当中,

不可避免的可能会去申请某些锁,包括他要去申请定时器,比如我们调用的synchronized方法,那么这个时候线程可能会被阻塞住,

也就是线程会被挂起,挂起的原因呢,是说这个线程,因为他要进入临界区之前,他先要申请到一个定时器,或者是对象的一把锁,

可能被其他的线程占用他,没有办法拿到,因此就导致说,如果一个线程他在执行过程当中,调用了wait方法,那么这个线程他就会

进入到一个等待状态,进入等待状态的线程呢,它会等待另外一些线程,对他进行一个通知,notify,如果他被通知到之后呢,他有等待

状态呢,他就可以切换为Runnable状态,继续执行,等待状态有两种,一种是无限期的等待,另外一种是有限期的等待,我就等待

7秒钟,有限的等待或者是无限的等待,线程的状态和其操作,下面我们看一下最简单的新建线程

t1就是线程的实例,调用start方法就可以把线程跑起来,对于初学者来讲容易犯的一个错误呢,调用了线程的

run方法,在这个线程当中,实际上是有两个可以调用的方法,一个start方法开启一个线程,另外一个是run方法,

run方法是继承于Runnable接口的,每一个线程它实际上都是Runnable的接口,接口的一个实现,他都是Runnable

接口的实现,start方法做什么呢,所谓的start方法呢,他只是在一个新的操作系统的线程上面,调用这个run方法,

换句话说,你调用了run方法,而不调用start方法呢,那你做的事情呢,叫start方法做的事情呢,是一样的,调用的

就是run函数,但是如果你调用run话呢,他并不会开启新的线程,而是在调用run当前这个线程当中,去执行你的这个

操作,而你只有使用了start方法,那才是在一个真的开的线程当中,去做run方法做的事情,另外一个要给大家介绍的

呢,是run方法的实现,我们知道Thread类表示一个线程,那么这个线程他实现了Runnable接口,那么在这个Runnable

接口当中呢,只有一个run方法,这个run方法怎么做呢

/**
 * If this thread was constructed using a separate
 * <code>Runnable</code> run object, then that
 * <code>Runnable</code> object's <code>run</code> method is called;
 * otherwise, this method does nothing and returns.
 * <p>
 * Subclasses of <code>Thread</code> should override this method.
 *
 * @see     #start()
 * @see     #stop()
 * @see     #Thread(ThreadGroup, Runnable, String)
 */
@Override
public void run() {
	if (target != null) {
		target.run();
	}
}
    
就是会调用target的run方法,target是什么呢,又是一个runnable的接口

/* What will be run. */
private Runnable target;

所以这就是说,我们这个线程的使用呢,是有两种方式,第一种方式呢,是说我直接覆盖这种run方法,也就是说你父类的

run方法,是怎么实现的呢,target的这个run,那么我能不能无视你这个target呢,我直接把你这个线程创建起来的时候

呢,其实我传给你的这个Runnable接口,是一个null,

/**
 * Initializes a Thread with the current AccessControlContext.
 * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)
 */
private void init(ThreadGroup g, Runnable target, String name,
				  long stackSize) {
	init(g, target, name, stackSize, null, true);
}

这个参数是个null,也就是你这个target本身是不存在的

/**
 * Allocates a new {@code Thread} object. This constructor has the same
 * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
 * {@code (null, null, gname)}, where {@code gname} is a newly generated
 * name. Automatically generated names are of the form
 * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
 */
public Thread() {
	init(null, null, "Thread-" + nextThreadNum(), 0);
}

如果这个target本身是不存在的,那我可以简单把你这个run方法给重载了,把我要做的事情呢,我把这个run方法重载之后呢,

我重启这个start方法,开启start方法之后呢,我就可以让start方法在新的线程中去做我要做的事情,那另外一个方式呢,

我不重载你这个方法,你该怎么样就怎么样,我给你这个target的这个实例呢,传一个给你,这样你会自动的去调用我target里面的

run,我给你传一个Runnable的实例,

/**
 * Allocates a new {@code Thread} object. This constructor has the same
 * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
 * {@code (null, target, gname)}, where {@code gname} is a newly generated
 * name. Automatically generated names are of the form
 * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
 *
 * @param  target
 *         the object whose {@code run} method is invoked when this thread
 *         is started. If {@code null}, this classes {@code run} method does
 *         nothing.
 */
public Thread(Runnable target) {
	init(null, target, "Thread-" + nextThreadNum(), 0);
}

这个构造一个线程的时候呢,传给他一个Runnable接口的实例,这个时候呢,我不需要去重载这个run方法,我只要start就行了,

这个run方法当中呢,会自动调用我这个Runnable接口,他的一些行为,这个线程的创建就这么简单,下面我们来看一下线程的终止

对于线程终止有一个方法stop,如果你调用stop方法,那线程马上就终止,但是我们看一下,stop方法其实是不推荐使用的,

他有这么一个标志

    /**
     * Forces the thread to stop executing.
     * <p>
     * If there is a security manager installed, its <code>checkAccess</code>
     * method is called with <code>this</code>
     * as its argument. This may result in a
     * <code>SecurityException</code> being raised (in the current thread).
     * <p>
     * If this thread is different from the current thread (that is, the current
     * thread is trying to stop a thread other than itself), the
     * security manager's <code>checkPermission</code> method (with a
     * <code>RuntimePermission("stopThread")</code> argument) is called in
     * addition.
     * Again, this may result in throwing a
     * <code>SecurityException</code> (in the current thread).
     * <p>
     * The thread represented by this thread is forced to stop whatever
     * it is doing abnormally and to throw a newly created
     * <code>ThreadDeath</code> object as an exception.
     * <p>
     * It is permitted to stop a thread that has not yet been started.
     * If the thread is eventually started, it immediately terminates.
     * <p>
     * An application should not normally try to catch
     * <code>ThreadDeath</code> unless it must do some extraordinary
     * cleanup operation (note that the throwing of
     * <code>ThreadDeath</code> causes <code>finally</code> clauses of
     * <code>try</code> statements to be executed before the thread
     * officially dies).  If a <code>catch</code> clause catches a
     * <code>ThreadDeath</code> object, it is important to rethrow the
     * object so that the thread actually dies.
     * <p>
     * The top-level error handler that reacts to otherwise uncaught
     * exceptions does not print out a message or otherwise notify the
     * application if the uncaught exception is an instance of
     * <code>ThreadDeath</code>.
     *
     * @exception  SecurityException  if the current thread cannot
     *               modify this thread.
     * @see        #interrupt()
     * @see        #checkAccess()
     * @see        #run()
     * @see        #start()
     * @see        ThreadDeath
     * @see        ThreadGroup#uncaughtException(Thread,Throwable)
     * @see        SecurityManager#checkAccess(Thread)
     * @see        SecurityManager#checkPermission
     * @deprecated This method is inherently unsafe.  Stopping a thread with
     *       Thread.stop causes it to unlock all of the monitors that it
     *       has locked (as a natural consequence of the unchecked
     *       <code>ThreadDeath</code> exception propagating up the stack).  If
     *       any of the objects previously protected by these monitors were in
     *       an inconsistent state, the damaged objects become visible to
     *       other threads, potentially resulting in arbitrary behavior.  Many
     *       uses of <code>stop</code> should be replaced by code that simply
     *       modifies some variable to indicate that the target thread should
     *       stop running.  The target thread should check this variable
     *       regularly, and return from its run method in an orderly fashion
     *       if the variable indicates that it is to stop running.  If the
     *       target thread waits for long periods (on a condition variable,
     *       for example), the <code>interrupt</code> method should be used to
     *       interrupt the wait.
     *       For more information, see
     *       <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
     *       are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
     */
    @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());
    }

这个方法是被弃用的,建议不要去用这个stop方法,一个主要的原因是说stop方法,太过暴力,如果我们线程正在做一件事情,

你想暴力的给他stop掉,这个时候其实你并不知道这个线程,他当时他执行到哪一个语句当中,然后他就死掉了,这个时候其实它会

释放掉所有的锁,monitor这个锁它是会释放掉的,但是引起的问题是说,有可能会导致一些多线程的不一致性,比如现在我们有

两条记录,比如数据库有两条记录,一条是ID等于1,名字是小明,第二条记录ID等于2,名字是小王,如果我们有一个对象实例,

对象u就表示user,表示用户,这个用户含有两个字段,默认情况下都是null,一个是0一个是null,我为了防止对象同时被修改,

我可能在写之前,或者在读之前,我都要给这个对象加锁,读的时候要等待,我要确保你写完了我才会去读,那么写也是一样,也要加锁,

那现在我在写之前加了一个锁,然后我对这个数据进行写入,如果我想把ID等于1,name等于小明写进去,写进去之后呢,为正准备

开始写入小明,这个时候你很暴力的把我stop掉了,这样导致一个什么后果呢,是说我这个锁会被释放掉,释放的后果呢,如果有一个线程

正在读我这个对象呢,他就能够读进来了,他就能够读到我这个对象,但我很不幸的是,我这个name还没开始设,因为你stop太暴力,

导致我这个线程就结束了,因此就会出现说我这个对象当中,这个ID是记录1的,name却不是记录1的,也就是说这个数据出现了不一致,

这个时候读线程就会读到了,错误的数据,并且这种错误呢,这种行为,你是很难发现的,如果你正在程序的执行当中,遇到了这么一种

状况,他这种错误,它是不会有什么异常,和信息可以打印出来的,你只有当数据错的很离谱的时候呢,这个问题还是比较难查的,

这个stop方法一般来讲呢,不是非常推荐使用,只有当你确认程序非常简单,可以试一下,但是一般不会推荐使用,下面一个重要的

概念是线程中断

有关这个线程中断呢,提供了三个方法,一个是interrupt,第二个是isInterrupt,第三个是静态的,interrupted,

这个是实例方法,这个是静态方法,什么叫线程中断,大家可以简单这么理解,一个线程如果在跑的过程当中,我们可以

给他发送一条一个,可以给他打一个招呼,那么中断相当于我给他打一个招呼,你给这个线程打一个招呼之后呢,线程会把

自己这么一个中断的一个标志位呢,知道有人来寻求过我,需要我做一些响应,那么这个时候他就知道,有人给我打过招呼了,

如果他知道有人给他打招呼之后呢,他就能够给自己做出一些额外的操作,比如说,刚才提到的stop案例,说直接把这个

线程stop掉,这个太暴力,我们多线程的一致性也很难得到保证,所以我们可不可以使用中断的方式去,假设有一个线程是

这样做的,run方法,while true,无限循环,我这里调用的是yield,这里可能会做一些很复杂的操作,很多的后台线程,都喜欢

写死,像这样的线程我们可能在某些,需要把它停止掉,如果你一个线程如果不是一个死循环写在里面,一般我们也不会有需求

去stop他,因为它一调起来就停掉了,那你也不需要去stop掉,通常我们特别需要stop的线程呢,可能都是一种形式存在的,

它内部有一个很大的循环体,我们在这个循环当中呢,这里简单的写了一个Thread.yield,他可能做这些操作,读取数据库的值,

同时也要保证读写的一致性,那么这个时候你怎么去stop他呢,假如这个线程是t1,我们可以interrupt一下,中断之后呢,如果只是

这么一段操作,你这样写代码的话呢,这个中断是不会有任何响应的,但是你并没有告诉这个线程说,有别人给你打了招呼之后,

你怎么办,你没有告诉他该怎么办呢,他就会不理他,你这个interrupt不会对我产生影响,你能够非常优雅的终止这个线程,

我们就可以在这个外循环的顶部呢,加上这么一段代码,我每次while循环进来,进来之后我可以判断,我当前线程是否被中断了,

是不是有人来给我打招呼了,我的中断标志位是否被设上了,如果是的,我就直接break掉了,这个break的意思是说,break这个

外循环,而对于这个线程来讲,如果你这个代码是这样写的,你不是外循环等同于这个线程,因为你跳出整个run以后呢,整个run

就是这个while循环,这个run就必然终止掉了,这个就会让线程停止工作,但是这种方式是非常优雅的方式,他不会说,复杂的

数据操作,去破坏我影响这些操作,因为他总是会等这个操作完成之后,并且在下一轮操作之前,让这个线程停止,知道你这个数据

安全得到保证,另外一个比较通用的方法,就是sleep方法

如果我们希望这个线程不要走太快,希望他慢慢走,这个时候我们会调用sleep方法,sleep方法有一个特点,

事实上除了sleep方法以外呢,需要让线程去等待的方法,都有这么一个特点,他们会抛出一个interruption异常,

原因就在于说,如果你这个线程一直处于等待的状态,或者你等更长的时间,希望在我等待过程当中,也能够stop掉,

我打招呼去响应,比如你这个线程在这里等,这个循环体当中你做完某个事情,5秒钟,但是你的需求可能是说,我在等5

秒钟的时候,因为我在等的过程当中,对业务上可能是没有任何意义的,有人要我结束了,有人要我产生某些动作了,

那我等的时候呢,应该要对打招呼的行为,要有响应的,所以这个sleep方法,它会抛出一个Interrupted异常,允许我们在

使用sleep方法的时候呢,会觉得很烦,我只是想让他sleep一下,你为什么要让我加一段try catch,我们先不说这个try

catch是否看起来优雅,实际上很让人烦,那try catch有什么必要呢,是因为说,只有try catch之后,只有我响应Interrupted

之后呢,我在sleep的过程当中,跟我打了招呼,中断了我,我就立即去响应,如果我在sleep当中,有人中断了我,我就跳到catch

当中去,这个时候我就能够做出某些动作,如果使用下面这种做法的话呢,我们在sleep方法,结束之后呢,他要做一件非常重要

的事情,还要对自己调用一次interrupted,原因是说,某一个线程他抛出中断异常之后呢,他的中断标志位呢,他就会被清空,

当你在这个里面再去检查是否被中断之后呢,你是检测不到的,换句话如果你说这句话,直接去这个循环体做这件事情,是不是

被中断呢,这个线程没有被中断,再去设置我的中断标志,使得上一个能够检测到

suspend是把线程挂起,暂停一段时间,就是线程运行过程当中,我希望他先停一下,如果你把它停了之后,

你希望他可以继续往下走,那你就可以调用resume方法,他就会继续往下走,看起来这是两个比较方便的操作,

实际上这两个操作是不推荐使用的,

/**
 * Suspends this thread.
 * <p>
 * First, the <code>checkAccess</code> method of this thread is called
 * with no arguments. This may result in throwing a
 * <code>SecurityException </code>(in the current thread).
 * <p>
 * If the thread is alive, it is suspended and makes no further
 * progress unless and until it is resumed.
 *
 * @exception  SecurityException  if the current thread cannot modify
 *               this thread.
 * @see #checkAccess
 * @deprecated   This method has been deprecated, as it is
 *   inherently deadlock-prone.  If the target thread holds a lock on the
 *   monitor protecting a critical system resource when it is suspended, no
 *   thread can access this resource until the target thread is resumed. If
 *   the thread that would resume the target thread attempts to lock this
 *   monitor prior to calling <code>resume</code>, deadlock results.  Such
 *   deadlocks typically manifest themselves as "frozen" processes.
 *   For more information, see
 *   <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
 *   are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
 */
@Deprecated
public final void suspend() {
	checkAccess();
	suspend0();
}

这是suspend方法,它是被标记为不推荐使用,resume方法也是被标记为不推荐使用,suspend之所以不被推荐使用的原因呢,

如果目标线程获取一个锁之后呢,如果临界区资源被suspend了,临界区资源并不会被释放掉,因此没有没有任何线程可以访问

被他锁住的临界区,直到目标对象调用了resume方法,resume方法先于suspend被调用呢,使得线程没有办法被继续执行下去,

会出现线程类似被冻结的一种状态,这个是什么意思呢,现在假设有一个线程1,线程1加了一把锁,获得u这把锁,意思就是说,

如果我不释放这把锁,那就没有人可以拿到这把锁,为这把锁而等待,接着这个线程就有可能会被挂起,因为我们是多线程

操作,对于这个线程挂起,对于线程resume继续执行,这两个操作,很有可能发生在系统不同的地方,那比如我们希望在线程2

里面去,resume这个线程,希望这个线程能够继续执行下去,但实际上呢,你没有办法一定保证说,resume一定发生在suspend,

之后,因为在不同的线程当中,有可能发生在suspend之前,如果发生在suspend之前呢,那就相当于说你对于一个在执行的线程,

调用resume方法,这个时候对这个线程是没有影响的,然后你把它挂起suspend了,这个之后呢,会发现呢,因为已经调用了resume

方法,所以没有人再回来resume这个线程,线程1来执行,所以就会出现线程1呢,因为线程1永远被冻结,线程1所占用的锁,u这个

锁也不会被释放,线程3等待u这个锁,导致线程3也没有办法,继续执行下去,因此会有比较尴尬的状态,下面我们来看一下这个是什么

事情,现在我有两个线程,t1跟t2,线程它是可以有个名字的,每个线程实际上都有个名字的,所以我们在写代码的时候呢,推荐

都给我们线程起一个好听的名字,好看的名字,如果将来系统发生什么问题的话呢,我们通过这个名字呢,他这里不知道为什么

使用一个char,

private volatile String name;

线程都有个名字,我们可以看到Thread1,Thread2这些东西,这些是不利于我们排查问题的,如果线程都有个名字,比如它是干什么用的,

如果系统dump的时候,一眼就知道这个东西是干什么的,所以起个好的名字呢,我认为还是非常重要的,这里我们就给他起名为

t1,t2,然后我们在ChangeObjectThread这里加锁,获得u这个监视器,对象实例的监视器,同时在这里对自己进行挂起操作,很显然如果

他在这里被挂起来呢,就会导致说这个u呢,现在在主函数里面这样做,首先将t1进行执行,因为t1和t2只是线程不同,他所做的事情

是一样的,然后休眠一段时间,休眠的目的呢,把t1给挂起,接着我resume t1,resume t2,然后主线程等待t1和t2的执行,根据我们

对现在这段代码的直观理解呢,主函数是一定能够结束的,为什么呢,因为t1 start了,t2 start了,start之后呢,虽然都把自己给

suspend挂起了,但是我最后都有去resume他们,而且我resume操作,都是在start操作之后,就是我start完了,才去resume,

看起来没有什么问题,所以最终我这个程序呢,应该能够尽快结束,那我们来运行一下看一下,这个时候我们会发现,就是

我们in t1,in t2,主函数没有结束,在等待t1和t2的结束,这个专业方法我们会进行介绍,这个时候我们可以把这个线程dump出来看一下
package com.learn.thread;

public class BadSuspend {

    public static Object u = new Object();
    static ChangeObjectThread t1 = new ChangeObjectThread("t1");
    static ChangeObjectThread t2 = new ChangeObjectThread("t2");
    public static class ChangeObjectThread extends Thread{
        public ChangeObjectThread(String name) {
            super.setName(name);
        }

        @Override
        public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                Thread.currentThread().suspend();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        Thread.sleep(100);
        t2.start();
        t1.resume();
        t2.resume();
        t1.join();
        t2.join();
    }
}
首先我们看到所有的JAVA进程

jps

当前的进程是3908,然后去找所有的线程

jstack

这个时候我们会看到t2还在这里,这个时候你看不到t1,JAVA系统级的线程,表示t1已经结束了,t2还在执行,

t1之所以结束呢,t1事先调用suspend方法,因为我这里有sleep,resume获取到t1结束,t2还没来得及调用

suspend方法,我这里已经调用了resume,导致t2没有办法结束,那t2在干什么呢,t2在等待,但是一个非常非常

意外的现象呢,t2的状态还是Runnable,你把一个线程挂起来,他的状态还是Runnable

可见这里并不是一个合理的一个状态,但是由此可见说,JDK就认为这个东西以后尽量不要用了,我把它保留在这里

面也只是说,对向前版本的一个兼容而已,所以从这里也可以看到,如果我们想要使用suspend,那你必须,自己心里非常

清楚地说,将来必须把这个线程resume起来,否则像这样写呢,你并不能保证线程是可以正常被执行到的,而一旦进入

这种状况呢,其实t2就永远的被冻到那边了,永远没有办法再往下走了,这就是suspend和resume的一个问题,下面我们

来看一下join和yield

我们先说这个比较简单的yield,所谓这个yield呢,它是指说,首先这个yield它是一个静态方法,所以你可以直接使用

Thread.yield来调用它,

/**
 * A hint to the scheduler that the current thread is willing to yield
 * its current use of a processor. The scheduler is free to ignore this
 * hint.
 *
 * <p> Yield is a heuristic attempt to improve relative progression
 * between threads that would otherwise over-utilise a CPU. Its use
 * should be combined with detailed profiling and benchmarking to
 * ensure that it actually has the desired effect.
 *
 * <p> It is rarely appropriate to use this method. It may be useful
 * for debugging or testing purposes, where it may help to reproduce
 * bugs due to race conditions. It may also be useful when designing
 * concurrency control constructs such as the ones in the
 * {@link java.util.concurrent.locks} package.
 */
public static native void yield();

他的意思是说我当前线程,可能优先级比较高,我希望给予其他线程,有机会来争夺这个CPU,所以我会把自己当前调用

CPU的时间呢,给释放掉,我把自己占用的CPU资源释放掉之后呢,我可以使得其他的线程,更多的机会正常的去走,而不是

被我占用,但是注意我把CPU释放掉,并不意味着说,当前线程我释放了我执行的机会,我把我的东西放开来,我扔出来之后呢,

我不是说我白送给你的,我扔出来之后,我再来和你竞争,也就是说下一次我还是有可能,拿到这个时间片,只是说我给你

机会来跟我抢而已,这个看起来是一个非常罕见的方法,处于debug的目的,或者测试的目的,会使用这个方法,join是什么意思呢,

有时候我们启用多线程的时候呢,有一个问题,我不知道我开辟的线程什么时候执行完毕了,因为这里都是异步操作,我只是

开启线程的请求,把我这个Runnable这个实例传给你,但是你什么时候结束你并没有告诉我,但往往有时候呢,非常迫切的希望

我可能要等待你的某些数据,你的某些信息,才能进行下一步的动作,所以我会希望呢,等到你结束我再来做事情,join的英文含义

就是加入,加入到一起去做,为什么这里join会有等待的一个含义呢,有两个线程,本来是在两条路上走的,现在要join在一起,

合伙一起走了,那我肯定要停下来等等你,等你之后再合伙一起走,所以join的意思是说,我希望你at这个线程,我主线程会等你

在这个线程结束,结束之后一起走,这里的at线程做了一件什么事情呢,对静态的变量i,做++操作,我要等你做完,在你这个地方

做完之后呢,我把i打印出来,这个地方,打印出来的i呢,其实是这个值,因为主线程运行到这句话的时候,实际上at线程已经执行结束了,

已经join完毕,join是怎么实现的呢,内部主要调用两个方法,wait方法也会给大家介绍,wait方法是什么意思呢,它是说,我让

当前线程,等待在我调用wait操作的那个地方,我们来看看join方法

/**
 * Waits for this thread to die.
 *
 * <p> An invocation of this method behaves in exactly the same
 * way as the invocation
 *
 * <blockquote>
 * {@linkplain #join(long) join}{@code (0)}
 * </blockquote>
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public final void join() throws InterruptedException {
	join(0);
}

这个方法也可以传入一个时间,我们就看传递为0的,传递时间进去就是有一个时间的等待,我就等个10秒钟,或者是多少毫秒,

如果说我过了这段时间,你还没有执行完毕呢,那我就没有必要等你了,我就直接走了,我们来看一下join的实现

/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. As a thread terminates the
 * {@code this.notifyAll} method is invoked. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.
 *
 * @param  millis
 *         the time to wait in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
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");
	}

	if (millis == 0) {
		while (isAlive()) {
			wait(0);
		}
	} else {
		while (isAlive()) {
			long delay = millis - now;
			if (delay <= 0) {
				break;
			}
			wait(delay);
			now = System.currentTimeMillis() - base;
		}
	}
}

先判断这个线程是否已经结束isAlive,已经结束,所以我就直接出去了,如果我这个线程还没有结束呢,

那我就在这个线程做一个等待wait(0),那这个时候大家要注意了,如果我在这个线程上做了一个等待,

我必然希望有人能够通知我,什么时候等待结束了,如果说这个线程结束了,他就会调用notifyAll方法呢,

去通知这个上面等待的线程,后面我们会给大家来做介绍,但是在这里想跟大家说的是呢,这个notifyAll方法呢,

没有地方去调他的,这个方法调实际上,实际上是在JAVA虚拟机实现层,是使用C++代码去调用notifyAll的,

大家所要知道的呢,这个线程如果结束了,不工作了,他就会自动调用notifyAll,等待在当前线程,这句话有点拗口,

唤醒等待在当前线程实例上的所有线程,所以在大家写代码的时候有小小的意见,他会说,他说你的应用程序,

不要使用wait,notifyAll,在线程的实例上,后面我们会看到,因为这几个方法,wait,notify,notifyAll,是在Object

对象上,因此呢你任意的对象实例呢,都能够有这些方法,所以他给你的建议呢,在线程的对象实例上,不要调用这些方法,

原因就是说,这里的方法呢,是会被系统调用的,因此他没有办法保证说,你在线程对象上调用这些方法,能够如你所愿的,

这么去工作,这一点大家要注意,下我们来看一下守护线程

守护线程是指在后台默默运行的一些服务,也就是说他能帮助系统,做一些后台运维相关的东西,他跟业务

不是非常大,如果main函数结束了,或者main开启的一些非守护线程结束了,那么这个时候我们可以认为说,

这个JAVA程序呢,他已经没有存在的必要了,因为这个程序存在的目的呢,非守护线程,非守护线程会执行我们的

业务逻辑,一些系统的行为,守护线程他往往只起到一个辅助作用,如果我们所有的线程都不工作了,业务逻辑都

不运行了,这个时候我们主函数退出,那虚拟机当然就退出了,守护线程就是这么一种线程,他只在后台默默的为

整个系统提供支撑服务,大体上就是这个意思,如果说我系统中所有的非守护线程,全部都结束了,那么虚拟机就会

退出,虚拟机不会有你守护线程的存在而继续执行,我们来看一看
package com.learn.thread;

public class DaemonDemo {
	public static class DaemonT extends Thread {

		@Override
		public void run() {
			while(true){
				System.out.println("I am alive");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
	}}
	public static void main(String[] args) throws InterruptedException{
		Thread t1 = new DaemonT();
		t1.setDaemon(true);
		t1.start();
		
		Thread.sleep(2000);
	}
}
就会把这个线程设置为守护线程,如果一个线程被设置为守护线程了,那虚拟机就不会管你是否还存在,

如果他不是守护线程的时候呢,我们可以看到这个程序还是可以运行,还是可以不停的运行,这里是一个循环,

它是可以不停的运行的,如果我设置为守护线程,那我们看一下这个程序的行为,马上退出了,因为守护线程不作为

虚拟机是否退出的一个标志,另外一点需要注意的是,守护线程不是你想什么时候设置他就可以设他的,你要在

start方法之前,开启这个线程之前要告诉虚拟机,这是一个守护线程,如果我们把它写在后面,
	
t1.start();
t1.setDaemon(true);

这个守护线程的设置就失败的,它会告诉你说,你是一个非法的状态设置,因为我这个线程开启之后,状态定格下来,

你不能再去改变他了,下面我们来看一下线程优先级

线程是有优先级的,优先级高的更有可能抢到资源,更快的执行,那我们可以来看一下这个例子,

这个地方我们使用了两个线程,他们两个做同样的事情,这个count是两个不同的值,一个是LowPriority

线程的,但是他们在++之前呢,可以请求同一把锁,PriorityDemo.class,只要他们一起执行,就会有非常激烈的

竞争,程序完成之后呢,运行结束的一个语句,我们根据这个语句的先后顺序呢,就可以判断出,谁先执行完成,

谁后执行完成,设置线程优先级的方法呢,线程上调用setPriority这个函数,这里对high做了最高级的设置,

对low进行最低优先级的设置

high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);

然后同时开启

low.start();
high.start();

这里我把低优先级的先开,然后我们执行,高优先级的先完成
package com.learn.thread;

public class PriorityDemo {
	
	public static class HightPriority extends Thread {
		static int count = 0;
		@Override
		public void run() {
			while(true) {
				synchronized (PriorityDemo.class) {
					count++;
					if(count > 10000000) {
						System.out.println("HightPriority is completed");
						break;
					}
				}
			}
		}
	}
	
	public static class LowPriority extends Thread {
		static int count = 0;
		@Override
		public void run() {
			while(true) {
				synchronized (PriorityDemo.class) {
					count++;
					if(count > 10000000) {
						System.out.println("LowPriority is completed");
						break;
					}
				}
			}
		}
	}
 
	public static void main(String[] args) {
		HightPriority high = new HightPriority();
		LowPriority low = new LowPriority();
		high.setPriority(Thread.MAX_PRIORITY);
		low.setPriority(Thread.MIN_PRIORITY);
		low.start();
		high.start();
	}
 
}
当然这个优先级并不能保证,保证你在任何情况一定先执行,只是说概率上的一个问题,高优先级只能说有一个

更高的一个概率,那我们把高优先级和低优先级换一下,现在我们让low变成高优先级的,反过来了,这就是线程优先级,

现在我们来看一下线程之间的同步

也就是在多线程执行当中,多线程之间如何去通信,第一个如果我的线程被挂起了,我这里面被等待了,

别人怎么唤起我通知我继续执行了,如果我和你有一个数据竞争,彼此之间来协调这个竞争呢,这里JAVA提供了

最基本的方法,synchronized关键字,另外就是object的wait和notify,synchronized关键字呢,我要拿到对象的

一把锁,监视器,这个关键字呢,是JAVA内置的,这个关键字他的所有实现呢,是在虚拟机内部去做的,包括你要去

拿一些锁,然后我把这个线程挂起,或者在执行过程当中,可能会做一些优化,它是在虚拟机内部去实现的,那么

synchronized关键字总的来说,第一种是指定加锁的对象,synchronized关键字后面,跟上object的一个实例,

然后我去拿这个实例的monitor,进入synchronized同步块的代码,必须要拿到对象的这个锁,我用synchronized

关键字呢,用来修饰一个实例方法,相当于对当前实例加锁,也就是进入我这个方法之前,你必须要拿到我当前对象

实例的这把锁,另外就是把这个synchronized关键字呢,用一个静态方法,static,static synchronized,这样就相当于

对当前这个类进行加锁,进入我这个静态的synchronized方法之前呢,也要获得我这个class这个锁,我们简单的来看一下
package com.learn.thread;

public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync();
    static int i=0;
    @Override
    public void run() {
        //省略其他耗时操作....
        //使用同步代码块对变量i进行同步操作,锁对象为instance
        synchronized(instance){
            for(int j=0;j<1000000;j++){
                    i++;
              }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}

这里有一个instance对象进行加锁,用静态变量i进行++操作,然后我是有一个runnable实例,然后每一个线程

都给他加这么多次,因此加完之后呢,如果不是线程安全的呢,这个值一定是小于2千万的,而且小了很多,少了一个

就说明数据发生了冲突,基本上冲突有一半的可能性,如果说有了这个就能够保证,我这个数据是没有问题的,因为每一次

只有一个线程可以做i++操作,那这里有两个线程,这里同步对象就是instance,需要拿到instance监听器,这里我们也

调用join方法,调用join方法的目的呢,我要等待t1和t2都结束完了,我再把这个i打印出来,否则这个i是一定不会到

两千万的,因为打印i的时候呢,t1,t2根本就没有执行完,可以看到,第二种我们使用在静态的实例方法上面increase
package com.learn.thread;

public class AccountingSync2 implements Runnable{
    static int i=0;

    /**
     * 作用于静态方法,锁是当前class对象,也就是
     * AccountingSyncClass类对应的class对象
     */
    public static synchronized void increase(){
        i++;
    }

    /**
     * 非静态,访问时锁不一样不会发生互斥
     */
    public synchronized void increase4Obj(){
        i++;
    }

    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1=new Thread(new AccountingSync2());
        //new心事了
        Thread t2=new Thread(new AccountingSync2());
        //启动线程
        t1.start();t2.start();

        t1.join();t2.join();
        System.out.println(i);
    }
}
increase是实例方法,所以在这种情况之下呢,这个i++呢,它会把锁放到当前对象,当前对象是这个对象,

这里你如果调用这个方法之后呢,你要拿当前对象这个对象的实例,这里一定是两千万,这里有一个很容易

让初学者犯错误的地方呢,是我这个锁加错了地方,比如我们是加在当前对象实例上的
package com.learn.thread;

public class AccountingSyncBad implements Runnable{
    static int i=0;
    public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1=new Thread(new AccountingSyncBad());
        //new新实例
        Thread t2=new Thread(new AccountingSyncBad());
        t1.start();
        t2.start();
        //join含义:当前线程A等待thread线程终止之后才能从thread.join()返回
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
小于两千万,一定是有发生冲突不对的,这个原因在哪里呢,是因为我在这个地方调用了方法,

这个锁是在这个对象的实例上,但是我new Thread的时候呢,我构造了两个不同的对象实例,也就是当你再做

这个操作的时候呢,你一个线程是对这个对象做加锁,另外一个线程是对这个对象做加锁,两个对象肯定不是同一个,

都是我new出来的,所以根本就没有在一个锁上工作,使他们的数据产生了问题,如果说我把这个东西拿出来,我们假设

是t好了,这样t1和t2才是在同一个对象上
package com.learn.thread;

public class AccountingSyncBad implements Runnable{
    static int i=0;
    public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
    	AccountingSyncBad t = new AccountingSyncBad();
        //new新实例
        Thread t1=new Thread(t);
        //new新实例
        Thread t2=new Thread(t);
        t1.start();
        t2.start();
        //join含义:当前线程A等待thread线程终止之后才能从thread.join()返回
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
最后我们来看一下static synchronized,我在这个类上加锁,每一个对象都有独立的Runnable对象实例,他所对应的

Runnable是不一样的,根据刚才这里的说法呢,如果你加锁加载当前的对象上呢,你肯定是不行的,因为我们把锁加在

类上,不是对象实例上,这个类的实例上面
package com.learn.thread;

public class AccountingSyncClass implements Runnable{
    static int i=0;

    /**
     * 作用于静态方法,锁是当前class对象,也就是
     * AccountingSyncClass类对应的class对象
     */
    public static synchronized void increase(){
        i++;
    }

    /**
     * 非静态,访问时锁不一样不会发生互斥
     */
    public synchronized void increase4Obj(){
        i++;
    }

    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1=new Thread(new AccountingSyncClass());
        //new心事了
        Thread t2=new Thread(new AccountingSyncClass());
        //启动线程
        t1.start();t2.start();

        t1.join();t2.join();
        System.out.println(i);
    }
}
这里就是synchronized的一些使用,我们来看一下wait和notify方法,一个方法是指线程等待在当前对象上,

另外一个是通知等待这个对象上的线程,从wait这个函数当中呢,如果一个线程调用了object wait之后呢,

他就会进入一个wait的状态,不会再往下走了,但是wait这个操作呢,他有一个需要注意的地方是说,他需要获得

当前Object对象的监视器,他才能去调用这个方法

package com.learn.thread;

public class SimpleWN {

    final static Object obj=new Object();
    public static class T1 extends Thread{
        @Override
        public void run(){
            synchronized (obj){
                System.out.println(System.currentTimeMillis()+" :T1 start!");
                try {
                    System.out.println(System.currentTimeMillis()+" :wait for object!");
                    obj.wait();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis()+" :T1 END!");
            }
        }
    }

    public static class T2 extends Thread{
        @Override
        public void run(){
            synchronized (obj){
                System.out.println(System.currentTimeMillis()+" :T2 start! notify one!");

                System.out.println(System.currentTimeMillis()+" :wait for object!");
                obj.notify();

                System.out.println(System.currentTimeMillis()+" :T2 END!");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args){
        Thread t1=new T1();
        Thread t2=new T2();
        t1.start();
        t2.start();
    }
}
在这个对象上做一个等待,在等待之前,他要拿到这个对象的锁,他就是要在synchronized的方法当中,

wait要写在synchronized的方法里面才行,wait方法会使得当前线程释放这个锁,释放这个object监视器,

/**
 * Causes the current thread to wait until another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object.
 * In other words, this method behaves exactly as if it simply
 * performs the call {@code wait(0)}.
 * <p>
 * The current thread must own this object's monitor. The thread
 * releases ownership of this monitor and waits until another thread
 * notifies threads waiting on this object's monitor to wake up
 * either through a call to the {@code notify} method or the
 * {@code notifyAll} method. The thread then waits until it can
 * re-obtain ownership of the monitor and resumes execution.
 * <p>
 * As in the one argument version, interrupts and spurious wakeups are
 * possible, and this method should always be used in a loop:
 * <pre>
 *     synchronized (obj) {
 *         while (&lt;condition does not hold&gt;)
 *             obj.wait();
 *         ... // Perform action appropriate to condition
 *     }
 * </pre>
 * This method should only be called by a thread that is the owner
 * of this object's monitor. See the {@code notify} method for a
 * description of the ways in which a thread can become the owner of
 * a monitor.
 *
 * @throws  IllegalMonitorStateException  if the current thread is not
 *               the owner of the object's monitor.
 * @throws  InterruptedException if any thread interrupted the
 *             current thread before or while the current thread
 *             was waiting for a notification.  The <i>interrupted
 *             status</i> of the current thread is cleared when
 *             this exception is thrown.
 * @see        java.lang.Object#notify()
 * @see        java.lang.Object#notifyAll()
 */
public final void wait() throws InterruptedException {
	wait(0);
}

会使得当前线程等待,当前对象必须拥有这个monitor,必须拥有这个监视器,线程会释放这个监视器的所有权,

并且等待其他线程通知这个线程,继续执行,这里为什么要释放呢,如果不释放的话,他又在等待,就会使得其他线程,

没有办法获得这个对象的所有权,那一个等待在这个object上的线程呢,它会受到notify的通知,跟wait很像的就是,

你这个notify这个执行之前呢,你也要获得这个对象的监视器,

/**
 * Wakes up a single thread that is waiting on this object's
 * monitor. If any threads are waiting on this object, one of them
 * is chosen to be awakened. The choice is arbitrary and occurs at
 * the discretion of the implementation. A thread waits on an object's
 * monitor by calling one of the {@code wait} methods.
 * <p>
 * The awakened thread will not be able to proceed until the current
 * thread relinquishes the lock on this object. The awakened thread will
 * compete in the usual manner with any other threads that might be
 * actively competing to synchronize on this object; for example, the
 * awakened thread enjoys no reliable privilege or disadvantage in being
 * the next thread to lock this object.
 * <p>
 * This method should only be called by a thread that is the owner
 * of this object's monitor. A thread becomes the owner of the
 * object's monitor in one of three ways:
 * <ul>
 * <li>By executing a synchronized instance method of that object.
 * <li>By executing the body of a {@code synchronized} statement
 *     that synchronizes on the object.
 * <li>For objects of type {@code Class,} by executing a
 *     synchronized static method of that class.
 * </ul>
 * <p>
 * Only one thread at a time can own an object's monitor.
 *
 * @throws  IllegalMonitorStateException  if the current thread is not
 *               the owner of this object's monitor.
 * @see        java.lang.Object#notifyAll()
 * @see        java.lang.Object#wait()
 */
public final native void notify();

这个方法应该被哪些线程调呢,拥有这个对象监视器的线程,去调用它,就是你这个状态是不对的,

你还没有拿到这个监视器,你怎么能够去做notify呢,notify会使得这个wait线程往下走,但是wait线程要往下走呢,

他不是随随便便往下走的,因为我们知道在这句话当中,是在synchronized大括号里面的,也就是说,你这个wait,

整个函数,是必须要拿到监视器之后,你才能继续往下走的,虽然线程是允许你往下走了,但这个时候呢,你还没有拿到

我这个object的监视器,所以notify后你所处的状态是什么呢,我要争取去拿object的monitor才行了,我在notify之后,

你这里应该是看不到T1的输出,因为t1他准备去拿这个monitor,而你这个T2呢,还不释放这个monitor,你还要在这里

睡个两秒钟,所以T1是很郁闷的,应该是还会继续等待,知道什么时候呢,知道你这个T2释放了这个monitor为止,把这个sleep

完了之后呢,我这个T1才能把T1 end打印出来,所以我们执行代码,应该看到的是,是我T2 END打印完了之后,过了两秒钟以后,

T1 END才会被打印出来,我们来看看是不是这样,T2 END打印完了之后,两秒以后,T1才会打印,所以这一点要注意,并不是说,

我通知了你这个wait之后,wait就能继续往下走,走不下去的,还要把object monitor拿回来才能走,与notify对应的还有notifyAll
package com.learn.thread;

public class SimpleWNA {

    final static Object obj=new Object();
    public static class T1 extends Thread{
        @Override
        public void run(){
            synchronized (obj){
                System.out.println(System.currentTimeMillis()+" :T1 start!");
                try {
                    System.out.println(System.currentTimeMillis()+" :wait for object!");
                    obj.wait();
                    Thread.sleep(1000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis()+" :T1 END!");
            }
        }
    }

    public static class T2 extends Thread{
        @Override
        public void run(){
            synchronized (obj){
                System.out.println(System.currentTimeMillis()+" :T2 start! notify all threads!");
                obj.notifyAll();
                System.out.println(System.currentTimeMillis()+" :T2 END!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Thread t1=new T1();
        Thread t1_1=new T1();
        t1_1.start();
        t1.start();
        Thread.sleep(2000);
        Thread t2=new T2();
        t2.start();
    }
}
notify和notifyAll的区别呢,notify就是通知等待在当前对象上的一个线程,object.wait,可能好多线程都在

等待,那我notify,我就随机唤醒其中的一个,其他的我不管,那notifyAll呢,它会唤醒所有在等待线程上的线程,

但是条件和刚才的notify方法是一样的,不是说我唤醒你之后,你就能往下做事情了,根据刚才的说法呢,你就是在

synchronized大括号里面的,所以我唤醒所有线程的结果呢,我有等待在这个线程一起来竟用这个object,谁抢到了,

谁先执行,抢不到的,等抢到了再执行,我notify以后再谁两秒钟,因此呢虽然我notifyAll了,我这个T1还是会等两秒之后,

在从这个位置返回,返回之后才能拿到monitor,返回之后我T1还要再睡一秒钟,再过一秒钟之后呢,我才结束,因此如果

我又两个T1线程的话呢,必然会出现一个T1执行完成之后,另外一个T1要再过一秒钟,才能够执行完

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值