当前位置: 首页 > wzjs >正文

网络公司网站开发案例深圳网络营销运营

网络公司网站开发案例,深圳网络营销运营,网络服务器异常是怎么回事,如何建立一个免费网站线程安全问题是多线程编程中非常重要,它涉及到可执行文件在多线程环境下对共享资源的正确访问和操作。 一. 线程安全的概念 线程安全是指在多线程环境下,程序能够正确地处理共享资源,确保数据的完整性和一致性 意味着: 一个代码…

线程安全问题是多线程编程中非常重要,它涉及到可执行文件在多线程环境下对共享资源的正确访问和操作

一. 线程安全的概念

线程安全是指在多线程环境下,程序能够正确地处理共享资源,确保数据的完整性和一致性

意味着:

一个代码,不管在单线程下执行,还是多线程下执行,都不会产生bug,那么就是线程安全

一个代码,在单线程下运行正确,但是多线程下运行产生bug,那么就是“线程不安全”

典型的线程不安全示例 —— 多线程同时修改共享变量导致结果错误 

public class Demo_1 {public static int count = 0;public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{for (int i = 0; i < 5_0000; i++) {count++;}});Thread thread1 =new Thread(()->{for (int i = 0; i < 5_0000; i++) {count++;}});thread.start();thread1.start();thread.join();thread1.join();System.out.println("count: "+count);}
}

按道理说,应该输出10w ,但是结果不是,没有达到预期的结果,则说明该线程存在线程安全问题

 为什么没有输出预期结果?

cpu在运行可执行文件的时候,本质上是在执行指令,count++这个操作由3个代码组成

  1. 从内存中读取数据到cpu的寄存器中
  2. 将cpu寄存器中的数值+1
  3. 将寄存器中被修改后的值写回内存中

如果是一个线程,执行三条指令(执行顺序不发生改变),那么结果肯定是正确的

如果是两个线程(并发执行),可能会出现在原来线程1三条有序的指令中间插入线程2的指令(如图)会导致执行的结果不可预测

出现这种情况的根本原因:线程的随机调度和抢占式执行

注意:这里的并发执行,是并发+并行,具体那种,看cpu调度 

二. 线程不安全的原因

(1)线程间的执行方式

  • 抢占式执行和随机调度的机制导致了线程之间的执行顺序不可预测

(2)多个线程修改同一个变量

  • 一个线程修改同一个变量,不会造成线程不安全
  • 如果只是读取变量内容,不会造成线程不安全(没有修改)
  • 如果两个不同的变量,也不会造成线程不安全(没有相互覆盖)

(3)多线程修改变量的操作,本质上不是原子性

  • 每个cpu指令都是原子性的,要么不执行,要么执行完
  • 如果一个操作是由多个cpu指令组成,线程在执行一半的时候,容易会被调度走,导致其他的指令插入进来

注意:+=,-= 操作也是非原子,= 是原子

(4)内存可见性问题

  • 常发生在一个线程读数据,一个线程写数据,由于代码优化的功能,导致数据被修改,执行读数据操作的线程没有发现

(5)指令重排序问题

  • 如果一个操作是由多个cpu指令组成,在操作中指令的顺序发生了改变,导致出现bug

 知道了造成线程不安全原因,就可以对症下药,我们经常针对原因3做出操作,我们可以使用让多个指令打包在一起,成为“整体”

Thread thread = new Thread(()->{for (int i = 0; i < 5_0000; i++) {synchronized (object){count++;}}});

加锁的目的,是为了把三个操作,打包成一个原子的操作。 

三. 线程不安全的解决

1. 锁

(1)锁的作用 

锁用于 控制多个线程对共享资源的访问,确保同一时间只有一个线程能操作临界区资源

(2)锁的特点

锁互斥 / 锁竞争

  • 如果一个线程,针对一个对象上锁后,其他的线程,如果尝试向这个对象上锁,就会发生阻塞
  • 如果这个对象被释放(解开锁),才不会被阻塞
  • 如果是两个不同的对象上锁,那么就不会发生锁竞争
  • 如果一个线程加锁,一个线程不加锁,也不会发生锁竞争

加锁的核心一定是产生锁竞争,没有发生锁互斥,那么加锁没有意义

(3)synchronized关键字

在java中使用synchronized关键字,来表示锁

锁对象:在使用的时候,必须要有一个锁对象,

  1. 在java中,任何一个对象都可以做为一个锁对象
  2. 锁竞争不取决于操作的对象是什么,而是取决于是否在操作同一个对象。
  3. 如果两个线程操作的是同一个对象,那么就会产生竞争。如果不是同一个对象,则不会产生竞争。
                synchronized (object){count++;}
  1. 在 synchronized关键字中,进入{ }表示加锁,出了{ }表示解锁
  2. 加锁的目的,是为了把三个操作,打包成一个原子的操作。 并不是加锁之后,执行三个操作过程中,线程就不调度了,只是保证了其他线程无法“插队”
  3. 本质上synchronized关键字是通过调用系统的 API来进行加锁的。

