多线程实战(一)-基本用法与线程池用法(含代码)

多线程实战(一)-基本用法与线程池用法(含代码)_第1张图片 多线程实战(一)-基本用法与线程池用法

目录

 

一 多线程的作用

二 如何创建一个线程

1.继承Thread类

2.实现Runnable接口

三 有返回值的情况

四 线程池使用


 


 

一 多线程的作用

从一个简单的问题来进入多线程: 多线程有什么作用,用它来干什么?
(1)发挥多核CPU的优势
现在的电脑至少也都是双核的,4核、8核甚至16核的,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的”多线程”那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程”同时”运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。
(2)防止阻塞
从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

二 如何创建一个线程

一般两种
(1)继承Thread类
(2)实现Runnable接口
实现Runnable接口会好一点,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度

1.继承Thread类


来一个简单的例子看下如何使用
DemoThread

class DemoThread extends Thread {
 @Override public void run() {
 System.out.println("非主线程运行");
 } 
} 
public static void main(String[] args) 
    DemoThread thread = new DemoThread(); 
    thread .start();
    System.out.println("主线程运行");
}
​

2.实现Runnable接口

 

DemoRunnable.java

class DemoRunnable implements Runnable {
    @Override public void run() {
        System.out.println("非主线程运行");
    }
}
​
public static void main(String[] args) {
     Thread thread = new Thread(new DemoRunnable);
     thread.start();
   System.out.println("主线程运行");
​
}

三 有返回值的情况

如果多线程有返回值该如何处理呢?
对此 JDK1.5加入了Callable方法,它们的主要区别是 Callable 的 call() 方法可以返回值和抛出异常,而 Runnable 的 run() 方法没有这些功能。Callable 可以返回装载有计算结果的 Future 对象,下面我们来看下Callable如何使用
DemoCallable

四 线程池使用

JDK1.5 之后有个JUD java 并发包 提供了一个执行器,Executor为我们管理Thread对象,简化并发编程 看一个简单的使用例子:
线程池的优点

1)避免线程的创建和销毁带来的性能开销。
2)避免大量的线程间因互相抢占系统资源导致的阻塞现象。
3}能够对线程进行简单的管理并提供定时执行、间隔执行等功能。

 

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for(int i = 0;i < 5;i++) {
            executorService.execute(new LiftOff());
        }
        executorService.shutdown();
    }
}

Executor同样可以使我们可以管理异步任务的执行,而无需显示的管理线程的生命周期,是比较推荐的方法。在最后记得调用shutdown()方法 ,防止不断创建线程
线程也有不同的执行器,我们简单列举下 比较下区别

  • 1)newCachedThreadPool 是一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute() 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。

  • 2)newSingleThreadExecutor 创建是一个单线程池,也就是该线程池只有一个线程在工作,所有的任务是串行执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

  • 3)newFixedThreadPool 创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

  • 4)newScheduledThreadPool 创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

线程池返回值实例

CallableDemo.java


public class CallableDemo {
​
    public static void main(String[] args) {
        //使用线程池
        ExecutorService executor = Executors.newSingleThreadExecutor();
        //创建返回的Array
        ArrayList> results = new ArrayList>();
        for (int i = 0; i < 10;i++) {
            results.add(executor.submit(new TaskWithResult(i)));
        }
        for(Future fs : results){
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                executor.shutdown();
            }
        }
​
    }
}
TaskWithResult.java
​
public class TaskWithResult implements Callable{
​
    private int id;
​
    public TaskWithResult(int id) {
        this.id = id;
    }
​
    @Override
    public String call() throws Exception {
        return "result of TaskWithResult " + id;
    }
}
​

注意调用的方法是 ExecutorService.submit()。

你可能感兴趣的:(java)