Java多线程的实现
用多线程只有一个目的:更好的利用cpu资源.烧水的例子.(当洗杯子花5分钟,线程要停5分钟等待返回结果才能进行后续的烧水操作,新开一个线程执行洗杯子操作)。
一、关于线程的一些概念
- cpu时间片:我们操作系统看起来可以多个程序同时运行.分时操作系统,将时间分成长短相同的时间区域,分配给一个线程使用,当线程还没有结束,时间片已经过去,该线程只有先停止,等待下一个时间片.cpu运行很快,中间的停顿感觉不出来.
- 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程(比如,下载程序,开启多个线程同时进行.)
- 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
- 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力.
- 线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果.
- Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能.
二、Java多线程的实现
1、继承Thread类创建线程
Thread类本质上是实现了Runnable接口,启动该线程的唯一方法是start()方法,
public class MyThread extends Thread{
//普通的调用方法,定义任务要完成的工作.
@Override
public void run() {
System.out.println("新线程正在执行,处理相关的逻辑!");
}
}
public class Test {
public static void main(String[] args) {
//实例化对象
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
//开启新的线程,分配新的资源
myThread1.start();
myThread2.start();
}
}
2、实现Runnable接口创建线程
java中是单继承的,如果继承了一个类,就不能直接继承Thread类,需要实现Runnable接口的方式达到开启新线程的目的.
public class MyThread implements Runnable {
//普通的调用方法,定义任务要完成的工作.
@Override
public void run() {
System.out.println("新线程正在执行,处理相关的逻辑!");
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread =new Thread(myThread);
//开启新的线程,分配新的资源
thread.start();
}
}
3、实现Callable接口
Callable接口的call()方法类似run()方法,都是定义任务要完成的工作.主要不同点是call()方法是有返回值的、可以抛出异常。Callable类型的任务可以有两种方法开启执行.
方法一:借助FutureTask执行(FutureTask、Callable)
将Callable接口对象放到FutureTask对象中,FutureTask的get()方法,可以获取返回值.
public class MyCallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("新线程正在执行,处理相关的逻辑!");
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++) {
sum += i;
}
return sum;
}
}
public class Test {
public static void main(String[] args) {
Callable<Integer> mycallabletask = new MyCallableTask();
//由Callable<Integer>创建一个FutureTask<Integer>对象:
FutureTask<Integer> futuretask = new FutureTask<Integer>(mycallabletask);
//注释:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future 和Runnable接口。
//由FutureTask<Integer>创建一个Thread对象:
Thread oneThread = new Thread(futuretask);
oneThread.start();
try {
//通过futuretask中get()方法可以得到MyCallableTask的call()运行结果.
//需要使用时获取出来,否则出现堵塞,本线程要等待新线程执行完返回结果才执行
Integer i = futuretask.get();
} catch (Exception e) {
e.printStackTrace();
}
}
}
方法二:借助线程池来运行 (ExecutorService、Callable、Future)
ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get()就可以获取到Callable任务返回的Object了。
public class MyCallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("新线程正在执行,处理相关的逻辑!");
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++) {
sum += i;
}
return sum;
}
}
public class Test {
public static void main(String[] args) {
int taskSize = 5;
//创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool(taskSize);
//提交一个Callable任务,返回一个Future类型
Future<Integer> future = threadPool.submit(new MyCallableTask());
try {
Thread.sleep(3000);//模拟本线程的一些任务
//获取执行结果get方法是阻塞的
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
采用匿名类直接新建Callable接口
public class Test{
public static void main(String[] args) {
// 创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
// 提交一个Callable任务,返回一个Future类型
Future<Integer> future = threadPool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("新线程正在执行,处理相关的逻辑!");
Thread.sleep(3000);
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
});
try {
Thread.sleep(3000);//模拟本线程的一些任务
//获取执行结果
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、使用场景
一、Tomcat内部采用了多线程,上百个用户同时访问同一个web应用,都会新开一个线程,调用到Servlet程序。如果不使用多线程,将串行操作,客户端将等待别人执行完才能访问。
二、异步请求,有两个任务Task a和Task b,单线程只能先进行a再进行b。
三、需要知道执行进度,比如说我们常看到的进度条,任务执行到一定进度给new 一个变量,给变量+1.新开一个线程去轮询这个变量,反馈给客户端,这样就可以看到进度情况.
总之,很多地方都用到了多线程,多线程是为了充分利用cpu资源,当你发现一个业务逻辑执行效率特别低,耗时长,可以考虑使用多线程.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。