温馨提示:距离2024年结束还剩18天,剩余约为4.92%...

转载

Java中关于并发编程的问题

1.介绍一下线程池?

  1. 线程池就是预先创建一些线程,它们的集合称为线程池。
  2. 线程池可以很好地提高性能,在系统启动时即创建大量空闲的线程,程序将一个task给到线程池,线程池就会启动一条线程来执行这个任务,执行结束后,该线程不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
  3. 线程的创建和销毁比较消耗时间,线程池可以避免这个问题。
  4. Executors是jdk1.5之后的一个新类,提供了一些静态方法,帮助我们方便的生成一些常见的线程池
    • newSingleThreadExecutor:创建一个单线程化的Executor。
    • newFixedThreadPool:创建一个固定大小的线程池。
    • newCachedThreadPool:创建一个可缓存的线程池
    • newScheduleThreadPool:创建一个定长的线程池,可以周期性执行任务。
  5. 我们还可以使用ThreadPoolExecutor自己定义线程池,弄懂它的构造参数即可:
    • int corePoolSize,//核心池的大小
    • int maximumPoolSize,//线程池最大线程数
    • long keepAliveTime,//保持时间/额外线程的存活时间
    • TimeUnit unit,//时间单位
    • BlockingQueue Runnable workQueue,//任务队列
    • ThreadFactory threadFactory,//线程工厂
    • RejectedExecutionHandler handler //异常的捕捉器

简要回答:

  1. 线程池就是预先创建一些线程
  2. 线程池可以很好地提高性能
  3. 线程池可以避免线程的频繁创建和销毁
  4. Executors可以创建常见的4种线程(单线程池、固定大小的、可缓存的、可周期性执行任务的)。
  5. 可以通过ThreadPoolExecutor自己定义线程池。

2.Java线程的状态或者生命周期?

  1. Java的线程状态被定义在公共枚举类java.lang.Thread.state中。一种有六种状态:
    • 新建(NEW):表示线程新建出来还没有被启动的状态,比如:Thread t = new MyThread();
    • 就绪/运行(RUNNABLE):该状态包含了经典线程模型的两种状态:就绪(Ready)、运行(Running):
    • 阻塞(BLOCKED):通常与锁有关系,表示线程正在获取有锁控制的资源,比如进入synchronized代码块,获取ReentryLock等;发起阻塞式IO也会阻塞,比如字符流字节流操作。
    • 等待(WAITING):线程在等待某种资源就绪。
    • 超时等待(TIMED_WAIT):线程进入条件和等待类似,但是它调用的是带有超时时间的方法。
    • 终止(TERMINATED):线程正常退出或异常退出后,就处于终结状态。也可以叫线程的死亡。
  2. 经典线程模型包含5种状态,:新建、就绪、运行、等待、退出
  3. 经典线程的就绪、运行,在java里面统一叫RUNNABLE
  4. Java的BLOCKED、WAITING、TIMED_WAITING都属于传统模型的等待状态。

3.synchronized 与lock区别?

  1. ock是一个接口,而synchronized是java的一个关键字。
  2. synchronized异常会释放锁,lock异常不会释放,所以一般try catch包起来,finally中写入unlock,避免死锁。
  3. Lock可以提高多个线程进行读操作的效率
  4. synchronized关键字,可以放代码块,实例方法,静态方法,类上
  5. lock一般使用ReentrantLock类做为锁,配合lock()和unlock()方法。在finally块中写unlock()以防死锁。
  6. jdk1.6之前synchronized低效。jdk1.6之后synchronized高效。

4.synchronized 与volatile区别?

  1. volatile是一个类型修饰符(type specifier)。
  2. volatile,它能够使变量在值发生改变时能尽快地让其他线程知道。
  3. 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且只能修改变量,而synchronized可以修饰方法,以及代码块。
  4. 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
  5. volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步
  6. 关键字volatile解决的下变量在多线程之间的可见性;而synchronized解决的是多线程之间资源同步问题。

5.Thread类中的start()和run()方法有什么区别?

  1. 通过调用线程类的start()方法来启动一个线程,使线程处于就绪状态,即可以被JVM来调度执行,在调度过程中,JVM通过调用线程类的run()方法来完成实际的业务逻辑,当run()方法结束后,此线程就会终止。
  2. 如果直接调用线程类的run()方法,会被当作一个普通的函数调用,程序中仍然只有主线程这一个线程。即start()方法能够异步的调用run()方法,但是直接调用run()方法却是同步的,无法达到多线程的目的。
  3. 因此,只用通过调用线程类的start()方法才能达到多线程的目的。

