多线程基础学习 多线程的好处 简单介绍 任务实现 线程池的使用 线程方法

1、并行编程可以使程序执行速度极大的提高,java本身是一种多线程语言。

2、使用多线程可以利用机器额外的处理器,资源充分利用。

简单介绍

java的线程机制是抢占式的,这表示调度机制会周期性的中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合

理的时间去驱动它的任务,并发编程使我们可以将程序划分为多个分离的、独立运行的任务。

任务实现

通过实现Runnable接口实现线程:

public class LiftOff implements Runnable{

//打印100以内的数
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}

当从Runnable导出一个类时,必须具有run()方法,但是这个方法并无特殊之处————它不会产生任何内在的县城能力,要实现线程行为,你必须显式地将一个任务附着到线程上。
将Runnable类提交给Thread的构造器,通过Thrad.start()启动线程,调用之后主线程立即返回执行下面的打印逻辑,启动的新线程再执行它的run()方法。

public class TestThread {
public static void main(String[] args) {
Thread t = new Thread(new LiftOff());
t.start();
System.out.println("waiting for thread !");
}

}

通过集成Thread类来实现线程:

public class SimpleThread extends Thread {
private int countDown = 5;
private static int threadCount = 0;

public SimpleThread() {
super(Integer.toString(++threadCount)); //通过构造器为线程赋名,改名字可以通过Thread.getName()获得
start();
}

@Override
public String toString() {
return "#" + getName() + "(" + countDown + ") .";
}

@Override
public void run() {
while (true){
System.out.println(this);
if(--countDown == 0) return;
}
}

public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new SimpleThread();
}
}
}






通过实现Callable接口实现线程:

如果希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。

public class TaskWithResult implements Callable<String> {

private int id;

public TaskWithResult(int id) {
this.id = id;
}

@Override
public String call() throws Exception {
return "result of TaskWithResult is " + id ;
}
}
测试主类:
public class TestCallThread {

public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<>();

for (int i = 0; i < 10; i++) {
results.add(executorService.submit(new TaskWithResult(i)));
}

for (Future<String> fs : results) {
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
System.out.println(e);
return;
} catch (ExecutionException e) {
e.printStackTrace();
return;
}finally {
executorService.shutdown();
}
}
}
}

ExecutorService.add()方法添加一个任务之后,会返回一个Future,通过Future的Future.isDone()可以查询任务是否已经完成,如果完成了可以调用Future.get(),此时get()方法会阻塞,直至结果准备就绪。

线程池的使用

为什么使用线程池:

1、每次new Thread() 新建对象,性能差;

2、线程缺乏统一的管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM(内存溢出);

3、缺少更多的功能,如更多执行,定期执行,线程中断。

线程池的好处:

1、重用 存在的线程,减少对象的创建、消亡的开销,性能佳;

2、可有效的控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞;

3、提供定时执行,定期执行,单线程,并发控制等高级功能。

在实际多线程开发中,使用线程池管理线程是优选方法。

线程池的类:

ThreadPoolExecutor

三个重要参数:

corePoolSize:核心线程数量; 线程数少于该值,创建新线程,即使存在空闲线程。

maximumPoolSize:线程最大线程数量;线程数少于该值,且大于corePoolSize,只有当阻塞队列满的时候会创建线程。

workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。当线程达到最大线程数量时,新提交的任务会添加到阻塞队列里,当阻塞队列满的时候,会使用下文的拒绝策略。

keepAliveTime:线程没有任务执行时最多保持多久时间终止。当线程数量大于核心线程数的时候,如果一个线程超过这个时间,没有任务提交,线程即销毁。

unit:keepAliveTime的时间单位。

ThreadFactory:线程工厂,用来创建线程,默认会使用默认的线程工厂来创建线程,使用默认的线程工厂创建线程的时候,线程具有相同的优先级,非守护的线程,具有线程名称的线程。

rejecthandler:当拒绝处理任务时的策略。

a、直接抛出异常(默认策略)(AbortPolicy)

b、用调用者的线程处理任务  (CallerRunsPolicy)

c、丢弃队列中最靠前的任务。(DiscardOldestPolicy)

d、直接丢弃任务。(DiscardPolicy)

如果想使cpu有一个合理的利用:建议设置较大的阻塞队列大小,较小的线程池容量。


public class ExecutorDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new LiftOff());
executorService.shutdown();  //使线程池继续执行已经提交的任务,但是不再接受新任务
}
}

