Java复习之线程与并发一

1、线程与进程的区别
  • 进程时操作系统资源分配的基本单位,而线程是操作系统调用的基本单位;
  • 进程是一个独立的运行环境,它可以被看成一个程序或应用,线程是进程中执行的一个任务;
  • 一个进程可以有多个线程,同一个类的线程共享相同的内存空间;
  • 线程较之进程的切换开销小、速度快等特点
2、使用多线程的原因
  • 充分利用资源,目前的计算机都是多核,多线程可以让程序在多个CPU上运行,对于单一的CPU,如果使用单线程,当线程阻塞的时候就只能等待;
  • 多线程在完成不同种类的任务时可以兼备考虑到不同种类任务之间的优先级和交换关系;
  • 对异步事件的简化处理,对于单线程来说收到服务端发来的请求之后,数据处理端会发生阻塞直至完成,而多线程则不会,回另辟一个线程完成数据处理,当前界面不会受影响;
  • 多线程实现多个任务同时进行,提高性能
3、Java实现多线程的三种方式

①、继承Thread类,重写run()方法,
②、实现Runnable接口,重写run()方法;
③、继承Callable接口,重写call()方法,并使用FutureTask接收

  • 三者区别
    • Java不支持多继承,但可以实现多个接口,所以当一个类需要继承其他类可以通过实现Runnable接口实现多线程;
    • 实现Callable接口的run()方法有返回值可以通过FutureTask对象的get方法获取并且可以抛出异常,而实现Runnable和继承Thread类的run()既不能抛出异常,也没有返回值;
4、start()方法和run()方法的区别

①、start()方法时启动后一个线程,调用start()方法的线程处于就绪状态,并没有正在运行,只有当线程获取到了CPU时间片之后才能运行线程执行run()方法,此方法不能被一个线程重复调用,调用start()方法执行的run()方法代码可以不用完全执行完再切换(发生了线程切换),直接调用run()方法则必须执行完;
②、run()方法,和普通方法一样,如果直接调用run()方法,则不会开启一个多线程程序仍然只有主线程这一个线程,run()可以被重复调用;

5、wait()和sleep()方法的区别

①、原理不同
sleep()方法时Thread的静态方法,是线程用来控制自身流程的;
wait()时Object类的方法,用于线程间通信

②、对锁的处理机制不同
sleep()方法不会释放锁,wait()方法会释放当前持有的锁

③、使用地方不同
sleep()方法可以在任何地方使用,而wait()方法只能在同步代码块或同步方法中使用

6、volatile
首先了解三个名词
  • 原子性,要么全部发生,要么全部不发生;
  • 可见性,多线程中一个线程改变某变量值,其他线程立即可见;
  • 有序性,程序执行顺序为代码书写的顺序
Java内存模型JMM以及变量的可见性

JMM定义了线程和主内存之间的关系,共享变量存储在主内存之中,每个线程都一个私有的本地内存,本地内存中保存了被该线程使用到的主内存的副本拷贝,线程对内存的所有操作都是在私有的本地工作内存中,而不能直接操作主内存中的变量。
在这里插入图片描述
对于线程A来说,A将共享变量修改为某个值,此时还未刷新到主内存中去,线程B已经缓存了主内存中的旧值,此时就导致了共享变量的不一致问题。解决这种共享变量再多线程模型中不可见问题比较粗暴的方法是使用synchronized或者Lock,这两种方式都比较重量级,此时可以考虑使用volatile。

注意:JMM只是抽象的概念,并不一定真实存在。

volatile特性

①、volatile修饰的变量,JMM会把该线程本地内存中的volatile变量刷新到主内存中去,这个刷新操作会导致其他线程的旧缓存无效;
②、禁止指令重排序,使用volatile修饰共享变量可以禁止指令重排序,被volatile修饰的共享变量再编译时会插入内存屏障来禁止某些特定的类型的处理器重排序,执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对volatile变量及其后面语句可见。

