Java线程安全

概念

当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象时线程安全的。

该定义要求线程安全的代码都必须具备一个共同特征:
代码本身封装了所有必要的正确性保障手段(如互斥同步等),令调用者无须关心多线程下的调用问题,更无须自己实现任何措施来保证多线程环境下的正确调用。

模拟简单的卖票程序

public class Ticket implements Runnable{
    private int ticket = 10;
    @Override
    public void run() {
        //获取当前程序执行所属的线程的名称
        String threadName = Thread.currentThread().getName();
        while(true){
            if(ticket>0){
                System.out.println(threadName+"正在售卖第"+ticket+"张票");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
            }else{
                break;
            }
        }
    }
}

测试

package test7;
/**
 * 多线程执行相同的程序会出现线程安全的问题(共享的变量两个线程都进行作可能出现错误情况)
 *
 * 以后在开发的时候,如果多线程实现一个功能,有交量值修改的作一定要注意线程的同步问题
 */
public class ThreadTest01 {
    public static void main(String[] args) {
        //实例化卖票美的对象
        Ticket ticket = new Ticket();
        //把卖票对象作为参数创建线程,启动线程开始卖票(创建了小明这个线程,用来执行卖票程序)
        Thread t1=new Thread(ticket,"小明");
        Thread t2=new Thread(ticket,"小兰");
        t1.start();
        t2.start();
    }
}

 线程同步 

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决

同步代码块

package test7;


public class Ticket implements Runnable {
    //自己资源变量,模拟票的总数
    private int ticket = 10;
    //定义一个同步资源锁
    Object lock = new Object();

    @Override
    public void run() {
        //1.获取当前程宁行所属的线程的名称
        String threadName = Thread.currentThread().getName();
        while (true) {
            //同步代码块:把大括号中的代码,使用资源锁同步起来(Cpu在执行该线程程序的时候,这段代码比完执行完才能离开)
            synchronized (lock) {
                if (ticket > 0) {
                    //卖第ticket张票
                    System.out.println(threadName + "正在售卖第" + ticket + "张票");
                    //模拟打印出票环节,系统等待一会
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //卖完了,总数要减1
                    ticket--;
                } else {
                    break;//投票结束循环
                }
            }
        }
    }
}

 测试结果

 同步方法

​
package test7;

public class Ticket1 implements Runnable {
    //自己资源变量,模拟票的总数
    private int ticket = 10;

    @Override
    public void run() {
        //1.获取当前程宁行所属的线程的名称
        String threadName = Thread.currentThread().getName();
        //2.一直卖票
        while (true) {
            boolean flag=fun(threadName);
            if (flag){
                break;
            }
        }
    }

    //同步方法
    private synchronized boolean fun(String threadName) {
        if (ticket > 0) {
            //卖第ticket张票
            System.out.println(threadName + "正在售卖第" + ticket + "张票");
            //模拟打印出票环节,系统等待一会
            try {
                Thread.sleep(100);
            }  catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卖完了,总数要减1
            ticket--;
            return false;
        } else {
            return true;
        }
    }
}


​

 同步资源锁(Lock)

public class Ticket2 implements Runnable{
    //自己资源变量,模拟票的总数
    private int ticket = 10;
    //定义一个同步资源锁
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        //1.获取当前程序执行所属的线程的名称
        String threadName = Thread.currentThread().getName();
        //2.一直卖票
        while(true){
            lock.lock();
            //资源的操作--卖票
            if(ticket>0){
                //卖第ticket张票
                System.out.println(threadName+"正在售卖第"+ticket+"张票");
                try {
                    //模拟打印出票环节,系统等待一会
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //卖完了,总数要减1
                ticket--;
            }else{
                //没票结束循环
                break;
            }
            //操作完毕解锁
            lock.unlock();
        }
    }
}

注意事项:

Lock() 一定要放在try外面

  • 如果放在try里面,如果try里面出现异常,还没有加锁成功就执行finally里面的释放锁的代码,就会出现异常
  • 如果放在try里面,如果没有锁的情况下释放锁,这个时候产生的异常就会把业务代码里面的异常给吞噬掉,增加代码调试的难度

