多线程(二)线程同步的理解

线程同步

定义

线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,处于等待状态,直到该线程完成操作, 其他线程才能对该内存地址进行操作。

举例卖票(模拟这类情况不能继承Thread,thread类的任务和线程是绑到一起的,开启一个线程,任务也会重新开启一个)

用卖票的例子,来模拟出现的一些问题,创建一个类,继承Thread类,在run方法中写买票的逻辑,创建多个线程,模拟买票的窗口。

卖票逻辑

package com.sj.thread;

public class ThreadDemo4 implements Runnable {
    //定义总票数为100张
    private  Integer tickets=100;
    //在run方法中实现卖票的具体逻辑
       /*
        1.先判断票数是否大于0,然后进行卖票的操作。
        2.把票的总数目进行减一。
        3.票卖完了可能还有人来咨询,设置一个死循环。
     */
    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + 					tickets + "张票");
                tickets--;
            }
        }
    }
}

创建多个线程模拟卖票

package com.sj.thread;

public class ThreadDemo4Test {
    public static void main(String[] args) {
        ThreadDemo4 td=new ThreadDemo4();
        //创建三个线程,模仿三个窗口买票
        Thread thread1 = new Thread(td);
        thread1.setName("窗口1");
        Thread thread2 = new Thread(td);
        thread2.setName("窗口2");
        Thread thread3 = new Thread(td);
        thread3.setName("窗口3");
        //启动线程
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
/*
输出结果:
    窗口1正在出售第100张票
    窗口2正在出售第100张票
    窗口3正在出售第98张票
    窗口2正在出售第97张票
    窗口1正在出售第97张票
    窗口3正在出售第95张票
    窗口1正在出售第94张票
    窗口2正在出售第94张票
    窗口3正在出售第92张票
     ...
     ...	
    窗口2正在出售第7张票
    窗口1正在出售第7张票
    窗口3正在出售第5张票
    窗口2正在出售第4张票
    窗口1正在出售第4张票
    窗口3正在出售第2张票
    窗口1正在出售第1张票
    窗口2正在出售第0张票
    窗口3正在出售第-1张票
*/

结论

看打印结果,同一张票被卖了多次,如果多次测试,还会出现负数的情况,这是不符合实际情况的。

问题分析

为了问题更加明显,我们在判断票数大于零后,让线程休眠100毫秒,模拟出票的延迟。

package com.sj.thread;
public class ThreadDemo4 implements Runnable{
    //定义总票数为100张
    private  Integer tickets=100;
    @Override
    public void run() {
        while (true) {
            //tickets 等于100
   			//t1,t2,t3 代表三个线程
            //假设t1有cpu的执行权,判断票数大于0
            if (tickets > 0) {
                try {
                    //休息100好秒模拟出票延迟
                     Thread.sleep(100);
                    //t1休眠了100毫秒
                    //假设t2拿到cpu的执行权,当t2执行到这里的时候,它也休息100毫秒.
                   //此时t3拿到cpu的执行权,当t3执行到这里的时候,它也休息100毫秒。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //假设t1,t2,t3按顺序醒过来
                //t1抢到cpu执行权,控制台打印窗口1正在出售第100张票
                //t2抢到cpu执行权,控制台打印窗口2正在出售第100张票
                //t3抢到cpu执行权,控制台打印窗口3正在出售第100张票
                System.out.println(Thread.currentThread().getName() + "正在出售第" + 					tickets + "张票");
                //假设走到这里还是按顺序来的话,减减就会执行三次,最终票就会变成97 理解卖票数为负数的情					况的话,把100换成1在捋一遍。
                tickets--;
            }
        }
    }
}

上述问题就是多线程环境下的数据安全问题之一

判断是否会出现数据安全问题色条件

1,是否是多线程环境(多个窗口)

2,是否有共享数据(共同卖100张票)

3,是否有多条语句操作共享数据(总票数减减,如果只是拿出来看一看,多少个人看都不会变,就不会出现问题)

解决办法

1.使用同步代码块。格式如下

synchronized(任意对象){
    操作共享数据的多条语句
}
package com.sj.thread;
public class ThreadDemo4 implements Runnable {
    //定义总票数为100张
    private Integer tickets = 100;
    //任意对象做锁
    Object obj = new Object();
    //在run方法中实现卖票的具体逻辑
    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + 					tickets + "张票");
                    tickets--;
                }
            }
        }

    }
}

