public class Thread
extends Object
implements Runnable
线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。
当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:
- 调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。
- 非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。
创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。这种方法不再推荐。
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动
- 在一个单独线程中执行一个任务的步骤如下:
-
- 将任务代码移到实现了Runnable接口类的run()方法中。
- 由Runnable对象创建一个Thread对象。Thread thread=new Thread(new Runnable());
- 启动线程。thread.start().
Runnable接口有唯一的方法run(),用于定义将要执行的任务。
Thread类实现了Runnable接口,有run(),start()方法,具体如下:
- Thread(Runnable target)
构造一个新的线程,用于调用给定目标的run()方法。
- public void start()
启动这个线程,将引发调用run()方法。这个方法立即返回,并且新线程将并发运行。
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
- public void run()
调用关联的Runnable的run()方法。
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回
- 即当调用Thread.start()方法时,会启动一个新的线程,并执行Runnable对象的run()方法。
- start()方法会立即返回,但是run()方法会继续执行。所以会出现多个线程同时在运行,当run方法运行结束时,线程终止。 ps:main()线程是主线程
- 不要调用Thread类或Runable对象的run()方法。直接调用run()方法,只会执行同一个线程中的任务,而不会启动新的线程。应该调用Thread.start方法。这个方法将创建一个执行run方法的新线程。
public void interrupt()
向线程发送中断请求。线程的中断状态被设置为true。如果目前线程被一个sleep调用阻塞,那么会抛出InterruptedException。
public
static
boolean interrupted()
测试当前线程是否已经中断。
线程的中断状态 由该方法清除,会将当前线程的中断状态重置为false
。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
public boolean isInterrupted()
测试线程是否已经中断。
线程的中断状态 不受该方法的影响
。
- 没有可以强制线程终止的方法,interrupt方法用来请求终止线程。当调用interrupt方法时,线程的中断状态被置位。没有要求一个被中断的线程应该终止。中断一个线程只不过是引起它的注意,被中断的线程可以决定如何响应。可以不理会中断,但是大部分线程是将中断作为一个终止的请求。实际上,运行中的线程被中断,目的是为了让其他线程获得运行的机会。
- 线程状态:
-
- New(新创建):使用new操作符创建新的线程
- Runnable(可运行):一旦调用start方法,线程便处于可运行状态。一个可运行的线程可能在运行,也可能没有运行。
- Bolcked(被阻塞),Waiting(等待):此时线程暂时不活动。
-
- 当线程试图获取一个内部的对象锁时,而该锁被其他线程所持有,则该线程进入阻塞状态。
- 当一个线程等待另个线程通知调度器一个条件时,它自己进入等待状态。
- 有几个方法有一个超时参数。调用它们导致进入计时等待。thread.sleep、thread.join、thread.wait,condition.await。
- Terminated(被终止):因为run方法正常退出而终止/因为一个没有捕获异常而终止。
public final void join()
等待该线程终止。
public final void join(long millis)
等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去
public static void
sleep
(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。
public static void
yield
()
暂停当前正在执行的线程对象,并执行其他线程。
- 线程同步
- 有两种方法可以防止代码受并发访问的干扰:1.使用synchronized关键字;2.jdk1.5引入了ReentrantLock类,实现了锁机制。
- ReentrantLock类保护代码的基本结构如下:
ReentrantLock myLock=new ReentrantLock():
myLock.lock()://获得锁
try{
临界区资源,存放着共同访问的资源
}
fianlly{
myLock.unlock();//释放锁
}
- 这一结构确保了任何时刻只有一个线程进入临界区。一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句。当其他线程调用lock时,它们被阻塞,直到第一个线程释放锁对象。注意,要把解锁操作放在fianlly子句中。如果两个线程试图访问同一个锁对象,那么锁以串行的方式提供服务;如果两个线程访问的是不同的锁对象,那么两个线程都不会阻塞。所以,如果想要使线程同步,必须是同一个锁对象。
- 锁是可重入的,因为线程可以重复获得已经持有的锁。锁保持一个持有计数来跟踪lock方法的嵌套调用。线程每次调用一次lock,都要调用一次unlock释放锁。被一个锁保护的代码可以调用另一个使用相同锁的方法。
public interface Lock
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
void lock()
获取锁。
如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态
void unlock()
释放锁
Condition newCondition()
返回绑定到此 Lock 实例的新 Condition 实例。
在等待条件前,锁必须由当前线程保持。调用 Condition.await() 将在等待前以原子方式释放锁,并在等待返回前重新获取锁。
public class ReentrantLock
extends Object
implements Lock
, Serializable
一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
- 条件对象
- 如果一个线程获得锁,进入临界区,但是该线程因为自身条件不足无法继续执行。可以使用条件对象使自己阻塞,并放弃锁,从而将机会让给别的线程。
- 一个锁对象可以有多个条件对象。可以使用newCondition()方法获得一个条件对象。
public interface Condition
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。
void await()
将该线程放到条件的等待集中。
void signalAll()
解除该条件的等待集中所有线程的阻塞状态。
void signal()
从该条件的等待集中随机地选择一个线程,解除其阻塞状态
。
- 当条件对象调用await()时,线程被阻塞,并放弃锁。
- 等待获得锁的线程与调用await()方法的线程本质上不同。一旦一个线程调用await()方法,它进入该条件的等待集中。当锁可用时,该线程不能马上解除阻塞,相反,它处于阻塞状态,直到另一个线程调用同一个条件上的siagnalAll方法时为止。当线程从等待集中移出时,它们再次成为可运行的。一旦锁成为可用的,它们中的某个将从await()调用返回,获得该锁,并从被阻塞的地方继续执行。
- 当一个线程调用await()方法时,它是没法自己激活自己,只能等待其他线程激活。如果没有线程来激活,就会出现死锁。
- 当一个线程拥有某个条件的锁时,它仅仅可以在该条件上调用await().signalAll()。
- synchronized关键字
- Java中的每个对象都有一个内部锁。如果一个方法使用synchonized关键字声明,那么对象的锁将保护整个方法。也就是说,调用该方法,线程必须获得内部的对象锁。调用synchonized方法时获取锁,方法返回时释放锁。
- 内部对象只有一个相关条件。wait方法添加一个线程到等待集中,notifyAll/notify方法解除等待线程的阻塞状态。wait(),notifyAll(),notify()方法是Object类的final方法,await(),signalAll(),signal()方法是Condition类的方法。两者等价。
- 每个对象有一个内部锁,该锁有一个内部条件。
- 将静态方法声明为synchonized也是可以的。
class Print implements Runnable{
@Override
public
synchronized
void run() {
// TODO Auto-generated method stub
for (int i = 0; i <30; i++) {
System.out.print(i+" ");
}
System.out.println();
}
}
public class SynchonizedTest {
public static void main(String[] args) {
Print print=new Print();
for (int i = 0; i < 5; i++) {
Thread thread=new Thread(print);
thread.start();
}
}
}
- volatile关键字为实例域的同步访问提供了一种免锁机制。可以使得多线程安全地读取一个域。
- 如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标识时如果发现中断标示为true(即线程在此时调用interrupt()方法),则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标别位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并在结束线程前让程序员有足够的时间来处理中断请求。