- Thread 实现的四种方式
- 方式一:继承Thread类,重写run方法
- 方式二:实现Runnable接口,实现run方法
- 方式三:实现Callnable接口,实现call方法
- 方式四:利用ExecutorService线程池的方式创建线程
Thread实现了Runnable接口
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
方式一:继承Thread类,重写run方法
package com.lxk.thread1;
public class TestThread extends Thread{
@Override
public void run() {
System.out.println("今天天气好晴朗");
}
public static void main(String[] args) {
TestThread t1=new TestThread();
TestThread t2=new TestThread();
t1.start();
t2.start();
}
}
方式二:实现Runnable接口,实现run 方法(实现接口后还需要用Runnable进行封装)
package com.lxk.thread2;
public class TestThread implements Runnable{
@Override
public void run() {
System.out.println("今天天气好晴朗");
}
public static void main(String[] args) {
TestThread r=new TestThread();
Thread t1=new Thread(r);
Thread t2=new Thread(r);
t1.start();
t2.start();
}
}
方式三:实现Callnable接口,实现call方法
- Callnable接口中有声明了一个方法call方法,该方法的返回类型不是void,而是Callnable接口的类泛型T
- 因此它是有返回值的,可以获取到线程执行体的返回值
- 线程类需要实现Callnable接口,在使用的时候需要先用FutureTask类封装线程类,然后用Thread类继续封装FutureTask类,然后调用start方法.
-
package com.lxk.thread3; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class MyThread implements Callable<Integer>{ int i=0; @Override public Integer call() throws Exception { i++; return i; } public static void main(String[] args) throws Exception { Callable mt=new MyThread(); FutureTask<Integer> ft=new FutureTask<Integer>(mt); Thread t=new Thread(ft); t.start(); Integer integer = ft.get();//获取到线程执行体的返回值 System.out.println(integer); } }
方式二和方式三的区别就是:Callnable有返回值,Runnable没有返回值、
- 方式一和方式二都是没有返回值的,实际使用的时候推荐使用方式二,因为java是面向接口编程的,java是单继承多实现,面向接口编程可以更好的对类进行扩展,因此推荐使用方式二
- 方式三是由返回值的,如果需要获取到线程执行体的返回值推荐使用方式三
- 方式四是线程池的思想,线程池里面既可以放方式一和方式二创建的线程也可以放方式三创建的线程.
方式四:利用ExecutorService线程池的方式创建线程
- 该方法是jdk1.5之后才出现的,利用ExecutorService一次性创建很多个线程,在需要该线程的时候直接从该线程池中拿取就可以了.(池的概念可以类比jdbc连接池)
package com.lxk.thread4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThread implements Runnable{
public static void main(String[] args) throws Exception {
ExecutorService e=Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
Thread t=new Thread(new MyThread());
//执行线程体,那个都可以
// t.start();
// e.execute(t);
}
e.shutdown();
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("执行线程!");
}
}
- 线程池的工作主要是控制运行线程的数量,处理过程中将任务放入队列,然后线程创建后启动这些任务 。如果线程数量超过了最大线程数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
- 特点 线程复用,控制最大并发数,管理线程
第一:降低资源消耗,通过重复利用已创建的线程,降低线程创建和销毁造成的资源消耗。
第二:提交响应速度,当任务到达时,任务可以不需要等待线程创建就能执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限的创建,不仅会消耗系统的资源,还会降低系统的稳定性,使用线程池可以统一的分配,调优和监控。
1.线程的概念:是进程的实体,是CPU调度和分配比进程更小的调度单位。
- 进程:是一个具有独立功能的程序在某个数据集合的一次运行活动,是操作系统进行资源分配和调度的独立单位。
- 线程小于进程,让多进程有高并发的特性,进程在运行的时候每个单元模块相互独立,线程之间按内存共享,优化用户体验。
2.启动新线程的时候,start和run方法的区别
- run :线程对象调用run 方法不开启线程,仅仅是对象调用方法,并且run方法只是Thread的一个普通方法,在主线程中执行。
- start:对象调用start方法可以启动线程,并让jvm调用run方法在开启的线程中执行,使线程进入就绪状态。
3.线程的五种状态以及转换
- 新建--就绪--运行--阻塞--死亡
- 其中就绪状态下有三种
- 等待,wait()方法,线程等待某种工作完成
- 超时等待,sleep(),等到处理完毕后线程才会重新转入就绪状态
- 同步阻塞,因为锁被其他线程占用,所以会进入同步阻塞
4.线程相关的基本方法
- wait 线程等待,只有等等另外线程的通知或者被中断才会返回,在调用wait()方法后会释放对象的锁,所以一般用在同步方法或者同步代码块当中。
- wait 来自于Object,等待过程会释放锁,在代码块中使用,不需要捕获异常
- sleep 线程睡眠,不会释放当前占有的锁,会导致线程进入TIMED-WATIN状态。
- sleep 来自于线程,等待过程不会释放锁,在任意地方使用,需要捕获异常
- yield 线程让步,会使当前线程让出CPU执行时间片,和其他线程一起重新竞争CPU时间片,但是 有些操作系统对线程优先级不敏感。
- interrupt 线程中断,也就是给线程一个通知信号,会影响这个线程的一个中断标识位,这个线程本身不会因此而改变。
- join 等待其他线程终止,join()方法,等待其他线程终止,在当前线程中调用一个线程的join方法,当前线程会转化为阻塞状态,回到另一个线程结束,当前线程会和由阻塞变为就绪
- notify 线程唤醒,Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
5.线程池的分类
- newCachedThreadPool:创建一个可进行缓存重复利用的线程池
- newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,线程池中的线程处于一定的量,可以很好的控制线程的并发量
- newSingleThreadExecutor:创建一个使用单个 worker 线程的Executor ,以无界队列方式来运行该线程。线程池中最多执行一个线程,之后提交的线程将会排在队列中以此执行
- newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期执行
- newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
- newWorkStealingPool:创建一个带并行级别的线程池,并行级别决定了同一时刻最多有多少个线程在执行,如不传并行级别参数,将默认为当前系统的CPU个数
6.线程的核心参数
- 核心线程池大小:corePoolSize
- 线程池创建线程的最大个数:maximumPoolSize
- 空闲线程存活时间:keepAliveTime 指定的时间单位:unit
- 阻塞队列:WorkQueue:用于保存任务的阻塞队列
- 创建线程的工程类:treadeFactory
- 饱和策略(拒绝策略):handler
7.线程池原理
- 线程池首先判断核心线程池里的线程是否已经满了。如果不是,则创建一个新的工作线程来执行任务。否则进入 2.
- 判断工作队列是否已经满了,倘若还没有满,将线程放入工作队列。否则进入3.
- 判断线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行。如果线程池满了,则交给饱和策略来处理任务
8.关闭线程池?
- 可以通过shutdown 和 shutdownNow 两个方法
- 原理:遍历线程池所有线程,然后依次中断
- 1-shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有正在执行任务的线程
- 2-shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行和未执行任务的线程,并返回等待执行任务的列表
9.关于拒绝策略?
- ThreadPoolExecutor.AbortPolicy(系统默认): 丢弃任务并抛出RejectedExecutionException异常,让你感知到任务被拒绝了,我们可以根据业务逻辑选择重试或者放弃提交等策略
- ThreadPoolExecutor.DiscardPolicy: 也是丢弃任务,但是不抛出异常,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
- ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程),通常是存活时间最长的任务,它也存在一定的数据丢失风险
- ThreadPoolExecutor.CallerRunsPolicy:既不抛弃任务也不抛出异常,而是将某些任务回退到调用者,让调用者去执行它。