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资源,当你发现一个业务逻辑执行效率特别低,耗时长,可以考虑使用多线程.


沧海一粟
49 声望3 粉丝

好好学习,天天向上