6.事务的隔离级别及引发的问题

  1. 4个隔离级别
  2. 读未提交、读已提交、可重复读、串行化
  3. 分别怎么理解呢?
    • 读未提交(READ UNCOMMITTED),事务中的修改,即使没有提交,对其它事务也是可见的。
    • 读已提交(READ COMMITTED),一个事务能读取已经提交的事务所做的修改,不能读取未提交的事务所做的修改。也就是事务未提交之前,对其他事务不可见。
    • 可重复读(REPEATABLE READ),保证在同一个事务中多次读取同样数据的结果是一样的。
    • 串行化(SERIALIZABLE),强制事务串行执行。
  4. 读已提交是sql server的默认隔离级别。
  5. 可重复读是mysql的默认隔离级别。
  6. 多个事务,各个隔离级别引起的问题
    • 读未提交:可能出现脏读、不可重复度、幻读;
    • 读已提交:可能出现不可重复度、幻读;
    • 可重复读:可能出现幻读;
    • 串行化:都没问题;

简要回答:

  1. 4个隔离级别,读未提交、读已提交、可重复读、可串行化。
  2. 读未提交(READ UNCOMMITTED),事务提交与否都可见,引发脏读、不可重复读、幻读。
  3. 读已提交(READ COMMITTED),已提交的事务可见,引发不可重复读、幻读。
  4. 可重复读(REPEATABLE READ),多次读取,数据一致,引发幻读。
  5. 串行化(SERIALIZABLE),串行执行。

辅助理解:

  • 事务隔离级别和引发的问题:
英文 中文 更新丢失 脏读 不可重复读 幻读
Read Uncommited 读未提交 不会出现 会出现 会出现 会出现
Read Commited 读已提交 不会出现 不会出现 会出现 会出现
Repeatable Read 可重复读 不会出现 不会出现 不会出现 会出现
Serializable 串行化 不会出现 不会出现 不会出现 不会出现
  • 大多数数据库的默认隔离级别为: Read Commited,如Sql Server , Oracle。

  • 少数数据库默认的隔离级别为Repeatable Read, 如MySQL InnoDB存储引擎。

  • 理解脏读、不可重复读、幻读:

    • 脏读:读到未提交的数据。
    • 不可重复读:重点是修改,同样的条件, 你读取过的数据, 再次读取出来发现值不一样了。
    • 幻读:重点在于新增或者删除,同样的条件, 第1次和第2次读出来的记录数不一样。

简单理解4个隔离级别

  • 读未提交,比如事务A和事务B同时进行,事务A在整个执行阶段,会将某数据的值从1开始一直加到10,然后进行事务提交。此时,事务B能够读取事务A操作过程中的未提交的数据(1、2、3、4、5、6...10)。
  • 读已提交,事务A在整个执行阶段,会将某数据的值从1开始一直加到10,然后进行事务提交。此时,事务B只能读取到最终的10。
  • 可重复读,事务B开始读取到的是某个值是0,事务A对值进行修改提交多次,事务B读取到的依然是0。多次读取,结果一致。
  • 串行化,是最严格的事务隔离级别,它要求所有事务被串行执行,一个事务没有结束,另外的事务没法继续。

7.工作中哪些地方使用了多线程?

  1. 一般业务,web层--> service层 -->dao --> sql基本用不到多线程
  2. 数据量很大(1000w级别、TB级别)的I/O操作,可以考虑多线程
  3. 举一些例子:
    • 自己做并发测试的时候,假如想写想模拟3000个并发请求。
    • 多线程下单抢单,假如支持5000人的并发下单。
    • 多线程写入mysql,假如有1000w条数据要入库。
    • 多线程写入redis,假如有1000w的数据要存入redis。
    • 多线程导入ES索引,假如有1000w的数据要添加到ES索引。
    • poi多线程导出,假如xls里面有10w的数据需要导出。
    • poi多线程导入,假如有10w条数据需要导入到xls。
    • 多线程发送邮件,假如有10w用户需要发送邮件。
    • 多线程发送短信,假如有10w用户需要发送邮件。
    • 多线程备份日志,假如10tb日志文件要备份。
    • 多线程验证数据,比如验证url是否存在,假如有100w个url。

8.线程的创建方式有哪些?

  1. 继承Thread类实现
  2. 实现Runnable接口方式
  3. 实现Callable接口方式
  4. 其中前两种比较常用。但是,需要有返回值需要实现Callable接口。

辅助理解:

  • 继承Thread类实现:
