Thead类与Runnable接口

Java的线程,即一个Thread实例。

Java的线程执行过程有两种实现方式:

  • 子类继承Thread类,并且重写void run()方法。
  • 自定义类实现Runnable接口,并且实现void run()方法。并在Thead构造时,将Runnable实例放入Thead。

Thread类

创建一个新线程必须实例化一个Thread对象。

使用方法:

  1. 子类继承Thread类。重写Thread的run()方法。
  2. 实例化该子类。
  3. 执行Thread的start()方法启动一个线程。

例:Thread使用方法

/**
* Thread类。
*/
class MyThread extends Thread {
public void run() {
//... 线程执行
}
}
public class TestThread1 {
public static void main(String args[]) {
MyThread thread = new MyThread (); //新建线程。
thread .start(); //线程开始执行。
//主线程其他方法
}
}

Runnable接口

实现Runnable接口需要实现run()方法.run()方法代表线程需要完成的任务.因此把run方法称为线程执行体.

使用方法:

  1. 类实现Runnable接口,并且实现run()方法。
  2. 实例化该类。Runnable runnable=new MyRun();
  3. 把该类注入到Thread对象中,即通过Thread的构造方法注入。Thread thread=new Thread(runnable);
  4. 调用Thread实例的start()方法。启动线程。thread.start();

例:Runnable使用方法

class MyRunner implements Runnable {    //实现Runnable接口
public void run() {
// ...... 线程执行
}
} public class TestThread1 {
public static void main(String args[]) {
MyRunner runner= new Runner1();
Thread t = new Thread(runner); //新建线程。
t.start(); //线程开始执行。
// ...... 主线程继续执行
} }

两种方式所创建线程的对比

实现Runnable接口方式的多线程:

  • 编程稍复杂。
  • 如果需要访问当前线程,必须使用Thread.currentThread()方法。
  • 线程只是实现了Runnable接口,还可以继承其他类。
  • 在这种方式下,可以多个线程共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU,代码,数据分开,形成清晰的模型,较好地体现了面向对象的思想。

继承Thread类方式的多线程:

  • 编程简单。
  • 如果需要访问当前线程直接使用this。
  • 已经继承了Thread类,无法再继承其他父类。

Thread的常用API

1  Thread类的构造方法

  • Thread()
  • Thread(Runnable target)
  • Thread(Runnable target, String name)
  • Thread(String name)
  • Thread(ThreadGroup group, Runnable target)
  • Thread(ThreadGroup group, Runnable target, String name)
  • Thread(ThreadGroup group, Runnable target, String name, long stackSize)
  • Thread(ThreadGroup group, String name)

2常用方法

(1) 启动线程

  • void run() :如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
  • void start() :使该线程开始执行;Java 虚拟机调用该线程的 run 方法。

(2) 线程状态控制

  • boolean isAlive()    测试线程是否处于活动状态。
  • int getPriority()    返回线程的优先级。
  • void setPriority(int newPriority)    更改线程的优先级。
  • static void sleep(long millis)
  • static void sleep(long millis, int nanos)    当前线程暂停运行指定的毫秒数(加指定的纳秒数),但此线程不失去已获得的锁旗标.
  • void join()     等待该线程终止。
  • void join(long millis)    等待该线程终止的时间最长为 millis 毫秒。
  • void join(long millis, int nanos)    等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
  • void interrupt()    中断线程。 调用该方法引发线程抛出InterruptedException异常。
  • static boolean interrupted()    测试当前线程是否已经中断。
  • boolean isInterrupted()    测试线程是否已经中断。
  • static void yield()    暂停当前正在执行的线程对象,并执行其他线程。
  • Thread.State getState()    返回该线程的状态。

