先说大概有三种方法,一个是继承thread类 一个是Runable接口 另一个是继承继承Callable接口。
用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。但其实从理解来讲,如果我们需要做很多的事情时,一个事情耗时,那么其他的任务就需要等待,造成了堵塞。最明显的就是在当前界面编程模型中,界面是在主线程中的,当你在主线程处理费事的东西,那么界面就无法响应,反观我们为什么不将时间切片为一小块一小块,你用一点我用一点,这其中被其他占用的一小点时间不会对我们界面的处理造成太大的延时,而实际上是存在被占用的时间的。
换一个夸张的想法,我们有四个CPU,为什么不能两个事情一个事情占用一个cpu,这样完全没有影响。但是我们程序太多了,打开任务管理器有数十上百的程序在同时运行着,所以我们是多个程序用同一个cpu的,并且同一个程序中的多个线程都是轮流占用cpu的,当然这其中有关键的调度算法,让不是那么关键的占用少的时间,甚至是不给时间,详细的百度搜索说的比我好。
多线程的使用规则,一些基本的,更深的只能自己实际使用中发现问题时才能深刻理解:
- 如果任务不独立,拿很难使用多线程,毕竟你还是得等其他线程完成然后获取计算结果
- 如果是并行化,那么尽量在高层进行并行化,简单的例子如果循环中的事每次循环都需要开一个线程,为什么不直接在循环上开线程,因为现成的创建和取消也是费资源的,如果在更高层进行并行化。
- 合理规划线程数量,每一个独立的任务已经在单独的线程和核心上运行的时候,再想通过增加线程的数量来利用空闲的多余核心的方法就提高不了程序的性能了。
- 不要琢磨多线程执行的顺序和时间,这个东西不是那么好把握的,独立的任务才能准确一些。
- 线程公用的东西,加锁一定要谨慎,最好的情况是在线程中单独分配内存空间存储单独的变量,多线程共用的数据,加锁方式尤其关键。
讲一下进程和线程的区别: https://www.cnblogs.com/yangdy/p/5274455.html
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
java中基本多线程类是Thread,可以在其中进行多线程任务的执行。通过继承而后使用start方法调用线程类中的run开始执行
public class Test {
public static void main(String[] args)
{
//异步类的实例化 及开始执行
MyThread mt = new MyThread();
mt.start();
//main线程中的另一个任务
try
{
for (int i = 0; i < 3; i++)
{
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
class MyThread extends Thread
{
public void run()
{
try
{
for (int i = 0; i < 3; i++)
{
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
}
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
可以看到二者是交叉运行的,
另一个关键的就是输入参数和获取返回值,我们run是没有返回值的,所以获取返回值的方法是在run最后将结果保存给实例变量,然后就可以得到结果了。而输入参数的方法很多,可以在初始化class中给定,也可以通过实例变量传递
public class Test {
public static void main(String[] args)
{
//传递输入参数
MyThread mt = new MyThread(10);
mt.start();
try{
mt.join(); //阻塞 知道线程执行完成
}
catch(Exception e)
{
e.printStackTrace();
}
//显示执行的结果
System.out.println(mt.aaa);
}
}
class MyThread extends Thread
{
int aaa=0;
public MyThread(int input) {
this.aaa = input;
}
//需要执行的部分
public void run()
{
int temp = 5;
try
{
for (int i = 0; i < 10; i++)
{
Thread.sleep(100);
temp = temp + 1;
}
}
catch (Exception e)
{
e.printStackTrace();
}
//结果赋值给实例变量
aaa = aaa + temp;
}
}
//输出值
25
上文我们的一个异步线程继承于线程类,但实际的弊端很多,一个类只能继承一个类,无法再继承其他类,所以java有提供了异步的接口来解决这个问题,这个接口就是Runnable,它只有一个run()方法。具体的方法还是借用Thread类,传递一个实现了Runnable接口的实例对象,这样就会进行异步运行了。
class example {
public static void main(String[] args){
//实现了runable接口的类
MyThread myThread=new MyThread();
//初始化Thread实例
Thread thread=new Thread(myThread);
//开始运行
thread.start();
while(true)
{
try{
Thread.sleep(100);
System.out.println("Main方法在运行");
}
catch(Exception e)
{
}
}
}
}
//类 实现runable接口 包含run函数作为入口
class MyThread implements Runnable{
public void run(){
while(true){
try{
Thread.sleep(100);
System.out.println("MyThread类的run()方法在运行");
}
catch(Exception e)
{
}
}
}
}
Callable接口
可以看到一个问题 每个多线程类都是单独的,这个不利于资源的共享‘
继承Callable接口后需要实现call方法,而call方法默认是可以有返回值的,所以可以直接返回想返回的内容。
Callable接口实际上是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更加强大的功能。
- Callable可以在任务结束的时候提供一个返回值,Runnable无法提供这个功能。该方法有一个泛型返回值类型,你可以任意指定。
- Callable的call方法分可以抛出异常,而Runnable的run方法不能抛出异常。
接口的实现代码:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test
{
public static void main(String[] args)
{
//这就是基于这个框架的 所以实例化框架
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//Future类用于获取异步结果 带有泛型的
Future<String> future = threadPool.submit(new CallThread());
//阻塞的get方法 用于获取异步的结果
try {
System.out.println(future.get());
} catch (Exception e) {
} finally {
threadPool.shutdown();
}
}
}
//异步方法 返回类型为字符串
class CallThread implements Callable<String> {
@Override
// 返回类型 允许抛出异常
public String call() throws Exception {
return "Hello world"; //返回值 这里的call允许接受参数的
}
}
Futrue可以监视目标线程调用call的情况,当你调用Future的get()方法以获得结果时,当前线程就开始阻塞,直接call方法结束返回结果。 Futrue也是带有泛型<T>的
我们甚至可以创建列表存储 然后进行数组遍历
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.ArrayList;
public class Test
{
public static void main(String[] args)
{
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//创建Future数组 存储多个结果
ArrayList<Future<String>> future = new ArrayList<Future<String>>();
future.add(threadPool.submit(new CallThread()));
try {
for (Future<String> fs : future) //列表遍历 逐个获取
{
System.out.println(fs.get());
}
} catch (Exception e) {
} finally {
threadPool.shutdown();
}
}
}
总结
继承Thread类,直接调用start,需要实现run。无输入参数无返回值 但是可以使用实例变量或者构造器传递参数
class MyThread extends Thread{
String name;
//异步函数 可以有参数 无返回 例如传递参数name
public void run(){
//外部对name赋值 MyThread.name = xxxx;
}
//也可构造器进行name赋值
MyThread(String name)
{
this.name = name;
}
}
//调用
MyThread mt = new MyThread("123");
//或 不使用构造器 使用 mt.name = "123";
mt.start();
Runnable接口 是实现void run() 通过Thread调用 传参方式如上
class MyThread implements Runnable{
String name;
public void run(){
//异步部分
}
//可构造器 传递参数
}
//调用
MyThread myThread=new MyThread(); //可构造器传参数 也可以实例变量同上 不在细说
Thread thread=new Thread(myThread); //初始化Thread实例
thread.start(); //开始运行
最后的Callable是有参数有返回值,基于Executors框架的
//这就是基于这个框架的 所以实例化框架
ExecutorService threadPool = Executors.newSingleThreadExecutor();
//Future类用于获取异步结果 带有泛型的
Future<String> future = threadPool.submit(new CallThread());
//阻塞的get方法 用于获取异步的结果
future.get();
//而继承这个接口 需要规定返回值类型 实现call函数 允许抛出异常
//异步方法 返回类型为字符串
class CallThread implements Callable<String> {
@Override
// 返回类型 允许抛出异常
public String call() throws Exception {
return "Hello world"; //返回值 这里的call允许接受参数的
}
}