两种锁区别
synchronized和lock的区别

  • 关键字不同
  • synchronized自动进行加锁和释放锁,而Lock需要手动加锁和释放锁
  • synchronized是JVM层面上的实现,而Lock是Java层面锁的实现
  • 修饰范围不同,synchronized可以修饰代码块,静态方法,实例方法,而Lock只能修饰代码块
  • synchronized锁的模式是非公平锁,而lock锁的模式是公平锁和非公平锁
  • Lock的灵活性更高

线程通讯

所谓的线程通讯就是在一个线程中的操作可以影响另一个线程,wait(休眠线程),notify(唤醒一个线程),notifyall(唤醒所有线程)

 

 

 wait和notify

多线程的调度过程是充满随机性的,系统源码是改变不了的,所以系统层面上不能解决问题。

但是在实际开发中我们希望合理的协调多线程之间的先后顺序。

通过 wait和notify 机制,来对多线程之间的执行顺序,做出一定的控制。

当某个线程调用 wait 之后,就会阻塞等待。

直到其他某个线程调用 notify 把这个线程唤醒为止。

注意事项:

  • 将lock.notify()修改为lock.notifyAll(),则三个线程都能被唤醒
  • wait在不传递任何参数的情况下会进入waiting状态(参数为0也是waiting状态);当wait里面有一个大于0的整数时,它就会进入timed_waiting状态

生产者和消费者案例

资源类对象:多个线程共同操作的对象
package test8;

/**
 * 资源类对象:多个线程共同操作的对象
 */
public class BaoZi {
    String name;//包子名称
    boolean flag;//包子的状态(true表示存在	false表示不存在)
}
对于吃货-----消费者:
如果包子  不存在   进入等待状态
如果包子   存在    进入执行状态,吃包子。 吃完之后包子变为不存在 此时唤醒早餐店制作包子
package test8;

public class ChiHuo extends Thread {
    BaoZi baoZi;

    //构造方法:用来指定线程的名字和要操作的资源
    public ChiHuo(String name, BaoZi bz) {
        super(name);
        this.baoZi = bz;
    }

    /**
     * 对于吃货-----消费者:
     * 如果包子  不存在   进入等待状态
     * 如果包子   存在    进入执行状态,吃包子。 吃完之后包子变为不存在 此时唤醒早餐店制作包子
     */

    @Override
    public void run() {
        String threadName = Thread.currentThread().getName();
        int count = 0;
        while (true) {
            synchronized (baoZi) {
                count++;
                if (count > 10) {
                    break;
                }
                if (baoZi.flag) {//如果包子存在
                    System.out.println(threadName + "开始吃" + baoZi.name);//吃包子
                    baoZi.flag = false; //修改状态
                    baoZi.notify(); //唤醒同一资源下的其他线程

                } else {//如果包子不存在
                    try {
                        baoZi.wait();//进入等待状态
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
对于早餐店----生产者:
如果包子   不存在   进入执行状态,制作包子。  制作完毕包子存在,唤醒吃货
如果包子    存在    进入等待状态
package test8;

public class ZaoCanDian extends Thread {
    BaoZi baoZi;

    //构造方法:用来指定线程的名字和要操作的资源
    public ZaoCanDian(String name, BaoZi bz) {
        super(name);
        this.baoZi = bz;
    }

    /**
     * 对于早餐店----生产者:
     * 如果包子   不存在   进入执行状态,制作包子。  制作完毕包子存在,唤醒吃货
     * 如果包子    存在    进入等待状态
     */

    @Override
    public void run() {
        //获取当前线程的名称
        String threadName = Thread.currentThread().getName();
        int count = 0;
        while (true) {
            synchronized (baoZi) {
                count++;
                if (count > 10) {
                    break;
                }
                if (baoZi.flag) { //如果包子存在
                    try {
                        baoZi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else { //如果包子不存在
                    System.out.println(threadName + "开始制作" + baoZi.name); //制作包子
                    baoZi.flag = true; //更改包子状态
                    baoZi.notify(); //唤醒同一资源下的其他线程
                }
            }
        }
    }
}

测试

package test8;

public class Test01 {
    public static void main(String[] args){
        //定义资源对象
        BaoZi baoZi=new BaoZi();
        baoZi.name="韭菜鸡蛋";
        baoZi.flag=true;

        //定义两个线程,起名字且操作同一个对象
        ChiHuo ch=new ChiHuo("猪八戒",baoZi);
        ZaoCanDian zcd=new ZaoCanDian("春光早餐",baoZi);

        //启动线程
        zcd.start();
        ch.start();
    }
}

运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值