关于volatile推荐阅读:
Java线程同步之volatile关键字
Java中volatile关键字的最全总结

7、ThreadLocal

ThreadLocal类并不是用于解决多线程中共享变量,而是用来提供线程内部的共享变量,可以保证多线程下各线程之间的变量相互隔离互不影响

ThreadLocal推荐阅读:
Java并发编程之ThreadLocal详解
Java ThreadLocal

有关ThreadLocal相关总结
①、ThreadLocalMap中的Entry使用当前的ThreadLocal实例作为key,但Entry继承了WeakReference,为什么要用ThreadLocal的弱引用作为key?
先看这张图
在这里插入图片描述
如果ThreadLocal没有外部的引用去引用它,则在GC的时候ThreadLocal看就会被回收,这是ThreadLocalMap中就会出现key为null的Entry,也就无法访问这些key为null的Entry的value,如果线程一直不结束的话就会存在一条CurrentThreadRef->Thread->ThreadLocalMap->Entry->Object的引用链,这条引用链无法被回收,会造成内存泄漏。
为什么要使用弱引用而不是强引用呢?

  • 如果使用强引用,当引用ThreadLocal的ThreadLocalRef被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用(上图的key),如果没有手动删除的话,ThreadLocal不会被回收,导致内存泄漏。
  • 如果使用的是弱引用,当ThreadLocalRef被回收后,ThreadLocalMap持有的是ThreadLocal的弱引用,即使不手动删除ThreadLocal也会被回收,value在ThreadLocalMap下一次调用get()、set()、remove()的时候被清除。

综上可知:ThreadLocalMap和Thread的生命周期一样长,如果没有手动删除key都会导致内存泄漏,使用弱引用多了一层保证,在下次调用get()、set()、remove()的时候也会被清除,因此在每次使用完ThreadLocal的时候最好是使用remove()擦除数据

②、ThreadLocal与synchronized的区别
二者都是多线程环境下处理变量的问题,ThreadLocal是一个类,通过对当前线程的局部变量操作来解决不同的线程变量访问的冲突问题;
synchronized是一个保留字,它通过JVM的锁机制来保证临界区的方法和变量的原子性;
synchronized采用以时间换空间的方式让线程依次访问,而ThreadLocal为每个线程都保留一份变量副本,从而实现访问时互不影响。

③、ThreadLocal的实现原理
底层每个Thread维护了一个ThreadLocalMap哈希表,这个哈希表的key是ThreadLocal本身,而value是真正要存储的Object,这样设计的好处有两点:

  • 之前存储数量是由Thread数量决定的,现在是有ThreadLocal的数量决定的,这样Map中存储的Entry数量就会变少;
  • Thread销毁之后对应的ThreadLocalMap也会随之销毁,减少内存占用

对ThreadLocal小结如下:

  • 每个Thread维护着一个ThreadLocalMap的引用;
  • ThreadLocalMap是ThreadLocal的内部类,用Entry存储;
  • 调用ThreadLocal的set()方法就是往ThreadLocalMap中设置值,key为ThreadLocal,value就是传递进来的对象;
  • 调用ThreadLocal的get()方法就是从ThreadLocalMap中获取值,可以为ThreadLocal;
  • ThreadLocal并不存储值,真正存储的是ThreadLocal中的ThreadLocalMap
8、并发编程工具类

①、闭锁(CountDownLatch)
用于延迟线程的进度直到其终止状态,可以报凭证某些线程完成后再执行。
②、信号量(Semaphore)
控制同时可以访问的现成的个数,维护这一组虚拟的permit,只有有permit的线程才能执行。例题:交替线程打印奇偶数
③、栅栏(CyclicBarrier)
允许一组线程等待,直到到达某个公共屏障点,栅栏可以被重用,而闭锁不能,CountDownLatch注重的是等待其他线程完成,栅栏注重的是:当线程到达某个状态后,暂停下来等待其他线程,所有线程均到达以后,继续执行。

详细阅读另一篇文章:Java并发编程之工具类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值