【Java之轨迹】多线程与线程池


1. 线程的创建

  • 继承 Thread 类,重写 run() 方法,使用 xx.start() 启动
    注意:不能使用 xx.run() 去启动线程,这只是调用了 run 方法,并没有成功启动一个线程
  • 实现 Runnable 接口,实现 run() 方法,需要 new Thread(xx).start()启动
  • 实现 Callable 接口,实现 call() 方法,有返回值,类型右泛型决定
    // 创建执行服务
    ExecutorService executorService = Executors.newFixedThreadPool(<线程数>);
    // 提交执行
    Future<String> future = executorService.submit(<Callable 的实现类实例>);
    // 获取结果
    String result = future.get();
    // 关闭服务
    executorService.shutdownNow();
    

注意: 线程调用 start() 方法后并不是就开始执行了,而是等待获取 CPU 时间片。所以线程 start() 的顺序并不等于线程执行的顺序,这也体现了线程执行的随机性,谁先得到 CPU 时间片谁就先执行


2. 线程的生命周期与监测

  • new 新建状态:当线程被创建出来时
  • runnable 就绪状态:当线程被 start() 时进入就绪状态
    • 当获取到 CPU 时间片时执行 run() 方法
    • 如果时间片不够用则重新进入 runnable 状态,再次等待时间片
    • 如果够用了执行完毕,就进入 terminated 结束状态
    • 如果中途被 sleep(time) 或者 wait(time) 了则进入 time waiting 时间等待状态
    • 如果中途被 wait() 了则进入 waiting 等待状态
  • blocked 等待锁状态:当线程抢不到锁时,被锁在外面,等待锁被释放继续进入,如果进入了则变成 runnable 就绪状态
  • waiting 等待状态:当线程调用 wait() 或 join时进入等待状态,直到被 notify() 或 notifyAll() 才会重新进入 runnable 状态
  • time waiting 时间等待状态:当线程被 sleep(time) 或者 wait(time) 后进入,直到睡眠时间结束后重新进入 runnable 状态
  • terminated 结束状态:当线程的 run() 方法执行完毕后进入结束状态

其中 locked、waitting 和 time waiting 统称为阻塞状态
不建议使用 stop() 方法让线程停止,可以使用标志位让线程主动停止,如:

@Override
public void run() {
	while(flag) {
		// ...
	}
}

public void stop(){
	flag = false;
}

3. 常用方法

1) 线程状态的监测:getState();
2) 判断一个线程是否存活:isAlive();
3) 程序中活跃的线程数:activeCount();
4) 得到当前线程:currentThread();
5) 设置一个线程为守护线程:setDaemon();(直到程序结束时才结束)

4. sleep 和 wait 的区别

  • sleep 在 Thread 类中
    wait 在 Object 类中
  • sleep 只是先停下来,并不会释放锁
    wait 会将锁让出来给别的线程
  • sleep 可以在任何代码块中使用
    wait 只能在同步方法或同步代码块中使用
    为什么 wait 只能放在同步方法或者同步代码块当中?
    
    因为 wait 之后需要由别的线程来 notify 唤醒
    如果不用同步锁上的话,很可能是 wait 还没执行,notify 就先执行了
    这样在 wait 的那个线程将不能像预期那样被唤醒
    而如果加上了锁,即使该线程还没执行到 wait 
    那么由于用的是同一把锁,notify 的那个线程还不能执行
    就只能等到该线程执行了 wait ,才能执行到 notify ,这样就能确保不出现上面的问题
    

5. yield 和 join

yield 为礼让,指一个正在执行的线程停止执行,重新出来与其他线程竞争
join 则是强行执行,在其他线程还没执行完毕时,join 可强行获得执行权


6. 线程的优先级

获取优先级
thread.getPriority();

设置优先级
thread.setPriority();

最小优先级:MIN_PRIORITY = 1
最大优先级:MAX_PRIORITY = 10
不在这个范围的优先级都会报错

优先级必须在线程启动之前设置,启动后再设置没有效果
默认的优先级都是 NORM_PRIORITY = 5
所以主线程的优先级没法修改,一直是默认优先级

注:优先级大的不一定就先执行,只是先执行的机会变大了

7. 线程同步和锁

① synchronized

同步块(可以锁任何对象)
synchronized (object) {
	// ...	
}

同步方法(默认锁的是当前对象)
public synchronized void method() {
	// ...
}
相当于
public void method() {
	synchronized (this) {
	// ...
	}
}

加锁的对象是需要增删改的

② Lock

定义 lock 锁
ReentrantLock lock = new ReentrantLock();

锁必须紧跟 try 代码块,且 unlock 要放到finally第一行,如:
public void method() {
	lock.lock();
    try {
    	// ...
    } finally {
        lock.unlock();
    }
}

