千家信息网

Java线程安全与同步实例分析

发表于:2024-12-13 作者:千家信息网编辑
千家信息网最后更新 2024年12月13日,本篇内容介绍了"Java线程安全与同步实例分析"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!线程安全
千家信息网最后更新 2024年12月13日Java线程安全与同步实例分析

本篇内容介绍了"Java线程安全与同步实例分析"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

线程安全问题

多个线程可能会共享(访问)同一个资源

比如访问同一个对象,同一个变量,同一个文件

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题,称为线程安全问题

什么情况下会出现线程安全问题

多个线程共享同一个资源

且至少有一个线程正在执行写的操作

实例:

存钱取钱问题

分别有存钱和取钱2个线程

存钱 取钱
线程1 余额 线程2
1000 《----1000------》 1000
1000+1000-----》2000
500 《-----1000-500

正确:结束后余额应该是1500,而不是500

买票问题

有卖票2个线程

卖票 卖票
线程1 票数 线程2
1000 《----1000------》 1000
1000-1-----》999
999 《-----1000-1

正确:结束后余额应该是998,而不是999

买票问题错误(未线程同步)实例:

public class love implements Runnable{    private int piao=3000;//有3000张票    public boolean sale() {//ture代表还有票;false代表没有票了        if(piao<1) return false;         piao--;//卖1张票                  //细化piao--;         //寄存器=piao;         //寄存器=寄存器-1;         //piao=寄存器;                  String sk =Thread.currentThread().getName();//获取当前线程(买票窗口)的名字         System.out.println(sk+"卖了1张票,还剩下"+piao+"张");         return piao>1;    }    public void run() {         while(sale());//循环执行;直至卖完票返回false    }} public class Main {    public static void main(String[] a) {        love tjlove =new love();        for(int i=1;i<=4;i++) {//循环4次;产生4个线程(窗口)卖票            Thread tj = new Thread(tjlove());            tj.setName(""+i);            tj.start();        }    }}

部分输出结果:

线程安全问题

分析问题

线程A和B对类中1个变量值为17进行+1操作
最终结果为2个18

解决方案

加锁:

过程:首先线程A先访问到这个17,读上来后进行加锁并进去+1的操作改为18
并且17在加锁期间其它线程都不能访问
改完之后再进行写入,然后再解锁17
然后再由线程B去访问它,再进行加锁,重复上面操作变成19再解锁
这样做能保证在同一时间只有1个线程去访问它,这样就保证了安全;之前错误是由于这些线程一起去访问了它

线程同步

刚刚所说的加锁操作便是线程同步技术

可以使用线程同步技术来解决线程安全问题

线程同步在Java里有2种做法:

1.同步语句

2.同步方法

同步语句

public class love implements Runnable{        private int piao=3000;//本人cpu单核性能过强,数据量大些才能看到是4个线程在卖票        public boolean sale() {                synchronized(this) {//1个线程获取这个对象的锁,并加锁;    synchronized作用于整个语句                //this指向当前对象                //不能用new Object();这样会产生新的对象,产生新的锁                //把this换成"123",效果基本一样;因为其存在常量值里,每次访问的对象一样                        if(piao<1) return false;                        piao--;                        String sk =Thread.currentThread().getName();                        System.out.println(sk+"卖了1张票,还剩下"+piao+"张");                        return piao>0;                        }        }        public void run() {                 while(sale());        }}

部分输出结果:

synchronize(obj)的原理

1.每个对象都有一个与它相关的内部锁(intrinsic lock)或者叫监视器锁(monitor lock)

2.第一个执行到同步语句的线程可以获得 obj 的内部锁,在执行完同步语句中的代码后释放此锁

3.只要一个线程持有了内部锁,那么其它线程在同一时刻将无法再获得此锁

✓ 当它们试图获取此锁时,将会进入BLOCKED状态

4.多个线程访问同一个 synchronized(obj)语句时

obj必须是同一个对象,才能起到同步的作用

同步方法

public class love implements Runnable{    private int piao=3000;    public synchronized boolean sale() { //synchronized作用于整个方法            if(piao<1) return false;            piao--;            String sk =Thread.currentThread().getName();            System.out.println(sk+"卖了1张票,还剩下"+piao+"张");            return piao>0;    }    public void run() {         while(sale());    }}

synchronized不能修饰构造方法

同步方法的本质

实例方法:synchronized (this)

静态方法:synchronized (Class对象)

同步语句比同步方法更灵活一点

同步语句可以精确控制需要加锁的代码范围,减少处于BLOCKED状态的线程,充分利用劳动力

使用了线程同步技术后

虽然解决了线程安全问题,但是降低了程序的执行效率

因为加了锁就会有处于等待的线程,多了加锁解锁的操作

所以在真正有必要的时候,才使用线程同步技术

"Java线程安全与同步实例分析"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!

0