Java线程浅析

本文目录

1 线程概念

2 线程任务

3 线程操作

4 Executor

5 CompletionService

 

1 线程概念

运行程序,会使用到CPU、内存等资源。程序将通过“进程”来申请并管理所需要的相关资源。每个“进程”会有一个“主线程”,“线程”使用进程申请来的资源,去执行相关的逻辑操作。“主线程”可以创建其它新的“线程”,多个“线程”可以并行(同时)执行各自的逻辑操作。

 

实际上,对于单核处理器而言,多个线程并不是真正的同时执行,而是轮流获得CPU使用权,执行一部分操作,如图:

 

当每次CPU轮换的时间间隔无限缩小,趋近于0时,给我们的感觉,像是每个线程在同时进行,如图:

 

对于多核处理器,当线程数量小于处理器核数的时候,每个线程将由其中一个核单独处理。当线程数量大于处理器核数时,某些线程将共享一个核,相当于上述多线程单核情况。

 

2 线程任务

使用线程,首先要确定线程内执行什么任务。有两种方法,来确定线程任务。

 

2.1 方法一:继承Thread

class ThreadExample extends Thread
{
    // 重写run方法
    public void run() {
        // 线程执行内容
    }
}

 

2.2 方法二:实现Runnable

class RunnableExample implements Runnable
{
    // 重写run方法
    public void run() {
        // 线程执行内容
    }
}

 

2.3 二者区别

Thread类是对线程的一个抽象,其中包含一些线程的操作方法,例如:启动线程、终止线程等。

Runnable类是对线程任务的一个抽象,主要是包含相关的处理逻辑。

将“线程的操作”与“线程的任务”分离,各自关注各自的职责和任务,是一个很好的方案。

另外,由于Java不允许多继承关系,所以,对于某父类的子类,只能通过实现Runnable接口的方法。

 

3 线程操作

 

3.1 创建线程对象(构造方法)

若通过继承Thread类(方法一)来定义线程执行内容,则直接创建该子类对象即可。

class ThreadExample extends Thread
{
    // 重写run方法
    public void run() {
        // 线程执行内容
    }
}

class ThreadModel {

    public static void main(string[] args) {

        // 创建ThreadExample线程对象
        ThreadExample thread = new ThreadExample();
    }
}

 

若通过实现Runnable接口(方法二)来定义线程执行内容,则创建标准线程对象并传入Runnable对象。

class RunnableExample implements Runnable
{
    // 重写run方法
    public void run() {
        // 线程执行内容
    }
}

class ThreadModel {

    public static void main(string[] args) {

        // 创建RunnableExample对象
        RunnableExample runnable = new RunnableExample();

        // 创建标准线程并传入runnable对象(传入线程内容)
        Thread thread = new Thread(runnable);
    }
}

 

方法二,通过传入Runnable对象来指定线程内容,因此,可以通过Runnable对象实现数据共享(共享的私有变量)。

示例代码如下,多个线程可以使用同一个Runnable对象,当两个线程执行完毕时,私有变量shareNum值为2。

class RunnableExample implements Runnable
{
    // 私有变量
    int sharedNum = 1;

    // 重写run方法
    public void run() {
        sharedNum++;
    }
}

class ThreadModel {

    public static void main(string[] args) {

        // 创建RunnableExample对象
        RunnableExample runnable = new RunnableExample();

        // 创建标准线程1并传入runnable对象
        Thread thread1 = new Thread(runnable);

        // 创建标准线程2并传入runnable对象
        Thread thread2 = new Thread(runnable);
    }
}

 

指定与获取线程名称。

// 指定线程名称
Thread thread = new Thread(String threadName);  
Thread thread = new Thread(Runnable runObj, String threadName);

// 获取线程名称
Thread.currentThread().getName();

 

3.2 启动线程

threadModel.start()

开启一个新的线程。

 

threadModel.start() 和 threadModel.run() 的区别:

threadModel.start() 是开启一个新线程去执行 run() 方法,如图:

 

threadModel.run() 是在当前线程中调用 run() 方法,与调用其它方法一样,如图:

 

3.3 线程睡眠(暂停)

Thread.sleep(long millis)

静态方法。暂停当前线程一段时间(millis-毫秒),继续执行。

当前线程:调用Thread.sleep方法的线程,即Thread.sleep代码所在的线程。

 

Thread.sleep(long millis) 与 Object.wait() 的区别:

Thread.sleep方法是线程操作方法,Object.wait方法是同步锁的方法。

实际上,二者并不是同一类的内容,不应该进行比较。但是,二者表现出来的效果相似,因此,常常被混淆。

Thread.sleep方法涉及当前线程,是对线程进行暂停操作。

Object.wait涉及同步锁和并发问题,是关于锁和执行权的问题。

