java 编程思想 多线程学习笔记

简介: java 编程思想 多线程学习笔记

一、如何创建多线程?

一种是继承Thread类,一种是实现Runnable接口,最后一种就是Callable

1、继承 java.lang.Thread 类

public class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        System.out.println("MyThread - START "+Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
            //Get database connection, delete unused data from DB
            doDBProcessing();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("MyThread - END "+Thread.currentThread().getName());
    }
    private void doDBProcessing() throws InterruptedException {
        Thread.sleep(5000);
    }
}

2、实现 java.lang.Runnable 接口

把需要多线程运行的程序放到 public void run() 方法里,然后在main方法中 new 一个thread,并在实例化的时候调用start(); 例如如下代码:

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();
    }
  }
} ///:~
import com.chot.concurrency.LiftOff;
//当main()创建thread对象时,它并没有捕获任何对这些对象的引用。在使用普通对象时,这对与垃圾回收来说是一场公平的游戏,但是在使用thread时,情况就不同了。
//每个thread 都“注册”了它自己,因此确实有一个对它的引用,而且在它的任务退出其run() 并死亡之前,垃圾回收器无法清除它。
// (为何GC 无法将其清除呢? 答:因为有栈帧对其有引用,即有一个指针只想堆中那个由thread创建的object。故无法标记其可清除。)
public class MoreBasicThreads {
  public static void main(String[] args) {
    for(int i = 0; i < 5; i++)
      new Thread(new LiftOff()).start();
    System.out.println("Waiting for LiftOff");
  }
}

另一个例子

public class HeavyWorkRunnable implements Runnable {
@Override
public void run() {
    System.out.println("Doing heavy processing - START "+Thread.currentThread().getName());
    try {
        Thread.sleep(1000);
        //Get database connection, delete unused data from DB
        doDBProcessing();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("Doing heavy processing - END "+Thread.currentThread().getName());
}
private void doDBProcessing() throws InterruptedException {
    Thread.sleep(5000);
}
}

结果

public class ThreadRunExample {
    public static void main(String[] args){
        Thread t1 = new Thread(new HeavyWorkRunnable(), "t1");
        Thread t2 = new Thread(new HeavyWorkRunnable(), "t2");
        System.out.println("Starting Runnable threads");
        t1.start();
        t2.start();
        System.out.println("Runnable Threads has been started");
        Thread t3 = new MyThread("t3");
        Thread t4 = new MyThread("t4");
        System.out.println("Starting MyThreads");
        t3.start();
        t4.start();
        System.out.println("MyThreads has been started");
    }
}
Starting Runnable threads
Runnable Threads has been started
Doing heavy processing - START t1
Doing heavy processing - START t2
Starting MyThreads
MyThread - START Thread-0
MyThreads has been started
MyThread - START Thread-1
Doing heavy processing - END t2
MyThread - END Thread-1
MyThread - END Thread-0
Doing heavy processing - END t1

3、Callable接口

既然有了前面两种接口,为什么还需要第三种呢?这是因为前两种方式存在着几种缺陷,我们先来看看前面两种实现的方式,然后再来揭晓:

20200715143611959.png

现在我们观察里面的run方法,返回的都是void,也就是说这两种方式都不能返回处理后的结构。但是Callable接口的出现可以有效地解决这一问题。答案很简单。现在我们来看看如何实现。

1、创建线程

2、使用Executor

Executor在客户端和任务执行之间提供一个简介层,像是一个中介一样,任务就是在这些中介这里执行的,Executor允许你管理异步任务的执行,不需要显式的管理线程的生命周期。目前项目在使用的就是此种方式。


使用Executor有两种方式

                                  ExecutorService exec = Executors.newCachedThreadPool(); 
                                  ExecutorService exec = Executors.newFixedThreadPool(5);

两种方式孰优孰劣?

fixed 一次性预先执行代价高昂的线程分配。因而可以控制线程的数量。newCachedThreadPool在程序执行的过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程。

拿我接触到的项目来说,线程的创总和某些事情会关联起来,即会有时间来驱动来创建线程,所以使用的newCachedThreadPool。

总之,在任何线程池中,现有的线程可能的情况都会被复用。

下面的代码是用Executor来替换moreBasicThreads.java

1、cachedThreadPoll
package com.chot.concurrency.Chap21_2_3;
import com.chot.concurrency.LiftOff;
import java.util.concurrent.*;
// 将morebasicThreads 改造成使用cachedThreadPool
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();
  }
}
2、指定5个线程数
package com.chot.concurrency.Chap21_2_3;
import com.chot.concurrency.LiftOff;
import java.util.concurrent.*;
// 将morebasicThreads 改造成使用cachedThreadPool
public class CachedThreadPool {
  public static void main(String[] args) {
    ExecutorService exec = Executors.newFixedThreadPool(5);
    for(int i = 0; i < 5; i++)
      exec.execute(new LiftOff());
    exec.shutdown();
  }
}

还有一种 SingleThreadExecutor。这就像是线程数量为1的FixedThreadPool


ExecutorService exec = Executors.newSingleThreadExecutor();


个人认为在debug的时候使用此种方式将使你的focus不会很乱。如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队,每一个任务都会在下一个任务开始之前运行结束。所有的任务将使用相同的线程。


#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1),#0(Liftoff!)

#1(9), #1(8), #1(7), #1(6), #1(5), #1(4), #1(3), #1(2), #1(1), #1(Liftoff!)

#2(9), #2(8), #2(7), #2(6), #2(5), #2(4), #2(3), #2(2), #2(1), #2(Liftoff!)

#3(9), #3(8), #3(7), #3(6), #3(5), #3(4), #3(3), #3(2), #3(1), #3(Liftoff!)

#4(9), #4(8), #4(7), #4(6), #4(5), #4(4), #4(3), #4(2), #4(1), #4(Liftoff!)

在任务中产生返回值

Runnable是执行工作的独立任务,但是它不返回任何值。如果希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口

Callable是一个具有类型参数的泛型,他的类型参数表示的是从call()方法 <而不是run()> 方法中返回的值。并且必须使用ExecutorService.submit()**来调用。

如果是多个任务的集合则使用invokeall批量提交所有任务

二、java多线程能带来什么好处?

1、Java Threads are lightweight compared to processes, it takes less time and resource to create a thread.

2、Threads share their parent process data and code

3、Context switching between threads is usually less expensive than between processes.

4、Thread intercommunication is relatively easy than process communication.

来自:https://www.journaldev.com/1016/java-thread-example

三、Runnable vs Thread


目录
相关文章
|
3天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
39 14
|
6天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
36 13
|
7天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
1月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
109 17
|
2月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
1月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
9月前
|
安全 Java
深入理解Java并发编程:线程安全与性能优化
【2月更文挑战第22天】在Java并发编程中,线程安全和性能优化是两个重要的主题。本文将深入探讨这两个主题,包括线程安全的基本概念,如何实现线程安全,以及如何在保证线程安全的同时进行性能优化。
70 0
|
9月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
6月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
86 1
|
7月前
|
安全 Java 开发者
Java并发编程中的线程安全问题及解决方案探讨
在Java编程中,特别是在并发编程领域,线程安全问题是开发过程中常见且关键的挑战。本文将深入探讨Java中的线程安全性,分析常见的线程安全问题,并介绍相应的解决方案,帮助开发者更好地理解和应对并发环境下的挑战。【7月更文挑战第3天】
126 0

热门文章

最新文章