几种不同的Executor:

  • FixedThreadPool:一次性预先的执行代价高昂的线程分配,因而也就限制了线程的数量了。
    • 超出的数量会在队列中等待
  • CachedThraedPool: 这种执行器在执行过程中通常会创建于所需数量相同的线程,然后在他回收旧线程时停止创建新线程,因此他是合理的Executor的首选。只有当这种方式会引发问题时,你才需要切换到FixedThreadPool;
    • Executors.newCachedThreadPool()–>可缓存的线程池
  • SingleThreadPool:线程数量为1的FixedThradPool,这种线程池,如果提交了多个任务,每个任务都会在下一个任务开始之前运行结束,所有的任务将使用相同的线程每个任务都是按照他们被提交的顺序,并且是在下一个任务开始之前完成的。SingleThreadExecutor会序列化所有提交给他的任务,并会维护它自己的(隐藏)的悬挂任务队列。
  • ScheduledThreadPool:也是定长的线程池,支持定时,周期性的任务执行

用法:

//延迟3秒执行
public static void main(String[] args) {
//定义一个实例
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello!");
}
},3,TimeUnit.SECONDS);
}

//延迟1秒,每隔3秒执行一次

public static void main(String[] args) {
//定义一个实例
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("hello!");
}
},1,3,TimeUnit.SECONDS);
}




线程池的几种状态:

多线程基础学习
多线程的好处
简单介绍
任务实现
线程池的使用
线程方法

1、Running:接受新提交的任务,并且也能处理阻塞队列当中的任务;

2、ShutDown:不能再接受新提交的任务,但能提交阻塞队列保存的任务;

3、Stop:不再接受新提交的任务,也不处理阻塞队列中的任务;

4、Tidying:所有的线程终止,线程池中的工作线程数量为0;

5、Terminated:

线程池重要方法:

1、execute():提交任务,交给线程池执行。

2、submit():提交任务,能够返回执行结果,execute+Future。

3、shutDown():关闭线程,等待任务都执行完。

4、shutDownNow():关闭线程,不等待任务执行完,立即关闭。

以下几个方法可以对线程的实例进行监控:

5、getTaskCount():线程池已执行和未执行的任务总数

6、getCompleteTaskCount():已完成的任务数量

7、getPoolSize():线程池当前的线程数量

8、getActiveCount():当前线程池中正在执行任务的线程数量

线程池的合理配置:

1、CPU密集型任务:需要尽量压榨CPU,参考值可以设为N(CPU + 1);

2、IO密集型任务,参考值可以设置为2*N(CPU);




线程方法

休眠    Thread.sleep()

该方法使线程中止执行给定的时间。

设置优先级    Thread.setPriority()

线程的优先级将该线程的重要性传递给了调度器。尽管cpu处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先级最高的线程先执行。

public class SimplePriorities implements Runnable {

private int countDown = 5;
private volatile double d ;
private int priority;


public SimplePriorities(int priority) {
this.priority = priority;
}

@Override
public String toString() {
return Thread.currentThread() + ": " + countDown;
}

@Override
public void run() {
Thread.currentThread().setPriority(priority);
while (true){
for (int i = 0; i < 100000; i++) {
d += (Math.PI + Math.E) / (double)i;
if(i % 1000 == 0){
Thread.yield();
}
System.out.println(this);
if(--countDown == 0) return;
}
}
}
}
由于考虑到线程级别与操作系统兼容映射的问题,建议调整优先级的时候,只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY三种级别。

让步  Thread.yield()

该方法使建议具有相同优先级的其他线程可以运行,但是建议不一定被采用,继续执行的可能还是这个线程。

后台线程(守护线程)

后台线程(守护线程)是指在程序运行的时候在后台提供一种通用的服务的线程,并且这种线程并不属于程序中不可或缺的部分。

特点:非守护线程全部结束时,程序终止,同时会杀死进程中所有的后台线程。

必须在启动线程之前调用 Thread.setDaemon()方法,才能把它设置为守护线程。

isDaemon()可以用来判断一个线程是否是守护线程,如果是一个守护线程,那么他创建的所有线程都是守护线程。

当非守护线程结束之后,会立即关闭正在执行的守护线程,从而不会按照既定的逻辑再次往下走,比如try…catch(){}finally{}  fianlly中的语句也不会再终止时执行。