基本线程机制

简介:

基本线程机制

一个程序可以被划分为多个独立的任务,每个独立的任务可以由线程来驱动执行;

一个进程可以包含若干个线程,即拥有若干个并发执行的任务,在程序运行时,CPU时间被划分成片段分配给所有的线程;

在单处理器的机器上使用多线程可以提高性能的原因在于任务阻塞;

为机器增加处理器可以显著加快使用多线程程序的运行速度;

使用线程机制使程序更加透明、可扩展,代码不需要知道它是运行在单处理器还是多处理器上;

创建线程方式

方式一、创建一个任务类实现Runnable接口,并将其具体对象提交给Thread构造器

创建一个发射类LiftOff实现Runnable接口:

  View Code

以上代码中调用了Thread.yield()方法,该方法的作用是建议线程调度器切换到其它线程执行任务,注意,只是建议,不保证采纳;

创建完任务类之后,可以在Main函数中使用LiftOff对象创建一个Thread对象,并调用其start方法启动该线程,如下:

复制代码
package concurrency;

public class BasicThreads {
    public static void main(String[] args) {
        Thread t = new Thread(new LiftOff());
        t.start();
        System.out.println(Thread.currentThread() + "Waiting for LiftOff");
    }
} 
复制代码

打印结果如下,注意该程序中是同时存在两个线程(main和Thread-0)在运行的;

另外关于Thread对象的打印形式为[Thread-0,5,main],其中依次代表[线程名,线程优先级、线程组名], 具体可查看Thread类的toString方法;

复制代码
Thread[main,5,main]Waiting for LiftOff
Thread[Thread-0,5,main]#0(9), 
Thread[Thread-0,5,main]#0(8), 
Thread[Thread-0,5,main]#0(7), 
Thread[Thread-0,5,main]#0(6), 
Thread[Thread-0,5,main]#0(5), 
Thread[Thread-0,5,main]#0(4), 
Thread[Thread-0,5,main]#0(3), 
Thread[Thread-0,5,main]#0(2), 
Thread[Thread-0,5,main]#0(1), 
Thread[Thread-0,5,main]#0(Liftoff!), 
复制代码

最后,提个醒,有些人在创建完任务类后,直接在main函数中新建一个任务类对象,并调用其run方法,如下代码,运行正常,也看到了run方法中的运行结果,以为创建了线程,其实这种使用方式是错误的,并没有创建任何新线程,只是在main线程里调用执行了一个普通对象的方法而已;

复制代码
package concurrency;

public class MainThread {
    public static void main(String[] args) {
        LiftOff launch = new LiftOff();
        launch.run();
    }
} 
复制代码

方式二、继承Thread类,调用其具体对象的start方法

  View Code

对比通过实现Runnable接口的方式,该方式不建议使用,因为java的单继承机制,通常通过实现接口比继承会更好点;

另外还可以通过内部内部类将线程代码隐藏在类中,如下写法;

  View Code

方式三、创建一个任务类实现Runnable接口,并将其具体对象提交给Executors【推荐】

java.util.concurrent包中的执行器Executors可以帮助我们管理Thread对象,简化并发编程,如下,可以使用Executors类中的newCachedThreadPool静态方法创建一个可缓存的线程池,并用其执行相关任务;

复制代码
package concurrency;

import java.util.concurrent.*;

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++)
            exec.execute(new LiftOff());
        exec.shutdown();
    }
}
复制代码

在Executors类中,除了通过newCachedThreadPool创建线程池外,还可以创建通过以下方法创建其它种类的线程池:

newFixedThreadPool:固定大小度的线程池

newSingleThreadExecutor:单线程线程池

newScheduledThreadPool:执行定时和周期性任务

方式四、创建一个任务类实现Callable接口,并将其具体对象提交给Executors【推荐】

实现Callable接口的类同样是一个任务类,与实现Runnable接口的区别是该方式可以有返回值;

在实现Callable接口的类中,线程执行的方法是call方法(有返回值),而不是run方法;

在main方法中可以通过调用ExecutorService的submit方法,返回一个Future对象,通过该对象可以获取线程运行的返回值,注意需要等Future完成后才能取得结果,可以通过isDone方法来查询Future是否已完成,或者直接调用get方法来获取(会阻塞,直到结果准备就绪)。

复制代码
package concurrency;