synchronized 与 Lock 的对比:
synchronized 是隐式锁,出了作用域会自动释放
Lock 是显示锁,需要手动开启和关闭锁,而且它只有代码锁,没有方法锁

③ 轻量级同步机制:volatile

使得变量在多个线程之间可见,即一旦一个被 volatile 修饰的值改变了,其他线程立即就知道了,常用于标志位的改变通知其他线程。

例如可以用于一个线程在某个标识下循环做某件事,而另一个线程通过改变该标识让其停止循环

④ 锁的分类

  • 可重入锁
    synchronized 和 Lock 都是可重入锁
    指的是,如果一个线程持有一个锁的时候还能够继续成功申请该锁,称该锁是可重入的, 否则就称该锁为不可重入的

  • 死锁

    • 资源是互斥的
    • 持有资源而不释放
    • 资源不可抢占
    • 循环等待 / 相互等待

    处理方法:当需要获得多个锁时,所有线程获得锁的顺序保持一致即可

  • 乐观锁
    乐观锁指的是当多个线程同时修改一个数据时,该数据会被带上一个版本号,这时所有线程都可以同时修改这个数据,产生不同版本的数据,最后再区决定那个版本的数据被保留

  • 悲观锁
    悲观锁指的是当一个数据在被修改时,其他线程想要修改这个数据都会被阻塞,等待锁的释放

8. 线程池

线程池存放了一定数量的线程,外部只需要将 Runnable 任务放入线程池的阻塞队列中,然后各个线程会从阻塞队列中取出任务并执行

线程池的创建

ThreadPoolExecutor 的构造方法:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, 
ThreadFactory threadFactory, RejectedExecutionHandler handler)

各个参数含义:

  • corePoolSize, 指定线程池中核心线程的数量
  • maxinumPoolSize,指定线程池中最大线程数量
  • keepAliveTime,当线程池线程的数量超过 corePoolSize 时,多余的空闲线程的存活时长,即空闲-线程在多长时长内销毁
  • unit, 是 keepAliveTime 时长单位
  • workQueue,任务队列,把任务提交到该任务队列中等待执行
  • threadFactory,线程工厂,用于创建线程
  • handler 拒绝策略,当任务太多来不及处理时,如何拒绝

队列分类:

  • 直接提交队列 SynchronousQueue,该队列没有容量,有新任务时则交给线程执行,线程不够就创建新的,达到最大线程数 maxinumPoolSize 则执行拒绝策略
  • 有界任务队列 ArrayBlockingQueue,该队列需指定一个容量,当有任务要执行时,若线程数小于核心线程数 corePoolSize 则创建新线程。如果队列已满,在线程数小于最大线程数 maxinumPoolSize 时创建新的线程来执行,已满的话执行拒绝策略
  • 无界任务队列 LinkedBlockingQueue,队列没有容量,除非系统资源耗尽,否则不会存在仍无入队失败的情况。有新任务时,在线程数小于核心线程数 corePoolSize 时,创建新的线程来执行任务,大于时则加入等待队列。
  • 优先任务队列 PriorityBlockingQueue,是一个特殊的无界队列,前面两个时按照先进先出原则,而这个时按照优先级先后执行的。

9. CyclicBarrier 用法

当等待的线程达到一定数量时,才让所有线程一起执行
常用于多线程的分组计算

用法:在一个线程的某一个位置加上 cyclicBarrier.await();
则线程将会在执行到这里时被阻塞
直到被阻塞的线程达到预设数量时,全部线程的阻塞将一起被释放,如:

public static void main(String[] args) throws InterruptedException {
    CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

    for (int i = 0; i < 6; i++) {
        new Thread(()->{
            try {
                System.out.println("被阻塞之前");
                cyclicBarrier.await();
                System.out.println("阻塞释放后执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();

        Thread.sleep(500);
    }
}

10. CountDownLatch 类

该类是一个同步辅助类,用在其他线程需要执行的操作之前
在需要的线程中,某一个位置加上 await() 方法使线程阻塞,让线程一直等待
在它初始化时会设定一个默认值 n,每次调用 countDown() 方法时都会使 n 值 -1
当 n 值减到 0 时,await() 将放开阻塞,达到在等待的线程开始执行往后的任务的效果

该功能可以使用在抢票系统中
通常由很多用户过来抢票,但抢票时间还没到
这是利用这个类,将用户阻塞在抢票之前
接着使用 countDown() 模拟抢票倒计时
等到抢票时间到了,await() 阻塞将放开,达到所有用户共同抢票的效果。


点滴零星,万籁无声(IceClean)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寒冰小澈IceClean

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值