文章目录
- 线程和进程的区别
- 线程的五个状态
- 多线程中使用run()和start()的区别
- 线程常用的方法
- synchronized
- 死锁
- 预防死锁:
- 避免死锁:
- 检测死锁:
- 解除死锁:
- CAS操作 (CompareAndSwap 系统原语,底层是用汇编实现的)
- 锁升级过程
- 生产者消费者模式
- 单例模式
线程和进程的区别
线程只能属于一个进程,而进程可以创建多个线程,且最少创建一个主线程
进程是资源分配的基本单位,线程是系统调度的基本单位,线程是真正的执行体
线程的五个状态
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
多线程中使用run()和start()的区别
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,
调用start()方法来启动一个线程,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行
线程常用的方法
-
join()
就是夹队,暂停其他线程,优先执行此线程,它能够使得t.join()中的t优先执行,当t执行完后才会执行其他线程。能够使得线程之间的并行执行变成串行执行 ,举个例子,如下代码:
public class Mythred implements Runnable {&#64;Overridepublic void run() {for (int i &#61; 0; i < 1000; i&#43;&#43;) {System.out.println("vip插队啦" &#43; i);}}public static void main(String[] args) throws InterruptedException {Thread thread1 &#61; new Thread(new Mythred());thread1.start();for (int i &#61; 0; i < 1000; i&#43;&#43;) {if (i &#61;&#61; 50) thread1.join();System.out.println("普通用户排队中" &#43; i);}}}
起初两个线程并行执行,打印着各自的结果,在主线程执行到50时,thred1调用啦join()方法,暂停啦主线程,
执行完thred1的内容后,才继续执行主线程的内容
- 暂停当前正在执行的线程对象(将线程转为就绪态),并执行其他线程
- 让cpu重新调度,但不一定成功
synchronized
部分内容摘自 synchronized
synchronized关键字最主要有以下3种应用方式&#xff0c;下面分别介绍
- 修饰实例方法&#xff0c;作用于当前实例加锁&#xff0c;进入同步代码前要获得当前实例的锁
- 修饰静态方法&#xff0c;作用于当前类对象加锁&#xff0c;进入同步代码前要获得当前类对象的锁
- 修饰代码块&#xff0c;指定加锁对象&#xff0c;对给定对象加锁&#xff0c;进入同步代码库前要获得给定对象的锁。
注意锁的必须是同一个对象时才管用,对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁,若是不同对象,岂不就没啦标志的意义?
死锁
部分内容摘自 添加链接描述
产生的四个必要条件
- 1、互斥使用&#xff0c;即当资源被一个线程使用(占有)时&#xff0c;别的线程不能使用
- 2、不可抢占&#xff0c;资源请求者不能强制从资源占有者手中夺取资源&#xff0c;资源只能由资源占有者主动释
- 3、请求和保持&#xff0c;即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 4、循环等待&#xff0c;即存在一个等待队列&#xff1a;P1占有P2的资源&#xff0c;P2占有P3的资源&#xff0c;P3占有P1的资源。这样就形成了一个等待环路。
产生的原因: 多个线程争夺资源而产生的互相等待的状况
举个例子: 有两个线程,线程1占用啦打印机这个对象锁(其他线程在这个线程释放打印机这个对象锁前是不能获取这个打印机锁的),线程2占用啦电脑锁,现在线程1申请获取线程2的电脑锁,线程2申请获取线程1的打印机锁,结果就导致两个线程在没有拿到自己想要的对象锁前是不会释放自己目前所占有的锁的,然后两者就处于相互等待的状况!
代码演示
public class Ticket implements Runnable {int flag;public static Object ob1 &#61; new Object();public static Object ob2 &#61; new Object();&#64;Overridepublic void run() {if (flag &#61;&#61; 1) {synchronized (ob1) {System.out.println(Thread.currentThread().getName() &#43; "目前拥有打印机ob1的锁");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (ob2) {System.out.println(Thread.currentThread().getName() &#43; "目前拥有打印机ob1和ob2的锁");}}} else {synchronized (ob2) {System.out.println(Thread.currentThread().getName() &#43;"目前拥有打印机ob2的锁");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
的锁synchronized (ob1) {System.out.println(Thread.currentThread().getName() &#43;"目前拥有打印机ob1和ob2的锁");}}}}public Ticket(int flag) {this.flag &#61; flag;}public static void main(String[] args) throws InterruptedException {Ticket ticket1 &#61; new Ticket(1);Ticket ticket2 &#61; new Ticket(2);Thread thread1 &#61; new Thread(ticket1);Thread thread2 &#61; new Thread(ticket2);thread1.start();thread2.start();}}
上述就是发生死锁的过程
解决死锁的办法
预防死锁&#xff1a;
通过设置某些限制条件&#xff0c;去破坏产生死锁的四个必要条件中的一个或几个条件&#xff0c;来防止死锁的发生
- 破坏“互斥”条件 “ 互斥”条件是无法破坏的。因此&#xff0c;在死锁预防里主要是破坏其他几个必要条件&#xff0c;而不去涉及破坏“互斥”条件。
- 破坏“占有并等待”条件 要求每个进程提出新的资源申请前&#xff0c;释放它所占有的资源。这样&#xff0c;一个进程在需要资源S时&#xff0c;须先把它先前占有的资源R释放掉&#xff0c;然后才能提出对S的申请&#xff0c;即使它可能很快又要用到资源R。要求每个进程提出新的资源申请前&#xff0c;释放它所占有的资源。这样&#xff0c;一个进程在需要资源S时&#xff0c;须先把它先前占有的资源R释放掉&#xff0c;然后才能提出对S的申请&#xff0c;即使它可能很快又要用到资源R。
- 破坏“不可抢占”条件 破坏“不可抢占”条件就是允许对资源实行抢夺。
如果一个进程请求当前被另一个进程占有的一个资源&#xff0c;则操作系统可以抢占另一个进程&#xff0c;要求它释放资源。只有在任意两个进程的优先级都不相同的条件下&#xff0c;方法二才能预防死锁。 - 破坏“循环等待”条件 破坏“循环等待”条件的一种方法&#xff0c;是将系统中的所有资源统一编号&#xff0c;进程可在任何时刻提出资源申请&#xff0c;但所有申请必须按照资源的编号顺序&#xff08;升序&#xff09;提出。这样做就能保证系统不出现死锁。
避免死锁&#xff1a;
在资源的动态分配过程中&#xff0c;用某种方法去防止系统进入不安全状态&#xff0c;从而避免死锁的发生
- 有序资源分配法 这种算法资源按某种规则系统中的所有资源统一编号&#xff08;例如打印机为1、磁带机为2、磁盘为3、等&#xff09;&#xff0c;申请时必须以上升的次序。系统要求申请进程&#xff1a;
1、对它所必须使用的而且属于同一类的所有资源&#xff0c;必须一次申请完&#xff1b;
2、在申请不同类资源时&#xff0c;必须按各类设备的编号依次申请。例如&#xff1a;进程PA&#xff0c;使用资源的顺序是R1&#xff0c;R2&#xff1b; 进程PB&#xff0c;使用资源的顺序是R2&#xff0c;R1&#xff1b;若采用动态分配有可能形成环路条件&#xff0c;造成死锁。
采用有序资源分配法&#xff1a;R1的编号为1&#xff0c;R2的编号为2&#xff1b;
PA&#xff1a;申请次序应是&#xff1a;R1&#xff0c;R2
PB&#xff1a;申请次序应是&#xff1a;R1&#xff0c;R2
这样就破坏了环路条件&#xff0c;避免了死锁的发生 - 银行家算法
检测死锁&#xff1a;
允许系统在运行过程中发生死锁&#xff0c;但可设置检测机构及时检测死锁的发生&#xff0c;并采取适当措施加以清除
解除死锁&#xff1a;
当检测出死锁后&#xff0c;便采取适当措施将进程从死锁状态中解脱出来
CAS操作 (CompareAndSwap 系统原语,底层是用汇编实现的)
是用汇编编写的一道指令,偏硬件级别 lock cmpxchg, 作用是多个线程操作同一变量时,能保证线程安全,java中关于cas的操作其实也是调用的本地方法
具体操作思路:比如现在,现有一个变量&#xff49;值为0&#xff0c;多个线程想对这个变量进行自增的操作。CAS的具体操作如下&#xff0c;线程1访问变量i的时候&#xff0c;会把变量i的初始值&#xff0c;存在另一个变量e中&#xff0c;之后线程1再进行i自增的操作&#xff0c;此时&#xff0c;i的新值应该是1&#xff0c;但这个这时候不着急把新值赋给i,而是先再将e的值与i当前的值进行比较(因为其他线程有可能在此时更改啦i的值,也就是说i本身的值是有随时有可能变化的,你不能把前一秒i的值自加吧 )&#xff0c;进行比较&#xff0c;如果相等,则说明此时i的值未经其他线程的变更,是准确无误的值,可以放心的把新值赋给i,若不相等,说明此时有线程动过这个值,那怎么办,回炉重新走遍这个流程! 那么在这无限回炉重复的过程也就是自旋操作!
具体思路
锁升级过程
无锁–>>偏向锁->>自旋锁(轻量级锁)->>重量级锁
最开始,当线程没有获取锁对象时,处于无锁状态,当只有一个线程(没有竞争),当此线程获取对象锁时,升级为偏向锁
- 偏向锁
其实就是在对象头(markword)中把自己线程的id加入进去,当此线程再次尝试获取这个对象锁时,将自己的线程id与对象头中的记录的线程id进行比对,如果相同,则此线程便可以拥有这把锁,因为这把锁总是自己拥有,所以叫做偏向锁
当超过1个线程发生竞争对象锁时,此时便自动升级为自旋锁(轻量级锁)
- 自旋锁
当另一个线程尝试获取对象锁时,发现这把锁被其他线程占有,此时它并不放弃cpu的执行,而是选择不断的判断这把锁是否已经释放,这个过程叫做自旋,特点是占有cpu,并不放弃cpu的使用权,但损耗cpu较大,适用于同步代码块执行时间短,线程数较少,
当线程数较多,且自旋次数超过10次时,便升级为重量级锁
- 重量级锁
把工作交给操作系统,当另一个线程尝试获取对象锁时,发现这把锁被其他线程占有,此时操作系统会将此线程加入到阻塞队列里,由操作系统负责调度,特点是不占有cpu的使用权,适用于同步代码块执行时间长,线程数较多,
生产者消费者模式
就是两个线程&#xff0c;一个生产者当没货啦则负责生产&#xff0c;产完后等待&#xff0c;当有货时就唤醒生产者&#xff1b;&#xff1b;一个消费者当有货啦则负责消费&#xff0c;消费后就等待&#xff0c;当没货时就唤醒生产者&#xff1b;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Test {static int baozi &#61; 0;static Lock lock &#61; new ReentrantLock();public static void main(String[] args) {new Thread(new Producer()).start();new Thread(new Customer()).start();}static class Producer implements Runnable {&#64;Overridepublic void run() {while (true) {synchronized (lock) {if (baozi &#61;&#61; 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}baozi&#43;&#43;;System.out.println("生产完后当前包子数目&#xff1a;" &#43; baozi);lock.notify();} else {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}static class Customer implements Runnable {&#64;Overridepublic void run() {while (true) {synchronized (lock) {if (baozi !&#61; 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}baozi--;System.out.println("消费完后当前包子数目&#xff1a;" &#43; baozi);lock.notify();} else {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}
单例模式
最最基础的单例模式手写&#xff0c;死也呆给记住&#xff1b;
public class SingleExample {private static volatile SingleExample example;public static synchronized SingleExample getExample() {if (example &#61;&#61; null) {synchronized (SingleExample.class) {if (example &#61;&#61; null) {example &#61; new SingleExample();}}}return example;}public static void main(String[] args) {System.out.println(SingleExample.getExample());}
}