(3) 当前线程

  • static Thread currentThread() : 返回对当前正在执行的线程对象的引用。
  • String getName() : 返回该线程的名称.
  • void setName(String name) : 设置线程名称.
  • void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
  • boolean isDaemon() :测试该线程是否为守护线程。
  • String toString() : 返回该线程的字符串表示形式,包括线程名称 优先级和线程组。
  • long getId() :返回该线程的标识符。
  • void checkAccess() :判定当前运行的线程是否有权修改该线程。
  • ClassLoader getContextClassLoader() :返回该线程的上下文 ClassLoader。
  • static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() :返回线程由于未捕获到异常而突然终止时调用的默认处理程序。
  • Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() :返回该线程由于未捕获到异常而突然终止时调用 的处理程序。

(4) 线程组

  • static int enumerate(Thread[] tarray) :将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中。
  • StackTraceElement[] getStackTrace() :返回一个表示该线程堆栈转储的堆栈跟踪元素数组。
  • static int activeCount() :返回当前线程的线程组中活动线程的数目。
  • ThreadGroup getThreadGroup() :返回该线程所属的线程组。

(5) 其他

  • static Map<Thread,StackTraceElement[]> getAllStackTraces() 返回所有活动线程的堆栈跟踪的一个映射。

线程的生命周期

1  新建和就绪状态

当程序使用new关键字创建了一个线程后,该线程就处于新建状态。JVM为Thread对象分配内存。初始化其成员变量的值。线程对象调用start()方法之后,该线程处于就绪状态。 JVM会为其创建方法调用栈和程序计数器。
    就绪状态的线程并没有开始运行,它只是表示该线程可以运行了。JVM的线程调度器调度该线程运行。

注意:

  • 调用start()启动线程之后,run()方法才会被看作线程执行体。
  • 直接调用run()方法,则run()方法就只是一个普通方法。

2  运行和阻塞状态

就绪状态的线程获得了CPU,开始执行run方法的线程执行体。则该线程处于运行状态。线程在执行过程中可能会被中断,以使其他线程获得执行的机会,线程调度取决于底层平台采用的策略。

现代桌面和服务器操作系统一般都采用抢占式策略。一些小型设备如手机则可能采用协作式调度。 抢占式策略的系统:系统给每个可执行的线程一个小时间段来处理任务;当该时间段用完,系统会剥夺该线程所占有的资源,让其他线程获得执行机会.在选择下一个线程时,系统会考虑线程的优先级.

3  线程进入阻塞状态

  1. 线程调用sleep方法主动放弃所占用的处理器资源。
  2. 线程调用了一个阻塞式的IO方法,该方法返回之前,该线程被阻塞。
  3. 线程试图获得一个同步监视器,但同步监视器正被其他线程所持有。
  4. 线程在等待某个通知(notify)。
  5. 线程调用了线程的suspend方法将线程挂起。不过这个方法容易导致死锁,所以程序应该尽量避免使用该方法。

4  阻塞线程重写进入就绪状态

  1. 调用sleep方法的线程经过了指定的时间。
  2. 线程调用的阻塞式IO方法已经返回。
  3. 线程成功地获得了试图取得同步监视器。
  4. 线程正在等待某个通知时,其他线程发出了一个通知。
  5. 处于关闭状态的线程被调用了resume恢复方法。

5  线程死亡

线程在以下情况下死亡:

  • run()方法执行完成,线程正常结束.
  • 线程抛出一个未捕获的Exception或Error.
  • 直接调用该线程的stop()方法来结束该线程.该方法容易导致死锁,通常不推荐使用.

Thread对象的isAlive()方法,查看线程是否死亡。

注意:

  • 死亡的线程不能再用start()方法重新启动。
  • 一个线程的start()方法不能两次调用。

6  线程睡眠:sleep

sleep方法将线程转入阻塞状态。时间到即再转入就绪状态。

线程的操作与特性

1  线程让步: yeild

yeild() 方法是静态方法。该方法使当前线程让出CPU资源,继续参与线程竞争。

2  join线程

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException

join()方法表示当前线程等待指定线程的结束,等待的线程结束后继续执行当前线程。

