您的位置:首页 > 编程语言 > Java开发

Java之多线程(2)--基础开篇

2017-09-20 20:47 375 查看
一、创建线程

首先来看下源码:

/**
* A <i>thread</i> is a thread of execution in a program. The Java
* Virtual Machine allows an application to have multiple threads of
* execution running concurrently.
*
* 一个线程是一个程序的执行线程。java虚拟机的应用程序可以有多个同时执行的线程。
* <p>
* Every thread has a priority. Threads with higher priority are
* executed in preference to threads with lower priority. Each thread
* may or may not also be marked as a daemon. When code running in
* some thread creates a new <code>Thread</code> object, the new
* thread has its priority initially set equal to the priority of the
* creating thread, and is a daemon thread if and only if the
* creating thread is a daemon.
*
* 每个线程都有一个优先级。具有较高优先级的线程优先于较低优先级的线程执行。每个线程可能也可能不会被标记为
* 守护程序。当一些线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初等于创建这个线程的线程的
* 优先级。只有守护线程才能创建守护线程。
* <p>
* When a Java Virtual Machine starts up, there is usually a single
* non-daemon thread (which typically calls the method named
* <code>main</code> of some designated class). The Java Virtual
* Machine continues to execute threads until either of the following
* occurs:
* <ul>
* <li>The <code>exit</code> method of class <code>Runtime</code> has been
*     called and the security manager has permitted the exit operation
*     to take place.
* <li>All threads that are not daemon threads have died, either by
*     returning from the call to the <code>run</code> method or by
*     throwing an exception that propagates beyond the <code>run</code>
*     method.
* </ul>
*
* 当一个Java虚拟机启动时,就有一个单线程(一些特定的类一般称之为main方法)。Java虚拟机继续执行线程,
* 直到出现以下任一情况:
* 1.已经调用了类Runtime的exit方法,安全管理器已经允许进行退出操作。
* 2.不是守护线程的所有线程都已经死亡,无论是从调用返回到run方法还是通过抛出超出run方法传播的异常。
* <p>
*
* There are two ways to create a new thread of execution. One is to
* declare a class to be a subclass of <code>Thread</code>. This
* subclass should override the <code>run</code> method of class
* <code>Thread</code>. An instance of the subclass can then be
* allocated and started. For example, a thread that computes primes
* larger than a stated value could be written as follows:
*
* 创建一个新的执行线程有两种方法。一是将一个类声明为Thread的子类,这个子类需要重写Thread类的run方法。
* 然后可以分配并启动子类的实例。例如,计算初始值大于规定值的的线程可以写成如下:
*
* <hr><blockquote><pre>
*     class PrimeThread extends Thread {
*         long minPrime;
*         PrimeThread(long minPrime) {
*             this.minPrime = minPrime;
*         }
*
*         public void run() {
*             // compute primes larger than minPrime
*              . . .
*         }
*     }
* </pre></blockquote><hr>
* <p>
* The following code would then create a thread and start it running:
* <blockquote><pre>
*     PrimeThread p = new PrimeThread(143);
*     p.start();
* </pre></blockquote>
* <p>
*
* The other way to create a thread is to declare a class that
* implements the <code>Runnable</code> interface. That class then
* implements the <code>run</code> method. An instance of the class can
* then be allocated, passed as an argument when creating
* <code>Thread</code>, and started. The same example in this other
* style looks like the following:
*
* 另一种创建线程的方法就是去声明一个实现了Runnable接口的类,然后这个类实现了run方法。然后可以分配类的
* 实例,在创建Thread时作为参数传递,然后启动。
*
* <hr><blockquote><pre>
*     class PrimeRun implements Runnable {
*         long minPrime;
*         PrimeRun(long minPrime) {
*             this.minPrime = minPrime;
*         }
*
*         public void run() {
*             // compute primes larger than minPrime
*              . . .
*         }
*     }
* </pre></blockquote><hr>
* <p>
* The following code would then create a thread and start it running:
* <blockquote><pre>
*     PrimeRun p = new PrimeRun(143);
*     new Thread(p).start();
* </pre></blockquote>
* <p>
* Every thread has a name for identification purposes. More than
* one thread may have the same name. If a name is not specified when
* a thread is created, a new name is generated for it.
* <p>
*
* 每个线程都有一个用于识别目的的名称。多个线程可能具有相同的名称。如果一个线程创建时名字还没有规定,一个
* 新名字就会生成给它。
*
* Unless otherwise noted, passing a {@code null} argument to a constructor
* or method in this class will cause a {@link NullPointerException} to be
* thrown.
* 除非另有说明,否则将{@code null}参数传递给此类中的构造函数或方法将导致抛出{@link
* NullPointerException}


总结一下,创建线程应当有两种方法:

1.继承Thread类,重写该类的run()方法。

class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}

public void run() {
// compute primes larger than minPrime
 . . .
}
}

PrimeThread p = new PrimeThread(143);
p.start();


2.实现Runnable接口,并重写该接口的run()方法。

class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}

public void run() {
// compute primes larger than minPrime
 . . .
}
}

PrimeRun p = new PrimeRun(143);
new Thread(p).start();


二、线程的状态

public enum State {
/**
* Thread state for a thread which has not yet started.
* 线程尚未启动的线程状态。
*/
NEW,