/**
  * 继承Thread类,并重写run方法
  */
 public class MyThread extends Thread {
     @Override
     public void run() {
         super.run();
         System.out.println("MyThread...");
    }
 }
  • 调用:
 MyThread thread = new MyThread();
 thread.start();

注意:

  • 调用start方法并不意味立刻执行run方法,只是使该线程处于可运行状态,具体什么时候执行,由系统来决定。

  • java不支持多继承,这种方式有继承限制。

  • 实现Runnable接口方式:

/**
  * 实现Runnable接口,并重写run方法
  */
 public class MyRunnable implements Runnable{
 
     @Override
     public void run() {
         System.out.println("MyRunnable...");
    }
 }
  • 调用:
MyRunnable runnable=new MyRunnable();
   Thread thread=new Thread(runnable);
   thread.start();

注意:

  • 限制较小,推荐用这个方式。

  • 实现Callable接口方式:

/**
  * 实现Callable接口,并重写call方法
  */
 public class MyCallable implements Callable<String>{

     @Override
     public String call() throws Exception {
         return "MyCallable...";
    }
 }
  • 调用:
 //创建和调用
 MyCallable callable=new MyCallable();
 ExecutorService eService=Executors.newSingleThreadExecutor();
 Future<String> future=eService.submit(callable);

 //获取返回结果
 try {
    String result=future.get();
    System.out.println(result);
 } catch (Exception e) {
    e.printStackTrace();
 }

注意:

  • callable需要配合线程池使用
  • callable比runnable功能复杂一些: - Callable的call方法有返回值并且可以抛异常,而Runnable的run方法就没有返回值也没有抛异常,也就是可以知道执行线程的时候除了什么错误。 - Callable运行后可以拿到一个Future对象,这个对象表示异步计算结果,可以从通过Future的get方法获取到call方法返回的结果。但要注意调用Future的get方法时,当前线程会阻塞,直到call方法返回结果。

9.说一下事务特性?

  1. 事务特性指的就是ACID。
  2. 分别是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。
  3. 分别解释下:
    • 原子性:原子性是指事务包含的操作要么全部成功,要么全部失败。因此事务的操作成功就必须要完全应用到数据库。
    • 一致性:一致性强调的是数据是一致性的。假设用户A和用户B两者的钱加起来一共是5000,那么不管A还是B如何转账,转几次帐,事务结束后两个用户的钱加起来应该还是5000,这就是事务的一致性。
    • 隔离性:当多个用户并发访问数据库时,多个并发事务是相互隔离的。事务之间不能相互干扰。
    • 持久性:一个事务一旦被提交了,那么对数据库中的数据改变是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

简要理解:

  1. 也就是acid。
  2. 分别是原子性、一致性、隔离性、持久性。
  3. 原子性,要么同时成功要么同时失败。
  4. 一致性,数据应该是一致的。
  5. 隔离性,多个并发事务是相互隔离的。
  6. 持久性,事务提交,对数据的改变是永久的。

辅助理解:

  • 原子性,算是事务最基本的特性了。
  • 一致性,感觉像事务的目标,其他的三个特性都是为了保证数据一致性存在的。
  • 隔离性,为了保证并发情况下的一致性而引入,并发状态下单靠原子性不能完全解决一致性的问题,在多个事务并发进行的情况下,即使保证了每个事务的原子性,仍然可能导致数据不一致。比如,事务1需要将100元转入帐号A:先读取帐号A的值,然后在这个值上加上100。但是,在这两个操作之间,另一个事务2将100元转入帐号A,为它增加了100元。那么最后的结果应该是A增加了200元。但事实上,事务1最终完成后,帐号A只增加了100元,因为事务1覆盖了事务2的修改结果。
  • 持久性,好理解,事务一旦提交,对数据库的影响是永久的,保证所有操作都是有效。

10.同步和异步有何异同?

  1. 同步发了指令,会等待返回,然后再发送下一个。
  2. 异步发了指令,不会等待返回,随时可以再发送下一个请求
  3. 同步可以避免出现死锁,读脏数据的发生
  4. 异步则是可以提高效率
  5. 实现同步的机制主要有临界区、互斥、信号量和事件

11.进程和线程的区别?

  1. 程序被载入到内存中并准备执行,它就是一个进程
  2. 单个进程中执行中每个任务就是一个线程
  3. 一个线程只能属于一个进程,但是一个进程可以拥有多个线程。
  • 作者:CZC(关于作者)
  • 发表时间:2024-07-19 19:12
  • 版权声明
  • 评论区:

    留言