(удобнее просматривать исходный код на горизонтальном экране мобильного телефона)
проблема
(1) Каковы способы создания темы?
(2) Какие у них есть сценарии применения?
Введение
«Создание потоков — это самая основная операция в многопоточном программировании», — резюмировал брат Тонг, — «Существует около 8 способов создания потоков, знаете ли вы?»
Наследовать класс Thread и переопределить метод run().
public class CreatingThread01 extends Thread {
@Override
public void run() {
System.out.println(getName() + " is running");
}
public static void main(String[] args) {
new CreatingThread01().start();
new CreatingThread01().start();
new CreatingThread01().start();
new CreatingThread01().start();
}
}
Наследовать класс Thread и переопределить метод run(). Недостаток этого метода в том, что класс может наследовать только один родительский класс. Если сам класс унаследовал другие классы, этот метод использовать нельзя.
Реализовать интерфейс Runnable
public class CreatingThread02 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
public static void main(String[] args) {
new Thread(new CreatingThread02()).start();
new Thread(new CreatingThread02()).start();
new Thread(new CreatingThread02()).start();
new Thread(new CreatingThread02()).start();
}
}
Преимущество реализации интерфейса Runnable заключается в том, что класс может реализовать несколько интерфейсов, не затрагивая его систему наследования.
анонимный внутренний класс
public class CreatingThread03 {
public static void main(String[] args) {
// Thread匿名类,重写Thread的run()方法
new Thread() {
@Override
public void run() {
System.out.println(getName() + " is running");
}
}.start();
// Runnable匿名类,实现其run()方法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
}).start();
// 同上,使用lambda表达式函数式编程
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " is running");
}).start();
}
}
Способ использования анонимного класса: один — переписать метод run() Thread, другой — передать анонимный класс Runnable, а третий — использовать лямбда-метод.Теперь третий метод (java8+) обычно используется, что просто и быстро.
Реализовать интерфейс Callabe
public class CreatingThread04 implements Callable<Long> {
@Override
public Long call() throws Exception {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId() + " is running");
return Thread.currentThread().getId();
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Long> task = new FutureTask<>(new CreatingThread04());
new Thread(task).start();
System.out.println("等待完成任务");
Long result = task.get();
System.out.println("任务结果:" + result);
}
}
Реализуя интерфейс Callabe, можно получить результат выполнения потока, FutureTask фактически реализует интерфейс Runnable.
Таймер (java.util.Timer)
public class CreatingThread05 {
public static void main(String[] args) {
Timer timer = new Timer();
// 每隔1秒执行一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
}, 0 , 1000);
}
}
С помощью таймера java.util.Timer можно быстро реализовать временные задачи, а TimerTask фактически реализует интерфейс Runnable.
Пул потоков
public class CreatingThread06 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 100; i++) {
threadPool.execute(()-> System.out.println(Thread.currentThread().getName() + " is running"));
}
}
}
Используя пулы потоков, можно повторно использовать потоки и экономить системные ресурсы.
Параллельные вычисления (Java8+)
public class CreatingThread07 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// 串行,打印结果为12345
list.stream().forEach(System.out::print);
System.out.println();
// 并行,打印结果随机,比如35214
list.parallelStream().forEach(System.out::print);
}
}
Использование метода параллельных вычислений позволяет повысить эффективность работы программы и многопоточное параллельное выполнение.
Spring асинхронные методы
Во-первых, класс запуска springboot плюс@EnableAsync
Аннотация (@EnableAsync поддерживается Spring, вот удобный пример использования springboot).
@SpringBootApplication
@EnableAsync
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Во-вторых, метод плюс@Async
аннотация.
@Service
public class CreatingThread08Service {
@Async
public void call() {
System.out.println(Thread.currentThread().getName() + " is running");
}
}
Затем тестовый пример точно такой же, как при использовании обычного метода службы.
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class CreatingThread08Test {
@Autowired
private CreatingThread08Service creatingThread08Service;
@Test
public void test() {
creatingThread08Service.call();
creatingThread08Service.call();
creatingThread08Service.call();
creatingThread08Service.call();
}
}
Результаты приведены ниже:
task-3 is running
task-2 is running
task-1 is running
task-4 is running
Вы можете видеть, что поток, используемый каждый раз при выполнении метода, отличается.
Можно сказать, что способ использования асинхронных методов Spring достаточно удобен, и он подходит для некоторых методов, не связанных с front и back логикой и подходящих для асинхронных вызовов, таких как функция отправки коротких сообщений.
Суммировать
(1) Наследовать класс Thread и переопределить метод run();
(2) реализовать интерфейс Runnable;
(3) Анонимные внутренние классы;
(4) реализовать интерфейс Callabe;
(5) Таймер (java.util.Timer);
(6) пул потоков;
(7) параллельные вычисления (Java8+);
(8) Spring асинхронный метод;
пасхальные яйца
Выше описано так много способов создания потоков, что фактически есть два способа: один — наследовать класс Thread и переписать его метод run(), а второй — реализовать метод run() интерфейса Runnable. Какая связь?
Пожалуйста, посмотрите следующий пример, унаследуйте Thread и одновременно реализуйте интерфейс Runnable, что должно быть выведено?
public class CreatingThread09 {
public static void main(String[] args) {
new Thread(()-> {
System.out.println("Runnable: " + Thread.currentThread().getName());
}) {
@Override
public void run() {
System.out.println("Thread: " + getName());
}
}.start();
}
}
Сказав это, нам необходимо взглянуть на исходный код класса Thread:
public class Thread implements Runnable {
// Thread维护了一个Runnable的实例
private Runnable target;
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ...
// 构造方法传进来的Runnable会赋值给target
this.target = target;
// ...
}
@Override
public void run() {
// Thread默认的run()方法,如果target不为空,会执行target的run()方法
if (target != null) {
target.run();
}
}
}
Увидев здесь, вдруг не просветлел? Поскольку приведенный выше пример наследует Thread и одновременно реализует интерфейс Runnable, согласно исходному коду, это фактически эквивалентно переписыванию метода run() Thread, а метод run() Thread на самом деле не имеет ничего общего с цель.
Итак, вывод приведенного выше примера:Thread: Thread-0
, выводите содержимое только в методе run() переопределенного потока.
Добро пожаловать, чтобы обратить внимание на мою общедоступную учетную запись «Брат Тонг читает исходный код», проверить больше статей из серии исходного кода и поплавать в океане исходного кода с братом Тонгом.