例:join方法示例。

public class JoinMain {
public volatile static int i=0;
public static class AddThread extends Thread{
@Override
public void run() {
for(i=0;i<10000000;i++);
}
} public static void main(String[] args) throws InterruptedException {
AddThread at=new AddThread();
at.start();
at.join(); // 当前线程(主线程)等待at线程运行结束。
System.out.println(i);
}
}

join的本质

查看join方法的源码,可以了解到,join即相当于以下代码:

while ( 指定的线程.isAlive()) {
wait(0);
}

当前线程会一直调用wait()方法,所以当前线程会一直处于等待状态。当被等待的线程结束(即指定的线程结束),JVM会自动调用notifyAll()方法来通知当前线程wait已经结束。注意,此处不要手动调用thread.notifyAll()方法。(关于join方法,JDK文档有更详细的说明)

3  线程优先级

每个线程都有优先级。优先级高的线程获得较多的执行机会。默认情况下,main线程具有普通优先级。每个线程默认优先级与创建它的父线程具有同样的优先级。

Java提供的优先级范围是1~10。默认优先级为5。

Thread提供静态常量:

  • static int MAX_PRIORITY 线程可以具有的最高优先级。 值为10。
  • static int MIN_PRIORITY 线程可以具有的最低优先级。值为1。
  • static int NORM_PRIORITY 分配给线程的默认优先级。值为5。

注意:

  • 不同操作系统的优先级不同.应尽量避免直接为线程指定优先级,而应使用以上三个静态常量类设置优先级.

4  后台线程

运行在后台,用于为用户线程提供服务。又称为“守护线程”。所有用户线程都结束后,后台线程也会结束,JVM退出。

  • main方法的主线程是前台线程。
  • 前台线程创建的线程默认是前台线程。后台线程创建的线程默认是后台线程。

比较:

  • 普通线程:若主线程结束,普通线程不会结束,JVM不会退出。
  • 守护线程:若主线程以及所有普通线程都结束,则后台线程会直接死亡,JVM退出。

常用API

  • 调用Thread对象的setDeamon(true)方法可以将指定线程设置成后台线程。
  • Thread对象的isDeamon()方法用于判断指定线程是否为后台线程。

5  停止线程

若需要停止线程,不推荐使用stop()方法。stop方法强制线程停止,切线程会释放所有monitor。由于stop方法过于粗暴,已经被废弃。

例:现在有两条记录。两条线程的其中一个线程要写对象,另一个线程要读对象。两个线程对对象加锁。

若使用stop方法,可能导致数据一致性的错误。

public class StopThreadTest {

    static Student stu = new Student();

    public static void main(String[] args) throws InterruptedException {

        Thread writeThread = new Thread(){
@Override
public void run() {
// 假设writeThread比readThread先拿到stu的锁。
synchronized(stu){
stu.id = 2; // 可能该语句执行完后,主线程中 writeThread.stop(); 开始产生作用。
// 若主线程执行 writeThread.stop();则该线程在此处将强行结束。
java_label_stop:
stu.name = "小王";
}
}
}; Thread readThread = new Thread(){
@Override
public void run() {
// 假设writeThread比readThread先拿到stu的锁。
synchronized(stu){
// 若在java_label_stop处writeThread被结束,则该语句将打印出 2 ; 小明 。从而导致数据一致性错误
System.out.println(stu.id + ";" + stu.name);
} }
}; // 主线程设置stu。
stu.id = 1;
stu.name = "小明"; // 假设writeThread比readThread先拿到stu的锁。
writeThread.start();
readThread.start(); // 强制停止写线程。
writeThread.stop();
} } class Student{
public int id;
public String name;
}

6 线程中断

public void Thread.interrupt()               // 中断线程。修改线程的中断状态,但线程本身不会有任何响应,依旧运行。

public boolean Thread.isInterrupted()        // 判断是否被中断。

