线程及同步锁
- 1. 进程
- 1.1 进程的概念
- 1.2 进程的特点
- 2 线程
- 2.1 线程的概念
- 2.2 进程和线程的区别
- 2.3 进程和线程的关系
- 2.4 线程状态
- 2.5 多线程创建
- 2.5.1 继承Thread
- 2.5.2 实现Runnable接口
- 2.5.3 创建多线程方法的比较
- 2.5 线程的方法
- 2.5.1 阻塞线程的方法
- 2.5.1 停止线程的方法
- 2.5.3 线程的常用方法
- 2.6 并行和并发的区别
- 2.7 sleep()和wait()有什么区别?
- 3 同步锁
- 3.1 多线程的数据访问冲突
- 3.2 同步锁的原理
- 3.3 实现同步的方法
- 3.3.1 使用同步代码块
- 3.3.2 使用同步方法
- 3.4 同步和异步的区别
- 3.5 判断,我的资源有没有多线程并发的安全隐患
1. 进程
1.1 进程的概念
是程序在执行过程中分配和管理资源的基本单位,每一个进程都有一个自己的地址空间,至少有 5 种基本状态,它们是:初始态,就绪状态,执行态,等待状态,终止状态。
1.2 进程的特点
- 独立性:进程是系统中独立存在的实体,它可以拥有自己的独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
- 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。
- 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
2 线程
2.1 线程的概念
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以开启多个线程。
2.2 进程和线程的区别
- 进程是操作系统资源分配的基本单位
- 线程是任务调度和执行的基本单位
2.3 进程和线程的关系
- 一个进程中可以有多个线程
- 每个进程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存。
2.4 线程状态
- 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
- 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
- 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
- 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;
根据阻塞产生的原因不同,阻塞状态又可以分为三种:
等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态,JVM会把该线程放入等待队列(waitting queue)中;
同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态,JVM会把该线程放入锁池(lock pool)中;
其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 - 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
2.5 多线程创建
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。
- 启动线程的唯一方法就是通过Thread类的start()实例方法。
- Start()方法是一个native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()方法。
- 通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
- start()方法只是通知操作系统线程就绪,具体什么时间执行,操作系统来决定,JVM已经控制不了了。
2.5.1 继承Thread
- 定义 Thread 的子类
- 重写 run() 方法
- 在 run() 方法中的代码,是与其他代码并行执行的代码
- 线程启动后,自动自动执行 run() 方法
2.5.2 实现Runnable接口
如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口
- 定义Runnable子类
- 实现run() 方法
- 把Runnable 对象,放入 Thread 线程对象启动
- 线程启动后,执行 Runnable 对象的 run() 方法
2.5.3 创建多线程方法的比较
2.5 线程的方法
2.5.1 阻塞线程的方法
- Object.wait()
- Thread.sleep(毫秒值)
当前线程暂停指定的毫秒时长 - Thread.join()
当前线程,等待被调用的线程结束
在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
2.5.1 停止线程的方法
- 正常退出
当线程中run()或者call()按照逻辑流程正常的执行结束了,线程也就自然停止了 - stop暴力停止
直接在程序中使用thread.stop(),线程会马上停止,但是可能导致数据不同步,或者资源得不到回收的问题而且stop已经标注为作废方法,所以使用一定要慎重。 - interrupt()异常法
打断一个线程的暂停状态,被打断的线程出现InterruptedException
线程处于阻塞状态配合interrupt()都使线程停止,而且停止的方式都是通过抛异常方式
2.5.3 线程的常用方法
- Thread.currentThread()
获得正在执行这行代码的线程对象 - Thread.yield()
让步,当前线程放弃时间片,让出cpu资源,线程进入就绪状态
让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程 - getName(),setName()
- start()
- getPriority(), setPriority()
优先级,1到10,默认5
MIN_PRIORITY最低优先级1;NORM_PRIORITY普通优先级5;MAX_PRIORITY最高优先级10
2.6 并行和并发的区别
- 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的;
- 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行
2.7 sleep()和wait()有什么区别?
- Thread类的方法:sleep(),yield()等
Object的方法:wait()和notify()等 - 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
sleep方法没有释放锁,
wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。 - wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
- sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
3 同步锁
3.1 多线程的数据访问冲突
多个线程,共享访问数据
一个线程访问到修改的一半的数据,称为脏数据
3.2 同步锁的原理
用一个对象作为一把锁,给代码上锁,一个线程访问锁代码时,其他线程只能等待锁释放才能进来
3.3 实现同步的方法
3.3.1 使用同步代码块
锁的代码块,
需要指定锁对象,可以是任意对象,但是必须是同一个对象
synchronized(锁对象){ 有安全隐患的代码 }
3.3.2 使用同步方法
使用同步方法
使用的锁对象是this
synchronized public void eat(){ 有安全隐患的代码}
3.4 同步和异步的区别
- 同步是指同一时刻只能有一个人操作数据,别人只能排队等待。牺牲了效率,提高了安全。
- 异步是指同一时刻没人排队,大家一起上一起抢。提高了效率,牺牲了安全。
3.5 判断,我的资源有没有多线程并发的安全隐患
在多线程的场景下 + 共享资源,被多条语句操作 >=2