import java.util.concurrent.*;
import java.util.*;

class TaskWithResult implements Callable<String> {
    private int id;

    public TaskWithResult(int id) {
        this.id = id;
    }

    public String call() {
        return "result of TaskWithResult " + id;
    }
}

public class CallableDemo {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        ArrayList<Future<String>> results = new ArrayList<Future<String>>();
        for (int i = 0; i < 10; i++)
            results.add(exec.submit(new TaskWithResult(i)));
        for (Future<String> fs : results)
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                System.out.println(e);
                return;
            } catch (ExecutionException e) {
                System.out.println(e);
            } finally {
                exec.shutdown();
            }
    }
} 
复制代码

小结

其实,更普遍的,我觉得创建线程就两种形式:

  • 直接通过new Thread创建线程(可传入任务对象);
  • 创建任务对象提交给Executors去创建(其实内部的线程工厂也是通过new Thread创建);

另外,这里的任务对象也有两种方式创建,通过实现Runnable接口和实现Callable接口;

 守护线程(后台线程)

daemon线程是指在程序运行的时候,在后台提供一种通用服务的线程,这种线程的优先级非常低;

当所有其他线程结束时,会杀死进程中的所有守护线程;

可以在线程启动之前通过setDaemon(true)方法将线程设置为守护线程,注意只能在启动之前设置;

通过守护线程创建的线程会被自动设置为守护线程;

可以通过isDaemon方法来判断一个线程是否是守护线程;

举个守护线程的例子,代码如下,当main线程运行结束后,所有的守护线程也被终止:

复制代码
package concurrency;

import java.util.concurrent.*;

public class SimpleDaemons implements Runnable {
    public void run() {
        try {
            while (true) {
                TimeUnit.MILLISECONDS.sleep(100);
                System.out.println(Thread.currentThread() + " " + this);
            }
        } catch (InterruptedException e) {
            System.out.println("sleep() interrupted");
        }
    }

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            Thread daemon = new Thread(new SimpleDaemons());
            daemon.setDaemon(true); // Must call before start()
            daemon.start();
            
        }
        System.out.println("All daemons started");
        TimeUnit.MILLISECONDS.sleep(175);
    }
}
复制代码

加入一个线程Thread.join方法

一个线程(T1)可以在其它线程(T2)之上调用join方法,结果是T1线程被挂起,等待T2线程执行完毕(T2.isAlive()==false),然后继续执行T1线程;

也可以在join方法上加一个超时参数,保证join方法在指定时间内总能返回;

join方法可以被中断,如调用T2.interrupt()方法,中断后,join方法可以立即返回;

代码实例:

复制代码
package concurrency;

class Sleeper extends Thread {
    private int duration;

    public Sleeper(String name, int sleepTime) {
        super(name);
        duration = sleepTime;
        start();
    }

    public void run() {
        try {
            sleep(duration);
        } catch (InterruptedException e) {
            System.out.println(getName() + " was interrupted. "
                    + "isInterrupted(): " + isInterrupted());
            return;
        }
        System.out.println(getName() + " has awakened");
    }
}

class Joiner extends Thread {
    private Sleeper sleeper;

    public Joiner(String name, Sleeper sleeper) {
        super(name);
        this.sleeper = sleeper;
        start();
    }

    public void run() {
        try {
            sleeper.join();
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
        }
        System.out.println(getName() + " join completed");
    }
}

public class Joining {
    public static void main(String[] args) {
        Sleeper sleepy = new Sleeper("Sleepy", 1500), grumpy = new Sleeper(
                "Grumpy", 1500);
        Joiner dopey = new Joiner("Dopey", sleepy), doc = new Joiner("Doc",
                grumpy);
        grumpy.interrupt();
        
        try {
            sleepy.join();
        } catch (InterruptedException e) {
            
            e.printStackTrace();
        }
        System.out.println("main thread continue until sleepy thread over");
    }
} 
复制代码

在该示例中,我们把dopey、main线程加入到sleepy线程,doc线程加入到grumpy线程,结果如下:

grumpy线程被中断,然后join方法立即返回,打印Doc join completed,在grumpy线程中,isInterrupted()之所以打印false是因为异常捕获时把该标志清理了;

sleepy线程执行完毕后,join方法返回,继续执行dopey线程和main线程未完成部分,打印“main thread continue until sleepy thread over”和“Dopey join completed”;

