一、如何创建多线程?
一种是继承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接口
既然有了前面两种接口,为什么还需要第三种呢?这是因为前两种方式存在着几种缺陷,我们先来看看前面两种实现的方式,然后再来揭晓:
现在我们观察里面的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