线程、volatile与synchronized、Lock

线程

1、概念:

进程是程序运行资源分配的最小单位

线程CPU调度的最小单位,必须依赖于进程而存在。

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位。

2、线程生命周期:

线程生命周期的5种状态:

Java线程具有五中基本状态

新建状态(New):当线程对象被创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

  注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

  (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待队列中。
  (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
  (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

结束状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

Tip:

当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。

3、线程调度

共同点:

  1. wait()和sleep()他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
  2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态
    不同点
  3. Thread类的方法:sleep(),yield()等
    Object对象的方法:wait()和notify()等
  4. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
    sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法
  5. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
  6. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

4、线程实现

4.1、实现方式

  继承Thread覆盖run()方法

  实现Runnable接口,实现run()方法

  实现Callable接口,实现call()方法

4.2、之间的区别:

4.2.1、Thread类与Runnable接口区别:

    Thread:每个线程都独立,不共享资源

    Runnable:存在线程共享概念

4.2.2、Runnable和Callable的区别:

两者最大的不同点是:

1)是否能返回执行结果:

	<font color=red>实现Callable接口的任务线程**能返回执行结果**;而实现Runnable接口的任务线程**不能返回结果**</font>;

2)方法是否能抛出异常:

	Callable接口的<font color=red>call()方法**允许抛出异常**</font>;而Runnable接口的<font color=red>run()方法的异常只能在内部消化,**不能继续上抛**</font>;

5、线程安全

线程安全性包括两方面:可见性和原子性。

5.1、volatile与synchronized

5.1.1、volatile

volatile关键字解决的是内存可见性的问题:

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
使用volatile修饰的变量会强制将修改的值立即写入主存,主存中值的更新会使缓存中的值失效。这时候会去主内存读取。

5.1.2、volatile与synchronized区别

1) volatile轻量级,只能修饰变量synchronized重量级,可以修饰变量、方法、类

2) volatile只能保证数据的内存可见性,不能保证原子性,因为多个线程并发访问volatile修饰的变量不会阻塞。

synchronized不仅保证内存可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。

一个线程执行临界区代码过程如下:
    1 获得同步锁
    2 清空工作内存
    3 从主存拷贝变量副本到工作内存
    4 对这些变量计算
    5 将变量从工作内存写回到主存
    6 释放锁

所以说:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。

5.1、synchronized关键字的使用和缺点

1)、获取锁的线程执行完毕,然后线程释放对锁的占有
2)、线程执行发生异常,此时JVM会让线程自动释放锁
缺点:

即使大量读操作也会堵塞,不会并发去读。如果线程中有堵塞超时任务,其他任务也不会往下执行,全部堵塞了。

5.2、Lock

5.2.1、Lock接口中常用方法

  lock()
  tryLock() :尝试获取锁
  tryLock(long time, TimeUnit unit):按照超时时间获取锁
  lockInterruptibly():打断锁(注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的)
  unLock():释放锁

5.2.2、lock线程间的通信:

Conditon中的await()对应Object的wait();
Condition中的signal()对应Object的notify();
Condition中的signalAll()对应Object的notifyAll()

5.2.3、ReentrantLock(可重入锁)

ReentrantLock支持两种获取锁的方式,一种是公平模型,一种是非公平模型。

公平锁:保证执行顺序,先进先出,FIFO

非公平锁:JVM优化后一种插队模式,后进先出,LIFO(先进的数据可能一直消费不到)

5.2.4、ReentrantReadWriteLock (读写锁)

允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。

相对于排他锁,提高了并发性。

在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。

5.3、lock和synchronized区别:

Lock和synchronized的选择:

类别 synchronized Lock
存在层次 Java的关键字,在jvm层面上 是一个接口
锁的释放 1、以获取锁的线程执行完同步代码,释放锁
2、线程执行发生异常,jvm会让线程释放锁
在finally中必须释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。
如果A线程阻塞,B线程会一直等待
分情况而定,Lock有多个锁获取的方式,可以尝试获得锁,线程可以不用一直等待
锁状态 无法判断 可以判断
锁类型 可重入
不可中断
非公平
可重入
可判断
可公平(两者皆可)
性能 少量同步 大量同步

Lock可以提高多个线程进行读操作的效率。

6、守护线程

守护线程,可以理解为后台运行线程。进程结束,守护线程自然而然地就会结束,不需要手动的去关心和通知其状态。Java的垃圾回收也是一个守护线程。

它的好处就是你不需要关心它的结束问题。

原文地址:https://www.cnblogs.com/amunamuna/p/10287875.html