捕获线程异常

在main方法中使用try-catch不能捕获其它线程产生的异常,如下示例,RuntimeException未被处理:

复制代码
package concurrency;

import java.util.concurrent.*;

public class ExceptionThread implements Runnable {
    public void run() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        try {
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.execute(new ExceptionThread());
        } catch (RuntimeException ue) {

            System.out.println("Exception has been handled!");
        }
    }
}
复制代码

在JAVA SE5之前,可以使用线程组捕获异常,在JAVA SE5之后可以用Executor来解决这个问题;

只需要写一个异常处理类并实现Thread.UncaughtExceptionHandler接口,然后在创建线程的时候,设置该线程的未捕获异常处理器为该类实例,通过setUncaughtExceptionHandler方法设置,如下代码;

  View Code

除了为每个线程设置专门的未捕获异常处理器外,还可以设置默认的未捕获异常处理器,当系统检查到某个线程没有专门的未捕获异常处理器的时候,会使用默认的未捕获异常处理器;

复制代码
package concurrency;

import java.util.concurrent.*;

public class SettingDefaultHandler {
  public static void main(String[] args) {
    Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
    ExecutorService exec = Executors.newCachedThreadPool();
    exec.execute(new ExceptionThread());
  }
} 
复制代码

 

本文转自风一样的码农博客园博客,原文链接:http://www.cnblogs.com/chenpi/p/5306710.html,如需转载请自行联系原作者

相关文章
|
13天前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
40 2
|
17天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
22 1
|
5月前
|
Java
并发编程的艺术:Java线程与锁机制探索
【6月更文挑战第21天】**并发编程的艺术:Java线程与锁机制探索** 在多核时代,掌握并发编程至关重要。本文探讨Java中线程创建(`Thread`或`Runnable`)、线程同步(`synchronized`关键字与`Lock`接口)及线程池(`ExecutorService`)的使用。同时,警惕并发问题,如死锁和饥饿,遵循最佳实践以确保应用的高效和健壮。
46 2
|
1月前
|
安全 Java 开发者
在多线程编程中,确保数据一致性与防止竞态条件至关重要。Java提供了多种线程同步机制
【10月更文挑战第3天】在多线程编程中,确保数据一致性与防止竞态条件至关重要。Java提供了多种线程同步机制,如`synchronized`关键字、`Lock`接口及其实现类(如`ReentrantLock`),还有原子变量(如`AtomicInteger`)。这些工具可以帮助开发者避免数据不一致、死锁和活锁等问题。通过合理选择和使用这些机制,可以有效管理并发,确保程序稳定运行。例如,`synchronized`可确保同一时间只有一个线程访问共享资源;`Lock`提供更灵活的锁定方式;原子变量则利用硬件指令实现无锁操作。
20 2
|
6月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
2月前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
30 0
|
3月前
探索操作系统中的线程同步机制
【8月更文挑战第31天】在多线程编程领域,理解并实现线程同步是至关重要的。本文通过浅显易懂的语言和生动的比喻,带你走进线程同步的世界,从互斥锁到信号量,再到条件变量,逐步揭示它们在协调线程行为中的作用。我们将一起动手实践,用代码示例加深对线程同步机制的理解和应用。
|
4月前
|
调度
【浅入浅出】Qt多线程机制解析:提升程序响应性与并发处理能力
在学习QT线程的时候我们首先要知道的是QT的主线程,也叫GUI线程,意如其名,也就是我们程序的最主要的一个线程,主要负责初始化界面并监听事件循环,并根据事件处理做出界面上的反馈。但是当我们只限于在一个主线程上书写逻辑时碰到了需要一直等待的事件该怎么办?它的加载必定会带着主界面的卡顿,这时候我们就要去使用多线程。
151 6
|
4月前
|
存储 前端开发 Java
(二)JVM成神路之剖析Java类加载子系统、双亲委派机制及线程上下文类加载器
上篇《初识Java虚拟机》文章中曾提及到:我们所编写的Java代码经过编译之后,会生成对应的class字节码文件,而在程序启动时会通过类加载子系统将这些字节码文件先装载进内存,然后再交由执行引擎执行。本文中则会对Java虚拟机的类加载机制以及执行引擎进行全面分析。
|
4月前
|
Java 调度
Java中的线程池机制详解
Java中的线程池机制详解