线程相关知识整理

Java 实现多线程的几种方式

  1. 继承Thread类创建线程
  2. 实现Runnable接口创建线程
  3. 实现Callable接口通过FutureTask包装器来创建Thread线程
  4. 使用ExecutorService、Callable、Future实现有返回结果的线程(线程池方式)

Thread类本质上也是实现了Runnable接口的一个实例,代表一个线程的实例。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。
继承Thread类创建线程

public class Demo1 extends Thread{
    public Demo1(String name){
        this.setName(name);
    }

    @Override
    public void run() {
        System.out.println("当前线程名"+Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        new Demo1("MyThread1").start();
        new Demo1("MyThread2").start();
    }
}

实现Runnable接口创建线程

public class Demo2 implements Runnable{

    @Override
    public void run() {
        System.out.println("当前线程名"+Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        Demo2 runnable = new Demo2();
        new Thread(runnable,"MyThread1").start();
        new Thread(runnable,"MyThread2").start();
    }
}

实现Callable接口通过FutureTask包装器来创建Thread线程

public class Demo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = () -> {
            System.out.println("线程任务开始执行了...");
            Thread.sleep(1000);
            return 0;
        };

        FutureTask<Integer> task = new FutureTask<>(callable);

        new Thread(task).start();

        System.out.println("线程启动之后,线程结果返回之前...");
        Integer result = task.get();
        System.out.println("主线程中拿到异步任务执行的结果为:" + result);
    }
}

一个线程的生命周期有哪几种状态?它们之间如何流转的?

NEW:毫无疑问表示的是刚创建的线程,还没有开始启动。

RUNNABLE: 表示线程已经触发 start()方式调用,线程正式启动,线程处于运行中状态。

BLOCKED:表示线程阻塞,等待获取锁,如碰到 synchronized、lock 等关键字等占用临界区的情况,一旦获取到锁就进行 RUNNABLE 状态继续运行。

WAITING:表示线程处于无限制等待状态,等待一个特殊的事件来重新唤醒,如通过wait()方法进行等待的线程等待一个 notify()或者 notifyAll()方法,通过 join()方法进行等待的线程等待目标线程运行结束而唤醒,一旦通过相关事件唤醒线程,线程就进入了 RUNNABLE 状态继续运行。

TIMED_WAITING:表示线程进入了一个有时限的等待,如 sleep(3000),等待 3 秒后线程重新进行 RUNNABLE 状态继续运行。

TERMINATED:表示线程执行完毕后,进行终止状态。需要注意的是,一旦线程通过 start 方法启动后就再也不能回到初始 NEW 状态,线程终止后也不能再回到 RUNNABLE 状态

sleep()方法和wait()方法的不同点

  1. wait()、notify()方法必须写在同步方法中,是为了防止死锁和永久等待,使线程更安全,而sleep()方法不需要有这个限制
  2. .wait()方法调用后会释放锁sleep()方法调用后不会释放锁。(趣记:抱着锁睡觉)
  3. sleep()方法必须要指定时间参数;wait()方法可以指定时间参数。
  4. 两个方法所属类不同,sleep()方法属于Thread类;wait()属于Object类中,放在Object类中是因为Java中每个类都可以是一把锁。

为什么必须写在同步代码块内

// 线程A 的代码
while(!condition){ // 不能使用 if , 因为存在一些特殊情况, 使得线程没有收到 notify 时也能退出等待状态
    wait();
}
// do something
// 线程 B 的代码
if(!condition){ 
	// do something ...
    condition = true;
    notify();
}

现在考虑, 如果wait() 和 notify() 的操作没有相应的同步机制, 则会发生如下情况

  1. 线程A】 进入了 while 循环后(通过了 !condition 判断条件, 但尚未执行 wait 方法), CPU 时间片耗尽, CPU 开始执行线程B的代码
  2. 线程B】 执行完毕了 condition = true; notify(); 的操作, 此时【线程A】的 wait() 操作尚未被执行, notify() 操作没有产生任何效果
  3. 线程A】执行wait() 操作, 进入等待状态,如果没有额外的 notify() 操作, 该线程将持续在 condition = true 的情形下, 持续处于等待状态得不到执行。
    由于错误的条件下进行了wait,那么就有可能永远不会被notify到,所以我们需要强制wait/notify在synchronized中

synchronized 中的 4 个优化

锁膨胀
指 synchronized 从无锁升级到偏向锁,再到轻量级锁,最后到重量级锁的过程,它叫做锁膨胀也叫做锁升级
锁消除
JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。
锁粗化
锁粗化是指,将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。
自适应自旋锁
自旋锁是指通过自身循环,尝试获取锁的一种方式.如果线程自旋成功了,则下次自旋的次数会增多,如果失败,下次自旋的次数会减少。

synchronized 与ReentrantLock

1.层面:
底层实现上来说,synchronized 是JVM层面的锁,是Java关键字,通过monitor对象来完成,对象只有在同步块或同步方法中才能调用wait/notify方法.
ReentrantLock 提供的API层面的锁。
2.实现:
synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向OS申请重量级锁;
ReentrantLock实现则是通过利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile保证数据可见性以实现锁的功能。
3.是否可手动释放:
synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用;
ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。
4.是否可中断
synchronized是不可中断类型的锁,除非加锁的代码中出现异常或正常执行完成;
ReentrantLock则可以中断,可通过设置超时方法或者调用interrupt方法进行中断。
5.是否公平锁
synchronized为非公平锁
ReentrantLock则即可以选公平锁也可以选非公平锁,通过构造方法new ReentrantLock时传入boolean值进行选择,为空默认false非公平锁,true为公平锁。
6.锁是否可绑定条件Condition
synchronized不能绑定;
ReentrantLock通过绑定Condition结合await()/singal()方法实现线程的精确唤醒.
7.锁的对象
synchronzied锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock锁的是线程,根据进入的线程和int类型的state标识锁的获得/争抢。

为什么不建议使用stop中止线程

1.调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常
2.调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
建议使用:1.使用 interrupt() 中断线程 2.使用 interrupt() 中断线程

线程池的启动流程

  1. 创建线程池,设置各个参数, 此时没有一个线程
  2. execute或submit传入一个任务,同时会唤醒休眠的线程。
  3. 先判断线程数达到最大线程数没有, 如果没有达到就新建一个线程来运行任务
  4. 如果达到最大线程数,就开始会把任务加入队列中
  5. 任务队列也加满了,就会执行拒绝策略,
  6. 当某个线程运行完任务后, 会再次从队列中获取新的任务运行。
  7. 如果队列中没有任务,线程会休眠,休眠时间是传入的时间
  8. 某个线程休眠结束后,会再次从任务队列中获取任务,如果任务队列是空的, 则判断当前存活线程数是否大于核心线程数, 如果大于则这个线程就会死亡。
  9. 如果小于或者等于最小核心线程, 就会继续休眠。

为什么不推荐使用 Executors 来创建线程

4种线程池参数配置不合理 :

  1. 线程池大小(可缓存线程池INT的最大值)
  2. 任务队列无界
  3. 定时任务的线程池(定时任务执行过长,来不及释放就会创建线程,如果前后两个任务存在锁,可能会出现死锁问题)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值