测试

package com.sj.thread;

public class ThreadDemo4Test {
    public static void main(String[] args) {
        ThreadDemo4 td=new ThreadDemo4();
        //创建三个线程,模仿三个窗口买票
        Thread thread1 = new Thread(td);
        thread1.setName("窗口1");
        Thread thread2 = new Thread(td);
        thread2.setName("窗口2");
        Thread thread3 = new Thread(td);
        thread3.setName("窗口3");
        //启动线程
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
/*
输出结果:
        窗口3正在出售第100张票
        窗口3正在出售第99张票
        窗口1正在出售第98张票
        窗口1正在出售第97张票
        窗口1正在出售第96张票
            ...
            ...
        窗口3正在出售第6张票
        窗口1正在出售第5张票
        窗口1正在出售第4张票
        窗口1正在出售第3张票
        窗口3正在出售第2张票
        窗口3正在出售第1张票 
*/

2.同步方法(就是在普通方法返回值前加synchronized关键字)

package com.sj.thread;

public class ThreadDemo5 implements Runnable {
    //定义总票数为100张
    private Integer tickets = 100;
    //在run方法中调用同步方法
    //在同步方法中写具体的操作逻辑
    @Override
    public void run() {
        while (true) {

          sellTickets();
        }
    }




    private  synchronized void  sellTickets() {
        if (tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + 				"张票");
            tickets--;

        }
    }

}

测试

package com.sj.thread;

public class ThreadDemo4Test {
    public static void main(String[] args) {
        ThreadDemo5 td=new ThreadDemo5();
        //创建三个线程,模仿三个窗口买票
        Thread thread1 = new Thread(td);
        thread1.setName("窗口1");
        Thread thread2 = new Thread(td);
        thread2.setName("窗口2");
        Thread thread3 = new Thread(td);
        thread3.setName("窗口3");
        //启动线程
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
/*
输出结果:
        窗口3正在出售第100张票
        窗口3正在出售第99张票
        窗口3正在出售第98张票
        窗口3正在出售第97张票
        窗口3正在出售第96张票
        窗口3正在出售第95张票
        窗口3正在出售第94张票
        窗口3正在出售第93张票
            ...
            ...
        窗口2正在出售第9张票
        窗口2正在出售第8张票
        窗口2正在出售第7张票
        窗口2正在出售第6张票
        窗口2正在出售第5张票
        窗口2正在出售第4张票
        窗口2正在出售第3张票
        窗口2正在出售第2张票
        窗口2正在出售第1张票 
*/

3.使用lock锁(明确加锁,解锁)

package com.sj.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo6 implements Runnable {
    //定义总票数为100张
    private Integer tickets = 100;
    //先new一把锁
    Lock lock = new ReentrantLock();
    //在run方法中实现卖票的具体逻辑
    @Override
    public void run() {
        while (true) {
            lock.lock();
            if (tickets > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + 					tickets + "张票");
                tickets--;
            lock.unlock();
            }else{
               //防止由于死循环,最后上锁等不到解锁,报错
               lock.unlock(); 
            }
        }
    }
    
}

测试

package com.sj.thread;

public class ThreadDemo4Test {
    public static void main(String[] args) {
        ThreadDemo6 td=new ThreadDemo6();
        //创建三个线程,模仿三个窗口买票
        Thread thread1 = new Thread(td);
        thread1.setName("窗口1");
        Thread thread2 = new Thread(td);
        thread2.setName("窗口2");
        Thread thread3 = new Thread(td);
        thread3.setName("窗口3");
        //启动线程
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
/*
输出结果:
        窗口3正在出售第100张票
        窗口3正在出售第99张票
        窗口3正在出售第98张票
        窗口3正在出售第97张票
        窗口3正在出售第96张票
        窗口3正在出售第95张票
        窗口3正在出售第94张票
        窗口3正在出售第93张票
            ...
            ...
        窗口2正在出售第5张票
        窗口2正在出售第4张票
        窗口2正在出售第3张票
        窗口2正在出售第2张票
        窗口2正在出售第1张票 
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值