/**
* Thread state for a runnable thread.  A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.

12507
* 可运行线程的线程状态。 可运行状态的线程正在Java虚拟机中执行,但它可能正在等待来自操作系统
* (例如处理器)的其他资源。
*/
RUNNABLE,

/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*
* 一个在堵塞状态的线程等待监视器将它锁入synchronized block方法或者在wait()之后重新进入
* synchronized block方法
*/
BLOCKED,

/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* 一个等待状态的线程可能是因为以下方法:
*
* <ul>
*   <li>{@link Object#wait() Object.wait} with no timeout</li>
*   <li>{@link #join() Thread.join} with no timeout</li>
*   <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* 1.Object.wait方法没超时
* 2.Thread.join方法没超时
* 3.LockSupport.park方法
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* 处于等待状态的线程正在等待另一个线程执行特定动作。
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*
* 比如说,在一个对象上调用了Object.wait()的线程就是在等待另一个线程在这个对象上去调用
* Object.notify()或者Object.notifyAll()。 一个调用了Thread.join()的线程就是在等待一个
* 特定的线程去终结它。
*/
WAITING,

/**
* Thread state for a waiting thread with a specified waiting time.
* 具有指定等待时间的等待线程的线程状态。
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
*   <li>{@link #sleep Thread.sleep}</li>
*   <li>{@link Object#wait(long) Object.wait} with timeout</li>
*   <li>{@link #join(long) Thread.join} with timeout</li>
*   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
*   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,

/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}


综上,线程有五种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。

三、线程的主要方法

1.join()

/**

* Waits for this thread to die.
* 等待这个线程死亡.
*
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
* 调用此方法的行为方式与调用完全相同.
*
*/
public final void join() throws InterruptedException {
join(0);
}

调用了有参数的join方法:

/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* 最多等待millis毫秒 让这个线程死亡。
* millis=0,永远等待这个线程的死亡。
*
* <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.
*/

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;
}
}
}


举例

没使用join之前:

public class LiftOff implements Runnable {
protected int countDown = 10; // Default
private static int taskCount = 0;
private final int id = taskCount++;

public LiftOff() {
}

public LiftOff(int countDown) {
this.countDown = countDown;
}

public String status() {
return "#" + id + "(" +
(countDown > 0 ? countDown : "Liftoff!") + "), ";
}

public void run() {
while (countDown-- > 0) {
System.out.print(status());
Thread.yield();
}
}
}

public class JoinTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"开始");
new Thread(new LiftOff()).start();
System.out.println(Thread.currentThread().getName()+"结束");
}
}

输出:
main开始
main结束
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!),


使用join()方法之后:

public class JoinTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "开始");
Thread joinTest = new Thread(new LiftOff());
joinTest.start();
try {
joinTest.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}

输出:
main开始
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!), main结束


使用join(1)之后

public class JoinTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "开始");
Thread joinTest = new Thread(new LiftOff());
joinTest.start();
try {
joinTest.join(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");

}
}

输出:
main开始
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), main结束
#0(2), #0(1), #0(Liftoff!),


总结:

在主线程main中:

①.joinTest线程调用Join(),主线程**等待**joinTest线程执行完毕后 继续执行。

②joinTest线程调用Join(1),主线程**等待**joinTest线程执行1毫秒之后执 继续执行。

2.wait()

需要指出的是,wait方法是java.lang包的方法。并且不难发现,join()方法就是由wait()方法现实的。

下面看看wait()方法的源码:

/**
* 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)}.
*
* 使当前线程等待直到另外的线程为这个对象执行了notify()或者notifyAll()方法。
* 换句话说,这个方法的行为就好像简单地执行调用{@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>
*
* 当前线程必须拥有该对象的监视器。线程释放此监视器的所有权,并等待直到另一个线程(t)通知那些 在等
* 待该对 象的监视器去唤醒自身线程们,或者(t)调用{@code notify}方法或{@code notifyAll}方法
* 去唤醒那些线程。ps:怎么都翻译不对。。
*
*
* 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 (<condition does not hold>)
*             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.
*  该方法只能由作为该对象监视器所有者的线程调用。
*/
public final void wait() throws InterruptedException {
wait(0);
}
public final native void wait(long timeout) throws InterruptedException;


很遗憾,看不到具体现实的代码。下面举个例子来看看效果吧。