具体分析,请浏览《Java内置锁与显式锁浅析》文章。

 

3.4 终止线程

threadModel.interrupt()

终止threadModel线程,即使该线程还没执行完所有内容。

通过该方法终止线程,会使得被终止的线程抛出InterruptedException异常。

 

3.5 线程状态

threadModel.isAlive()

判断线程是否存活。若线程存活(没有被销毁),则返回true;否则,返回false。

 

3.6 线程加入(等待其它线程)

threadOther.join()

当前线程暂停,等待threadOther线程执行完毕后,继续执行,如图:

 

3.7 降低优先权

Thread.yield()

静态方法。表示临时降低当前线程优先权,使CPU优先分派资源(使用权)给其它线程。

 

4 Executor

4.1 基本概念

线程池用于管理线程,提供一系列管理线程的方法。

其主要优点在于,通过线程重用,减少线程创建及销毁的次数,从而减少相关操作的消耗。

Executor框架中的线程重用,不是简单的替换Runnable内容并重新运行线程,而是通过线程及阻塞队列达到重用的效果(即不需要重复创建和销毁线程),是典型的“生产者-消费者”模式。线程池会保持所有线程持续运行,每个线程不断从阻塞队列中提取Runnable任务并执行。若队列中无等待执行的任务,则暂时阻塞线程,待新的任务加入队列后,再次唤醒线程,从而达到线程重用的效果。

 

4.2 创建Executor

ExecutorService executor = Executors.newSingleThreadExecutor()

创建一个单线程池,所有可执行任务将按照指定顺序依次执行,即串行化执行。

 

ExecutorService executor = Executors.newFixedThreadPool(int threadNum)

创建一个定长线程池,该线程池拥有固定数量的线程,即可控制线程的最大并发数量。当可执行任务的数量超过线程数量时,多余的部分将在队列中等待。

 

ExecutorService executor = Executors.newCachedThreadPool()

创建一个可缓存的线程池,根据需要增加和减少线程数量,无线程数量限制(但是需要考虑硬件配置)。

 

ExecutorService executor = new ThreadPoolExecutor(...)

创建一个自定义线程池,可设置核心线程数、最大线程数、闲置线程存活时间、任务队列、任务队列满载处理方法等内容。

 

4.3 添加执行任务

executor.execute(Runnable task)

将task对象加入执行队列中,当有空闲线程时,按照队列顺序,执行task任务。

 

Future<V> future = executor.submit(Callable<V> task)

该方法同execute方法,不同之处在于,该方法会返回一个Future对象,通过Future对象,可以获得任务执行结果。

该方法涉及Callable和Future的使用,不在本文讨论范围。

 

4.4 停止

executor.shutdown()

可理解为关闭添加入口(关闭大门),无法再添加新的执行任务。已经添加到执行队列的任务,不受影响,继续等待被执行。

 

executor.shutdownNow()

尝试停止所有正在执行和等待执行的任务,并返回所有等待执行的任务列表(List)。

 

4.5 等待执行结束

executor.awaitTermination(long timeout, TimeUnit unit)

阻塞当前线程(调用该方法的线程),直到线程池中的所有执行任务被完成或发生超时(Timeout)。

 

5 CompletionService

5.1 基本概念

通过Executor管理线程池,大大简化了线程操作,通过Future对象,我们还可以对执行中的线程进行控制。

但是,通过Future和Executor框架,我们无法知道哪一个线程会最先执行完毕,因此,无法知道应该先对哪一个Future对象结果进行处理。若通过轮询每一个Future对象并执行get操作,虽然可行,但不是一个好的方法。

CompletionService将Executor和BlockingQueue的功能进行融合,可以将Callable任务交给它进行管理,由CompletionService进行任务的提交。当任务执行完成后,会将结果加入到BlockingQueue队列。因此,我们只需要关注队列中的数据,并通过队列来获取执行结果即可。最先执行完毕的任务会被第一个加入到队列,也将被第一个获取并处理。

 

5.2 创建CompletionService

 

CompletionService<T> service = new ExecutorCompletionService<>(Executor executor, BlockingQueue queue)

指定一个Executor和一个阻塞队列,创建一个CompletionService对象,此后,通过service对象进行任务提交和结果获取操作。

 

5.3 提交任务

service.submit(Callable<T> task)

向service对象提交一个任务,该任务将由executor执行,其结果将加入到queue队列。

 

5.4 获取结果

Future<T> future = service.poll()

获取下一个已完成任务的Future对象,若暂时没有已完成任务,则返回null值。

 

Future<T> future = service.take()

获取下一个已完成任务的Future对象,若暂时没有已完成任务,则阻塞等待,直到下一个已完成任务的Future对象被添加至队列。

 

6 相关文章

《Java内置锁与显式锁浅析》

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值