java-线程

1.创建线程的方式及实现
  1. 继承Thread类,复写run()方法,创建该类的对象,调用start方法开启线程。此法没有返回值。
  2. 实现Runnable接口,复写run方法,创建Thread类对象,将Runnable子类对象传递给Thread类对象。调用start方法开启线程。此方法将线程对象和线程任务对象分开,降低了耦合性,利于维护。此法无返回值。
  3. 创建FutureTask对象,创建Callable子类对象,复写call()方法(相当于run()方法),将其传递给FutureTask对象(相当于一个Runnable)。创建thread类对象,将FutureTask对象传递给Thread对象。调用start方法开启线程。这种方法可以获得线程执行之后的返回值。该方法使用Runnable功能更强大的子类,这个子类是具有返回值类型的任务方法。
  4. 线程池
    创建了线程队列,队列中保存着所有等待的线程。避免了创建和销毁额外开销。提高了响应速度。
    线程池的体系结构:java.util.concurrent.Executor:负责线程的使用与调度的根接口
ExecutorService 子接口线程池的主要接口
ThreadPoolExecutor线程池的实现类
ScheduledExecutorService 子接口负责线程的调度
ScheduledThreadPoolExecutor继承ThreadPoolExecutor,实现ScheduledExecutorService
// java.util.concurrent 并发工具包,包含许多线程安全,测试良好,高性能的并发构建块。实现conllection框架对数据结构所执行的并发操作,提高并发类的线程安全,可伸缩性、性能、可读性和可靠性。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

public class NewThreadDemo {

    public static void main(String[] args) throws Exception {
        
        //第一种方式
        Thread t1 = new Thread(){   //新建线程对象
            @Override
            public void run() {
                System.out.println("第1种方式:new Thread 1");
            }
        };
        t1.start();  //进入就绪状态
        
        TimeUnit.SECONDS.sleep(1);  
        
        //第二种方式
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {  //运行状态
                System.out.println("第2种方式:new Thread 2");
            }
        });
        t2.start();

        TimeUnit.SECONDS.sleep(1);
        
        
        //第三种方式
        FutureTask<String> ft = new FutureTask<>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                String result = "第3种方式:new Thread 3";
                return result;
            }
        });
        Thread t3 = new Thread(ft);
        t3.start();
        
        // 线程执行完,才会执行get(),所以FutureTask也可以用于闭锁
        String result = ft.get();
        System.out.println(result);
        
        TimeUnit.SECONDS.sleep(1);
        
         //第四种方式
        ExecutorService pool = Executors.newFixedThreadPool(5);

        Future<String> future = pool.submit(new Callable<String>(){
            @Override
            public String call() throws Exception {
                String result = "第4种方式:new Thread 4";
                return result;
            }
        });

        pool.shutdown();
        System.out.println(future.get());
    }
}

https://www.jianshu.com/p/2f8198373ff8

2.sleep()、join()、yield()区别(线程间协作的方法)

Thread.yield() 让当前线程让出持有的锁,让包含自己在内的其他线程去争抢锁,自己也有抢到锁的可能性。
Thread.sleep(0) 只是短暂的休眠,让出CPU使用权,并不会让出锁,依然是锁的持有者,阻塞状态。
Thread.join() 会让调用当前线程的线程处于阻塞状态,直到当前线程执行完毕。
Object.wait() 线程挂起,会释放对象锁
Object.notify() 线程恢复

3.CountDownLatch原理 (倒计时锁存器)

CountDownLatch四大并发工具之一。