public class LiftOff implements Runnable {
protected int countDown = 10; // Default
public LiftOff() {
}

public LiftOff(int countDown) {
this.countDown = countDown;
}

public String status() {
return Thread.currentThread().getName()+ "(" +
(countDown > 0 ? countDown : "Liftoff!") + "), " ;
}

public void run() {
synchronized (this) {
try {
while (countDown-- > 0) {
System.out.println(status());
}
System.out.println(Thread.currentThread().getName()+"执行完毕");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " call notify()");
// 唤醒当前的wait线程
this.notify();
}
}
}
public class WaitTest {
public static void main(String[] args) {
Thread t1 = new Thread(new LiftOff(5));
synchronized(t1) {
try {
System.out.println(Thread.currentThread().getName()+" start t1");
t1.start();
System.out.println(Thread.currentThread().getName()+" wait()");
t1.wait();
System.out.println(Thread.currentThread().getName()+" continue");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}


输出:

main start

main wait()

Thread-0(4),

Thread-0(3),

Thread-0(2),

Thread-0(1),

Thread-0(Liftoff!),

Thread-0执行完毕

Thread-0 call notify()

main continue

明明是t1.wait(),为什么反而是main 线程阻塞呢? 看代码不难发现,当前线程Thread.currentThread()是main,而不是t1,所以最终调用wait方法的是main。再来个例子证明一下观点:

public class WaitTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized(t1) {
try {
System.out.println(Thread.currentThread().getName()+" start t1");
t1.start();
System.out.println(Thread.currentThread().getName()+" wait()");
//                t1.wait();
System.out.println(Thread.currentThread().getName()+" continue");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}


输出:

main start

main wait()

main continue

Thread-0(4),

Thread-0(3),

Thread-0(2),

Thread-0(1),

Thread-0(Liftoff!),

Thread-0执行完毕

Thread-0 call notify()

所以最终可以得出结论,调用wait方法的是main线程。

3.yield():暂停当前正在执行的线程对象,并执行其他线程。

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态。

例子:

public class LiftOff implements Runnable {
protected int countDown = 10; // Default
private static int taskCount = 0;
private final int id = taskCount++;

public LiftOff() {
}

public LiftOff(int countDown) {
this.countDown = countDown;
}

public String status() {
return "#" + id + "(" +
(countDown > 0 ? countDown : "Liftoff!") + "), ";
}

public void run() {
while (countDown-- > 0) {
System.out.println(status());
if (countDown==10){
Thread.yield();
}
}
}
}

public class YieldTest {
public static void main(String[] args) {
new Thread(new LiftOff(15)).start();
new Thread(new LiftOff(15)).start();

}
}


输出:#0(14), #0(13), #0(12), #0(11), #0(10), #0(9), #1(14), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Liftoff!), #1(13), #1(12), #1(11), #1(10), #1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(Liftoff!)。

4.sleep()方法

sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;

   sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。

  在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

sleep()和yield()的区别

sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程

另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

wait()和sleep()异同

共同点:

1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。

2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。

如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。

需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。

不同点:

1. Thread类的方法:sleep(),yield()等

Object的方法:wait()和notify()等

2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。

sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

所以sleep()和wait()方法的最大区别是:

    sleep()睡眠时,保持对象锁,仍然占有该锁;

    而wait()睡眠时,释放对象锁。

  但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。

四、守护线程与优先级

4.1守护线程

首先我们可以通过t.setDaemon(true)的方法将线程转化为守护线程。而守护线程的唯一作用就是为其他线程提供服务。计时线程就是一个典型的例子,它定时地发送“计时器滴答”信号告诉其他线程去执行某项任务。当只剩下守护线程时,虚拟机就退出了,因为如果只剩下守护线程,程序就没有必要执行了。另外JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。

public class DaemonTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+RUN_START);
Thread deamon = new Thread(new DaemonRunner());
//设置为守护线程
deamon.setDaemon(true);
deamon.start();//启动线程
System.out.println(Thread.currentThread().getName()+RUN_OVER);
}

static class DaemonRunner implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
System.out.println("finlly代码块");
}
}
}

public static final String RUN_START="运行开始";
public static final String RUN_OVER="运行结束";
}


在java虚拟机退出时Daemon线程中的finally代码块并不一定会执行。

4.2 优先级

线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。可以用setPriority方法提高或降低任何一个线程优先级。可以将优先级设置在MIN_PRIORITY(1)与MAX_PRIORITY(10)之间的任何值。线程的默认优先级为NORM_PRIORITY(5)。虽然调度器倾向于让优先权最高的线程执行,但这并不意味着优先权低的线程得不到执行(也就是说,优先权不会导致死锁)。优先级较低的线程仅仅是执行的频率较低。

五、常见线程名词解释

主线程:JVM调用程序main()所产生的线程。

当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。

后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束

前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

线程类的一些常用方法:

  sleep(): 强迫一个线程睡眠N毫秒。

  isAlive(): 判断一个线程是否存活。

  join(): 等待线程终止。

  activeCount(): 程序中活跃的线程数。

  enumerate(): 枚举程序中的线程。

currentThread(): 得到当前线程。

  isDaemon(): 一个线程是否为守护线程。

  setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)

  setName(): 为线程设置一个名称。

  wait(): 强迫一个线程等待。

  notify(): 通知一个线程继续运行。

  setPriority(): 设置一个线程的优先级。

参考文章:

http://blog.csdn.net/evankaka

http://blog.csdn.net/javazejian/article/details/50878598
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程