public static boolean Thread.interrupted()   // 判断是否被中断,并清除当前中断状态

调用线程中断方法只是给线程“打个招呼”,线程不会本身不会做任何相应。

例:调用interrupt()方法,线程依旧运行。

public class InterruptThreadTest {
public static void main(String[] args) { Thread t1 = new Thread(){ @Override
public void run(){
while(true){
Thread.yield();
}
}
}; t1.start(); // 启动线程。
t1.interrupt(); // 终端线程,线程会依旧运行。
}
}

线程中断方法可以用于结束线程,非常优雅方便。

例:线程外部使用中断方法,结束线程。

public class InterruptStopThreadTest {
public static void main(String[] args) {
Thread t1 = new Thread() { @Override
public void run() {
while (true) {
// 检测线程被中断
if (Thread.currentThread().isInterrupted()) {
// 若线程中断,则推出run方法。
System.out.println("Interruted!");
break;
} Thread.yield();
}
}
}; t1.start(); // 启动线程。
t1.interrupt(); // 终端线程,但线程依旧运行。t1.isInterrupted()方法将返回true。
}
}

InterruptedException异常

大部分线程的等待方法都会抛出InterruptedException异常,中断标志位将会被清空。Java方法中默认的等待线程一旦被interrupt(),则等待方法会立即抛出InterruptedException异常。

例:处于sleep的线程被interrupt,立即抛出InterruptedException。

public class InterruptedExceptionTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() { @Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interruted!");
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("Interruted When Sleep");
// 抛出异常后会清除中断标记位。所以重新设置中断状态。
Thread.currentThread().interrupt();
}
Thread.yield();
}
}
}; t1.start(); // 让主线程在恰当的时间去中断t1。
Thread.sleep(1000); // 此时t1处于sleep状态。interrupt() 方法会使t1中的sleep方法抛出InterruptedException异常。
t1.interrupt(); }
}

打印输出:

Interruted When Sleep
Interruted!

7 suspend与resume

suspend() 方法表示将线程挂起。resume() 方法表示继续执行挂起的线程。

但需要注意suspend()不会释放锁。如果加锁发生在resume()之前,则发生死锁。

这两个方法都已经被废弃,不要使用。

例:运行以下程序,程序将被锁死。

package sjq.thread.suspend_resume;
package sjq.thread.suspend_resume; public class SuspendResumeThreadTest { public static Object u = new Object();
public static ChangeObjectThread t1 = new ChangeObjectThread("t1");
public 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 " + super.getName());
Thread.currentThread().suspend();
}
} } public static void main(String[] args) throws InterruptedException {
t1.start();
Thread.sleep(100);
t2.start();
t1.resume(); // 主线程通知t1要释放resume,但t1线程可能还未执行suspend,当t1执行了suspend后,则t1将永远被挂起。且不会释放资源。
t2.resume(); // 主线程通知t2要释放resume,但t2线程可能还未执行suspend,当t2执行了suspend后,则t2将永远被挂起。且不会释放资源。
t1.join();
t2.join();
}
}

控制台显示:

该程序通过jstack查看线程情况,发现t2线程处于RUNNABLE状态,被suspend0方法挂起。且t2拥有Object的锁,只要t2线程不结束,Object的锁就不会被释放。

