线程实现四种方式
-
继承Thread类
-
实现Runnable接口,重写Run方法
-
实现Callable接口,重写Call方法,此方式有返回值
-
利用线程池创建
如何启动线程
第一种方式,直接new对象,调用对象的start()方法
第二种方式,new 一个实现了Runnable接口的类,将这个对象作为Thread的有参构造方法的参数传入,调用Thread对象的start()方法
第三种方式, new 一个实现了Callable的接口的类,将这个对象作为FutureTask有参构造的参数传入,再把这个FutureTask对象作为Thread的有参构造方法的参数传入,调用Thread对象的start()方法
线程的生命周期:
1.新建状态:使用new之后,线程就处于新建状态
2.就绪状态:当调用了start方法之后,线程就处于就绪状态状态
3.运行状态:如果就绪状态的线程获取到cpu资源,就会处于运行状态,此时线程最为复杂,可以变为就绪状态,阻塞状态,死亡状态
4.阻塞状态:当一个线程执行了sleep,wait等方法,就处于阻塞状态
5.死亡状态:一个运行完的线程或其他终止 条件时,该线程就处于死亡状态
锁的问题
当线程开始多了起来就称为多线程,多线程一旦出现,那就出现线程安全问题,那么锁就用来解决线程安全问题
锁按照宏观分类,分为乐观锁和悲观锁。
乐观锁:指的是当一个线程执行时,他认为别的线程必定不会去执行,也就是说别的线程必定不会影响他,但是,他在最后会确认数据是否被修改。
举例:CAS ,Atomic,数据库可以设置version防止二类丢失更新,es的version
悲观锁:指的是一个线程运行时,他认为别的线程必定会执行,所以他会把其加锁,不让别的线程执行。
举例:synchronized ReentrantLock
synchronized用法:
在相应的代码块上加synchronized(){
}
()里面的参数可以是对象,也可以是字节码,无论是什么,都必须满足只有一份的情况。
()通常是this,或者是当前类的字节码
public class SyTest implements Runnable{
private int a = 100;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (this) {
if (a > 0) {
System.out.println(Thread.currentThread().getName() + "打印一手" + a--);
}
}
}
}
public static void main(String[] args) {
SyTest syTest = new SyTest();
new Thread(syTest).start();
new Thread(syTest).start();
new Thread(syTest).start();
}
}
ReentrantLock用法:
实列化lock对象,(程序中只有一份)在会产生线程安全的代码前加上锁,在finally代码块中解除锁
public class LockTest implements Runnable{
private int a = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try{
lock.lock();
if (a > 0) {
System.out.println(Thread.currentThread().getName() + "打印一手" + a--);
}
}finally {
if(lock.isLocked()){
lock.unlock();
}
}
}
}
public static void main(String[] args) {
LockTest syTest = new LockTest();
new Thread(syTest).start();
new Thread(syTest).start();
new Thread(syTest).start();
}
}
atomic基于CAS乐观锁实现
public class AtomicTest implements Runnable{
private AtomicInteger a = new AtomicInteger(100);
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (a.get()> 0) {
System.out.println(Thread.currentThread().getName() + "打印一手" + a.getAndDecrement());
}
}
}
public static void main(String[] args) {
AtomicTest syTest = new AtomicTest();
new Thread(syTest).start();
new Thread(syTest).start();
new Thread(syTest).start();
}
}
利用ThreadLocal实现线程安全
public class ThreadLocalTest{
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
//主线程设置值
threadLocal.set("main-value");
new Thread(()->{
System.out.println("thread线程获取:"+threadLocal.get()); //打印null,新开的线程是获取不到主线程设置的值的
threadLocal.set("thread-value");
System.out.println("thread线程获取:"+threadLocal.get()); //打印thread-value
}).start();
//主线程设置值
System.out.println("main线程获取:"+threadLocal.get()); //打印 main-value
}
}
线程池
*线程池执行流程 :* 核心线程 => 等待队列 => 非核心线程 => 拒绝策略
PS: 线程池7个参数的构造器非常重要[重要]
CorePoolSize: 核心线程数,不会被销毁
MaximumPoolSize : 最大线程数 (核心+非核心) ,非核心线程数用完之后达到空闲时间会被销毁
KeepAliveTime: 非核心线程的最大空闲时间,到了这个空闲时间没被使用,非核心线程销毁
Unit: 空闲时间单位
WorkQueue:是一个BlockingQueue阻塞队列,超过核心线程数的任务会进入队列排队
SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务;
LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小
ThreadFactory:使用ThreadFactory创建新线程。 推荐使用Executors.defaultThreadFactory
Handler: 拒绝策略,任务超过 最大线程数+队列排队数 ,多出来的任务该如何处理取决于Handler
AbortPolicy丢弃任务并抛出RejectedExecutionException异常;
DiscardPolicy丢弃任务,但是不抛出异常;
DiscardOldestPolicy丢弃队列最前面的任务,然后重新尝试执行任务;
CallerRunsPolicy由调用线程处理该任务
可以定义和使用其他种类的RejectedExecutionHandler类来定义拒绝策略。、
常见线程池有四种
CachedThreadPool:可缓存
FixedThreadPool :固定长度
SingleThreadPool:单个
ScheduledThreadPool:可调度
1.CachedThreadPool
这种线程池内部没有核心线程,线程的数量是有限制的 最大是Integer最大值。
在创建任务时,若有空闲的线程时则复用空闲的线程(缓存线程),若没有则新建线程。
没有工作的线程(闲置状态)在超过了60S还不做事,就会销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器。
public class cachedThread {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行");
}
});
}
}
}
FixedThreadPool :固定长度
该线程池的最大线程数等于核心线程数,所以在默认情况下,该线程池的线程不会因为闲置状态超时而被销毁。
如果当前线程数小于核心线程数,并且也有闲置线程的时候提交了任务,这时也不会去复用之前的闲置线程,会创建新的线程去执行任务(必须达到最大核心数才会复用线程)。如果当前执行任务数大于了核心线程数,大于的部分就会进入队列等待。等着有闲置的线程来执行这个任务。
适用:执行长期的任务,性能好很多
public class FixedThread {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 100; i++) {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行");
}
});
}
}
}
SingleThreadPool:单个
有且仅有一个工作线程执行任务
所有任务按照指定顺序执行,即遵循队列的入队出队规则。
适用:一个任务一个任务执行的场景。 如同队列
public class singleThread {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 100; i++) {
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行");
}
});
}
}
}
ScheduledThreadPool:可调度
DEFAULT_KEEPALIVE_MILLIS就是默认10L,这里就是10秒。这个线程池有点像是CachedThreadPool和FixedThreadPool 结合了一下。
不仅设置了核心线程数,最大线程数也是Integer.MAX_VALUE。
这个线程池是上述4个中唯一一个有延迟执行和周期执行任务的线程池。
适用:周期性执行任务的场景(定期的同步数据)
public class ScheduledThread {
public static void main(String[] args) {
//带缓存的线程,线程复用,没有核心线程,线程的最大值是 Integer.MAX_VALUE
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
//延迟 n 时间后,执行一次,延迟任务
// executorService.schedule(new Runnable() {
// @Override
// public void run() {
// System.out.println("延迟任务执行.....");
// }
// },10, TimeUnit.SECONDS);
//定时任务,固定 N 时间执行一次 ,按照上一次任务的开始执行时间计算下一次任务开始时间
// executorService.scheduleAtFixedRate(()->{
// System.out.println("定时任务 scheduleAtFixedRate 执行 time:"+System.currentTimeMillis());
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// },1,1, TimeUnit.SECONDS);
//定时任务,固定 N 时间执行一次 ,按照上一次任务的结束时间计算下一次任务开始时间
executorService.scheduleWithFixedDelay(()->{
System.out.println("定时任务 scheduleWithFixedDelay 执行 time:"+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},1,1,TimeUnit.SECONDS);
}
}