AQS 队列同步器 abstract queued synchronizer,是构造器或其他同步组件的基础框架(如ReentrantLock、ReentReadWriteLock重入锁、Semaphore、CountDownLatch等。
AQS定义两种资源共享方式:独占,只有一个线程能执行,ReentrantLock/共享,多个线程可以同时执行,如Semaphore,CountDownLatch.
参考链接 https://blog.csdn.net/qq_31957747/article/details/74910939

一. 实现 1.Sync
2. await
3 await
4. CountDown
5 GetCount.
二. CDL内部使用共享锁实现。
用给定的计数初始化CountDownLatch。调用了CountDown方法,在当前计数到达零之前,await方法一直受阻。之后,会释放所有等待的线程。await所有的后续调用都将立即返回。这种现象只出现一次——技术无法被重置。如需重新计数,需考虑CyclicBarrier.

4.CyclicBarrier原理(循环屏障)

两者的比较:

  1. CDL允许一个或N个线程等待其他线程完成操作。CyclicBarrier允许N个线程互相等待。
  2. CDL的计数器无法被重置,CyclicBarrier的计数器可以被重置后使用,它被成为是循环的Barrier。
    CyclicBarrier是通过独占锁实现的。在这里插入图片描述
4.Semaphore原理(信号量)

在JDK1.5被引入,可以控制同时访问特定资源的线程数量,通过协调各个线程,保证资源的合理使用。
semaphore内部维护了一组虚拟的许可,许可的数量可以通过构造函数的参数指定。

访问特定资源前,必须使用acquire方法获得许可,如果许可数量为0,则线程处于阻塞状态,直到有可用许可。
访问资源后使用release释放许可。
获取许可有公平策略和非公平策略,默认使用非公平策略。内部是基于Java的AQS实现的。
应用场景:流量分流,特别是对公共资源有限的场景,比如数据库连接。

5.说说Exchanger原理

可以在对中对数据进行交换和配对的线程的同步点。每个线程将条目上的某个方法呈现给exchange方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。
Exchanger允许并发任务之间进行数据交换,Exchanger类允许在两个线程之间定义同步点。当两个线程都进入到同步点时,他们交换数据。第一个线程的数据结构进入到第二个线程,第二个线程的数据结构进入到第一个线程。

6.说说CountDownLatch和CyclicBarrier区别
CDLCCB
允许一个或N个线程等待其他线程操作完成允许N个线程互相等待
CDL的计数器不能被重置CCB的计数器可以被重置后使用
内部通过共享锁实现通过独占锁实现
7.ThreadLocal原理分析

ThreadLocal是线程局部变量,它保证访问的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将线程和变量绑定在一起。

8.线程池实现原理
  1. 核心线程池----阻塞队列—线程池—按照包和策略进行处理
    首先判断核心线程池中的线程是否都在执行任务。若不是,则新建一个线程执行当前的任务。若是,则判断阻塞队列是否已满。若不是,则将当前任务加入到阻塞队列中。若是,则判断线程池中的所有线程是否都在执行任务。若不是,则新建一个线程执行当前任务。若是,则按照饱和策略进行处理。
  2. 创建线程池主要是通过ThreadPoolExecutor类来完成。他的构造方法有 corePoolSize,workQueue,maximumPoolSize,keepAliveTime,unit,handler阻塞队列,threadFactory创建线程的工程类。
    参考链接 https://juejin.im/post/5aeec0106fb9a07ab379574f
9.线程池的几种方式

ExecutorService是线程池接口。

  1. newCachedThreadPool
  • 底层:返回ThreadPoolExecutor实例,corePoolSize为0。当有新任务到来,则插入到SynchronousQueue(同步队列)中,由于Synchronous是同步队列,因此会在池中寻找可用线程来执行,若有可执行线程则执行,若无可执行线程则创建一个线程来执行该任务。若池中线程空闲时间超过指定大小, 则该线程会被销毁。
  • 适用:很多短期异步的小程序或者负载较轻的服务器。
  1. newFixeThreadPool:
  • 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nthread。创建可容纳固定线程数量的池子,没给现成的存活时间是无限的。池子满了就不再添加线程了。
    如果池中的所有线程都是繁忙状态,对于新任务会进入阻塞队列(无界的阻塞队列)。但是,在线程池空闲时,即线程池没有可 运行任务时,它不会释放工作线程,而是会占用一定的系统资源。
  • 适用:执行长期的任务,性能会好很多。
  1. newSingleThreadExrcutor:
  • 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1。创建只有一个线程的线程池,且线程的存活时间是无限的。当该线程繁忙时,对于新任务会进入阻塞队列(误解阻塞队列)。
  • 适用于:一个任务一个任务执行的场景。
  1. newScheduledThreadPool:
  • 底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数。创建一个指定大小的线程池,线程池内线程存活时间无限制。线程池可以支持定时及周期性执行任务。如果所有线程处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构。
  • 适用:周期性执行任务的场景。
10.线程的生命周期

当线程被创建并启动后,不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,要经过New新建,Runnable就绪,Running运行,Blocked阻塞,Dead死亡五种状态。线程执行后不可能一直独占CPU资源,所以CPU要在多条线程之间切换。于是,线程状态也多次在运行和阻塞之间切换。
https://juejin.im/post/5a72d4bd518825735300f37b#heading-16

11. java多线程的使用场景
  1. 程序包含复杂的计算任务时,主要利用多线获得更多的CPU时间。
  1. 将一个任务分为多个子任务。一些子任务是可以并发执行的,可以避免CPU需要IO操作的完成,并且提高系统的吞吐量。
  2. 缓存多线程的共享数据。
  1. 处理速度较慢的外围设备
  1. 比如连接多台打印机,网络程序,涉及数据包的收发,时间因素不确定。使用单独的线程处理这些任务,可是程序无需专门等待结果。
  1. 程序设计自身的需要

操作系统是基于消息循环的抢占式多任务系统,为使消息循环系统不至于阻塞,程序需要多个线程处理这些任务,可是程序无需处理等待结果。

12.多线程的三大特性
  1. 原子性
    即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
  2. 可见性
    当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
    若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
  3. 有序性
    程序执行的顺序按照代码的先后顺序执行。 一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
13.线程安全
  1. 为什么有线程安全问题?
    当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
  2. 什么是多线程同步
    答: 当多个线程共享同一个资源,不会受到其他线程的干扰。
  3. 如何解决多线程之间线程安全问题
    答: 使用多线程之间同步synchronized或使用锁(lock)。
  4. 为什么使用线程同步或使用锁能解决线程安全问题呢?
    答: 将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进 行执行。这样的话就可以解决线程不安全问题。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值