可重入性 

        Thread t = new Thread(()->{
//            可重锁
//            同一个线程内,连续使用两次锁,不会发生死锁synchronized (object) {synchronized (object) {System.out.println("111");}}});

 在java中,锁具有可重入性,所有这种情况是正确的(在c++/c,python等中出现这种情况会发生死锁)

在可重⼊锁的内部, 包含了两个信息:"线程持有者" 和 "计数器"
如果遇到 “ {  ” 则计数器+1 ,“  }  ”则计数器 - 1 

 

 一个线程进行加锁的时候,发现锁已经被使用了,那么正常情况下就会进入阻塞状态,但是如果被使用的对象是自己,那么就可以继续加锁

 (4)常见的写法

1. 修饰实例方法
    public synchronized void method() {// 同步代码块}    

 每次只有一个线程可以执行这个方法。

2. 修饰静态方法
    public static synchronized void method() {// 同步代码块}

因为静态方法属于类而不是实例,所以锁定的是类的对象,影响的是所有实例。

3. 修饰代码块
static Object o =new Object();synchronized(o) {// 同步代码块}

 最常用的写法

(5)死锁

1. 死锁经典案例
1. 一个线程两把锁

在java中,锁具有可重入性,所有出现反复加锁的情况不会发生死锁

2. 两个线程两把锁

类似于房间钥匙在车里,车钥匙在房间里

public class Demo_4 {static Object A = new Object();static Object B = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized (A){//休眠保证thread1线程可以拿到o2锁try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("thread线程状态(尝试获取B锁):"+Thread.currentThread().getState());synchronized (B){System.out.println("拿到了两把锁");}}});Thread t2 = new Thread(()->{synchronized (B){//休眠保证thread线程可以拿到o1锁try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("thread1线程状态(尝试获取o1锁):"+Thread.currentThread().getState());synchronized (A){System.out.println("拿到了两把锁");}}});t1.start();t2.start();}
}
public class Demo_4 {static Object A = new Object();static Object B = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized (A){//休眠保证thread1线程可以拿到o2锁try {Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("thread线程状态(尝试获取B锁):"+Thread.currentThread().getState());synchronized (B){System.out.println("拿到了两把锁");}}});Thread t2 = new Thread(()->{synchronized (B){//休眠保证thread线程可以拿到o1锁try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("thread1线程状态(尝试获取o1锁):"+Thread.currentThread().getState());synchronized (A){System.out.println("拿到了两把锁");}}});t1.start();t2.start();}
}

 

两个进程发生死锁现象,t1拿到了A锁,t2拿到了B锁。t1此时想要在拿到B锁的条件是,t2能够释放B锁;t2想要拿到A锁的条件是,t1能够释放A锁,两个线程互不相让,所以产生了死锁。

 产生了死锁,两个线程就卡住了,导致后面的代码无法正常执行

如何解决这种死锁? 

规定好加锁的顺序就解决这种死锁问题,比如都规定只有先拿到了A锁,才能拿B锁

3. N个线程M把锁

哲学家就餐问题:五个哲学家在一个圆桌上吃饭,桌子上只有5个筷子,怎么安排

其实在大都数情况下,筷子的数量是够的,可以正常完成就餐情况

但是如果在同一时刻,所有的哲学家同时拿起左边的筷子,那么就会发生死锁,不能正常就餐

解决死锁的方法:

  1. 增加一个筷子/去掉一个哲学家
  2. 增加一个计数器,限制最多可以多少人同时就餐
  3. 引入加锁顺序的规则
  4. 银行家算法

可以对这五个筷子(锁)进行编号,规定每个哲学家(线程) ,获取筷子的时候,必须先获得编号小的,才能获得编号大的

2. 死锁产生的必要条件
(1)互斥使用

一个线程获得这个锁,那么另外一个线程想获得这个锁,就只能阻塞等待

(2)不可抢占

一个线程获得这个锁,只能主动解锁,别的线程不能强行把锁抢走

(3)占有且等待

一个线程至少占有一个锁,并且等待获取其他锁

(4)循环等待/环路等待

存在一个线程等待环路,环路中的每个线程都在等待下一个线程所占有的资源。

 解除死锁

上面的四种产生的必要条件,只要破坏其中的一个,那么就会解除死锁

通常解除循环等待这个必要条件,通过指定一定的规则,就可以避免循环等待

2. volatile关键字

如果一个线程进行读操作,一个线程进行写操作,这时候会出现“ 内存可见性 ”引起的线程安全问题

import java.util.Scanner;//线程不安全问题:内存可见性
public class Demo_5 {
//这里即使修改flg的值也不会影响thread1线程的执行---flg变量的读写存在可见性问题volatile static int flg = 0;public static void main(String[] args) {Thread thread1 = new Thread(() -> {while (flg == 0) {}System.out.println("执行完毕");});Thread thread2 = new Thread(() -> {System.out.println("修改flg的值");Scanner scanner = new Scanner(System.in);flg = scanner.nextInt();scanner.close();});thread2.start();thread1.start();}
}

 

我们会发现,即使输入一个非0的数,线程1还是没有结束

因为这里JVM对代码进行了优化,比较flg是否等于0,flg中的值是缓存中寄存器中的值

这里的核心步骤:从内存中读取flg的值 和  拿着寄存器中的值和0比较 

从内存中读取flg的值开销比较大,由于内存的执行速度非常快(1秒几亿次),在执行几百次,发现flg的值没有发生任何改变,JVM就会以为flg是一直不变的,那么就会省略从内存中读取的操作,一直在使用之前缓存的值,提高了执行效率(代码优化功能)

所以在多线程中,线程2修改了其中的值,线程1会因为代码优化功能没有看见内存中这个值的变化 

 解决方案

在java中,提供了关键字volatile可以使JVM提供的代码优化功能强制关闭,每次都必须从内存中读取数据

 volatile static int flg = 0;

开销会变大,效率变低,但是数据的准确性提高了 

volatile关键字的核心功能:保证内存的可见性和禁止指令重排序

四. 线程饥饿问题

线程饥饿:在并发环境中,某些线程因资源分配或调度机制不合理,长期无法获取所需资源,无法执行其任务。饥饿的线程仍处于活跃状态(如等待队列中),但资源被其他线程持续抢占。

 举例说明:

模拟情况:假如一群人排队上厕所,这时候出现一个人,他去上厕所(上锁),发现厕所没有纸,于是他从厕所出来了(释放锁),但是他又跑进厕所(上锁)看现在有没有纸,发现没有又跑出来(解锁),就一直这样反复横跳,导致后面的人不能上,工作人员也不能往里面存放纸。

对于线程来说:有一个线程上锁又解锁,解锁又上锁,一直循环,导致后面线程不能使用这个资源

饥饿的线程处于活跃状态(如等待队列中),但资源被其他线程持续抢占。

解决方案 

在java中提供关键字wait( )notify( ),这两个关键字要搭配一起使用 

(1)wait

wait在使用的时候,使用的对象是锁

使用wait方法执行的操作

  • 让当前线程释放
  • 让线程进入WAITING状态或者TIMED_WAITING(是否带有时间参数)
  • 检测是否有其他线程使用notify方法,如果有则被唤醒,重新获取锁

这些操作是原子的,是一气呵成的,不能被其他指令插入

 结束等待条件

  • 其他线程调用该对象的 notify 方法.
  • 如果wait方法带有时间参数,等待时间超时 
  • 使用 interrupted 方法提前唤醒

public class Demo_6 {static Object A = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized (A){try {System.out.println("准备进入wait状态,等待notify唤醒");A.wait();System.out.println("被唤醒");} catch (InterruptedException e) {System.out.println("被interrupt唤醒");}}});Thread t2 = new Thread(()->{synchronized (A){try {//确保t1进入wait状态Thread.sleep(1000);System.out.println("准备唤醒A锁");A.notify();} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();}
}

 

执行过程:

  1. 在这里t1线程会先执行,拿到锁,打印"准备进入wait状态,等待notify唤醒",执行wait方法(释放锁,进入阻塞状态)
  2. t2线程执行,拿到锁,先执行sleep方法(保证t1先拿到锁),打印"准备唤醒A锁",然后执行notify操作,唤醒t1线程
  3. 这时候t1线程会尝试申请锁,但是由于t2线程还没有释放锁,会发生锁互斥(t1进入阻塞状态)
  4. t2执行完并释放锁,t1会得到锁资源,执行wait后面的内容,打印"被唤醒"

(2)notify

notify在使用的时候,使用的对象是锁notify方法可以把wait阻塞的线程唤醒,这两个方法经常一起搭配使用 

1)notify和wait之间是依靠锁对象联系在一起,如果是两个不同的对象,那么就无法唤醒

//wait和notify不是同一个对象
public class Demo_7 {static Object A = new Object();static Object B = new Object();public static void main(String[] args) {Thread t1 = new Thread(() -> {synchronized (A) {try {System.out.println("准备进入wait状态,等待notify唤醒");A.wait();System.out.println("被唤醒");} catch (InterruptedException e) {System.out.println("被interrupt唤醒");}}});Thread t2 = new Thread(() -> {synchronized (A) {try {//确保t1进入wait状态Thread.sleep(1000);System.out.println("准备唤醒A锁");B.notify();} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();t2.start();}
}

 

2)如果有多个wait 但是只有一个notify(前提锁对象都相同),那么就会随机唤醒一个

public class Demo_8 {static Object A = new Object();public static void main(String[] args) {Thread t1 = new Thread(()->{synchronized (A){try {A.wait();System.out.println("t1 被唤醒");} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(()->{synchronized (A){try {A.wait();System.out.println("t2 被唤醒");} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t3 = new Thread(()->{synchronized (A){try {A.wait();System.out.println("t3 被唤醒");} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t4 = new Thread(()->{synchronized (A){try {Thread.sleep(1000);A.notify();} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();t3.start();t4.start();}
}

这里会随机唤醒一个

也可以使用notifyAll唤醒这个对象的所有等待线程

        Thread t4 = new Thread(()->{synchronized (A){try {Thread.sleep(1000);A.notifyAll();} catch (InterruptedException e) {throw new RuntimeException(e);}}});

notify方法在使用的时候,即使没有wait,也不会产生副作用

五. 常见的阻塞状态 

1) 线程进入了 WAITING 状态(死等),必须要被唤醒

  1. 如果是wait( ),需要使用 notify()唤醒
  2. 当线程调用wait( ) 或 join()进入等待,可以调用 interrupt() 触发异常,导致提前唤醒。
  3. join 的自然结束

2) 线程进入了 BLOCKED 状态,必须持有锁的线程释放锁,操作系统来负责唤醒

3) 线程进入了 TIMED_WAITING 状态(sleep,wait,join),由操作系统会计时,时间到了之后进行唤醒

  1. 时间到了操作系统进行唤醒
  2. 如果是wait( ),需要使用 notify()唤醒
  3. 当线程调用wait( ) 或 join() sleep()进入等待,可以使用interrupt()触发异常,导致提前唤醒
  4. join的自然结束 

 六. sleep(),wait() 区别

(1) 所属类不同

  • sleep(): Thread类的静态方法,可以在任何地方使用
  • wait() : Object类的实例方法,必须在锁内使用(由锁对象调用)

(2) 锁的释放行为

  • sleep(): 不释放锁
  • wait() : 释放锁

(3) 唤醒条件 

  • sleep(): 时间到了,被操作系统唤醒,或者被异常唤醒
  • wait() : 其他线程调用同一对象的notify,时间到了,被操作系统唤醒(有时间参数),也可以被异常唤醒(特殊手段)

(4) 线程状态变化

  • sleep(): 线程只能进入TIMED_WAITING状态
  • wait() :线程进入WAITING状态或者TIMED_WAITING状态

 点赞的宝子今晚自动触发「躺赢锦鲤」buff!

http://www.dtcms.com/wzjs/23171.html

相关文章:

  • 律师做网站推广有用吗哪里有学计算机培训班
  • 国外什么网站是做外贸深圳外包网络推广
  • 服装培训网站建设搜索量排行
  • 成人用品怎样做网站推广山东最新消息今天
  • 网页设计效果图分析西安seo顾问培训
  • 网站地址怎么做超链接搜索引擎优化通常要注意的问题有
  • 本网站只做信息展示不提供在线交易做网站的网络公司
  • 天天斗地主官方网站开发网络营销人员招聘
  • 成都科技网站建设热优化推广服务
  • 城市建设网站金郑州seo推广优化
  • phpcms 手机网站如何自己编写网站
  • 建筑公司网站怎么设计seo网站自动发布外链工具
  • icp主体备案号 网站备案号外贸网站建站和推广
  • 个人手机网站大全网络推广发展
  • 用啥网站做首页官方app下载安装
  • 青岛经济新区建设局网站网站收录怎么做
  • 如何才能让自己做的网站百度能搜百度爱采购排名
  • 网站教程网东莞网站推广企业
  • o2o网站线上seo研究中心骗局
  • 建设网站公司哪家好晨阳seo
  • 大良营销网站建设策划北京网上推广
  • 商机加盟好项目下载优化大师
  • 上海市最新消息今天网站seo推广
  • 辽宁建设科技信息网网站会员营销
  • 码制作官网郭生b如何优化网站
  • 咨询装修seo联盟
  • 婚纱网站怎么做seo优化大师下载旧版本安装
  • 杭州网站设计制作网络营销手段有哪四种
  • 建网站麻烦吗外包公司和劳务派遣的区别
  • 静态网站做新闻系统酒店机票搜索量暴涨