Java 并发——多线程基础的更多相关文章

  1. java并发多线程显式锁Condition条件简介分析与监视器 多线程下篇(四)

    Lock接口提供了方法Condition newCondition();用于获取对应锁的条件,可以在这个条件对象上调用监视器方法 可以理解为,原本借助于synchronized关键字以及锁对象,配备了 ...

  2. Java并发多线程 - 并发工具类JUC

    安全共享对象策略 1.线程限制 : 一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改 2.共享只读 : 一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问, 但是任何线程都 ...

  3. 并发-Java并发编程基础

    Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...

  4. Java并发(基础知识)—— Executor框架及线程池

    在Java并发(基础知识)—— 创建.运行以及停止一个线程中讲解了两种创建线程的方式:直接继承Thread类以及实现Runnable接口并赋给Thread,这两种创建线程的方式在线程比较少的时候是没有 ...

  5. Java并发编程--基础进阶高级(完结)

    Java并发编程--基础进阶高级完整笔记. 这都不知道是第几次刷狂神的JUC并发编程了,从第一次的迷茫到现在比较清晰,算是个大进步了,之前JUC笔记不见了,重新做一套笔记. 参考链接:https:// ...

  6. java 并发多线程 锁的分类概念介绍 多线程下篇(二)

    接下来对锁的概念再次进行深入的介绍 之前反复的提到锁,通常的理解就是,锁---互斥---同步---阻塞 其实这是常用的独占锁(排它锁)的概念,也是一种简单粗暴的解决方案 抗战电影中,经常出现为了阻止日 ...

  7. java 并发多线程显式锁概念简介 什么是显式锁 多线程下篇(一)

    目前对于同步,仅仅介绍了一个关键字synchronized,可以用于保证线程同步的原子性.可见性.有序性 对于synchronized关键字,对于静态方法默认是以该类的class对象作为锁,对于实例方 ...

  8. Java并发编程基础

    Java并发编程基础 1. 并发 1.1. 什么是并发? 并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互 ...

  9. 多线程(一)java并发编程基础知识

    线程的应用 如何应用多线程 在 Java 中,有多种方式来实现多线程.继承 Thread 类.实现 Runnable 接口.使用 ExecutorService.Callable.Future 实现带 ...

随机推荐

  1. VJP1100 加分二叉树(树形DP)

    链接 归属树形DP  做着更像记忆化 DP很好做 就是那个输出路径恶心了..改代码 从60多行改到120多行..dp从1维加到三维.. 先类似记忆化搜索整棵树 枚举以i为根节点的最大值 子树类似 求完 ...

  2. Hibernate级联操作

    cascade属性的可能值有 all: 所有情况下均进行关联操作,即save-update和delete. none: 所有情况下均不进行关联操作.这是默认值. save-update: 在执行sav ...

  3. web项目测试方法总结

    在Web工程过程中,基于Web系统的测试.确认和验收是一项重要而富有挑战性的工作.基于Web的系统测试与传统的软件测试不同,它不但需要检查和验证是否按照设计的要求运行,而且还要测试系统在不同用户的浏览 ...

  4. JSOI2009 游戏

    1443: [JSOI2009]游戏Game Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 557  Solved: 251[Submit][Stat ...

  5. Win32下 Qt与Lua交互使用(二):在Lua脚本中使用Qt类

    话接上篇.成功配置好Qt+Lua+toLua后,我们可以实现在Lua脚本中使用各个Qt的类.直接看代码吧. #include "include/lua.hpp" #include ...

  6. [light oj 1328] A Gift from the Setter

    1328 - A Gift from the Setter   Problem setting is somewhat of a cruel task of throwing something at ...

  7. [转]关于WM_NCHITTEST消息

    http://www.cnblogs.com/GnagWang/archive/2010/09/12/1824394.html 我为了移动一个无标题栏的窗体,使用了WM_NCHITTEST消息,这个消 ...

  8. FZU2232 炉石传说 最大匹配

    思路:正好是二分图,自己敌人,符合条件的随从二人组建边,最大匹配为n是符合要求 #include <cstdio> #include <cstring> #include &l ...

  9. zabbix2.2.2-hostgroups.php代码分析(1)

    想模仿hostgroups.php仿写一个主机组页面.直接从页面展示开始分析. if (isset($_REQUEST['form'])) { //这里面是点击创建主机组的页面展示 }else{ // ...

  10. 【CSS3】Advanced9:Transformation

    1.transform:rotate(-10deg) skew(20deg,10deg) scaling(2/1,2) translate/移动(100px,200px) 2.transform:ma ...