java学习day17——线程安全

线程安全的概述

多线程访问了共享数据,会产生线程安全的问题
只有多线程会产生安全问题

线程安全产生的原理

在这里插入图片描述

线程安全解决方法

第一种方法——同步代码块

/*
卖票案例出现了线程安全问题,迈出了不存在的票和重复的票
解决线程安全的一种方式:使用同步代码块
格式:
    synchronized(锁对象){
        可能会出现线程安全的代码(访问了共享数据的代码)
    }
注意:
    1. 同步代码块中的锁对象,可以是任意的对象
    2. 必须保证多个线程使用的锁对象是同一个
    3. 锁对象作用:
        把同步代码块所著,只让一个线程在同步代码块中执行
 */
public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private int ticket = 100;
    // 创建一个锁对象,必须创建在run的外面
    Object obj = new Object();
    @Override
    public void run() {
        while(true){
            synchronized (obj){
                if (ticket>0){
                    // 提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 票存在,卖票
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
            }
            }
        }
    }
}
/*
模拟买票
创建三个线程,同时开启,对共享的票进行出售
 */
public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImpl run = new RunnableImpl();
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);

        t1.start();
        t2.start();
        t3.start();
    }
}

同步代码块的原理
在这里插入图片描述

第二种方法——同步方法

/*
第二种方式:使用同步方法
使用步骤:
    1. 把访问了共享数据的代码取出来,放到一个方法中
    2. 在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
    可能会出现线程安全的代码(访问了共享数据的代码)
}
 */
public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private int ticket = 100;
    // 创建一个锁对象,必须创建在run的外面
    Object obj = new Object();
    @Override
    public void run() {
        while(true){
            payTicket();
        }
    }
    // 定义一个同步方法
    public synchronized void payTicket(){
        if (ticket>0) {
            // 提高安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 票存在,卖票
            System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}

同步方法也会把方法内部的代码锁住,只让一个线程执行
同步方法的锁对象是谁? 就是实现类对象new Runnable(),也就是this

第三种方法——静态同步方法(了解)

public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private static int ticket = 100;//静态同步方法访问的变量也必须是静态的
    // 创建一个锁对象,必须创建在run的外面
    Object obj = new Object();
    @Override
    public void run() {
        while(true){
            payTicketStatic();
        }
    }

    /*
    静态同步方法的锁对象是谁?
    不能是this,this是创建对象之后产生的,静态方法优先于对象,
    静态方法的锁对象是本类的class属性——class文件对象(反射)
     */
    public static synchronized void payTicketStatic(){
        if (ticket>0) {
            // 提高安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 票存在,卖票
            System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}

第四种方法——Lock锁

/*
解决线程安全的第四种解决方案:Lock锁
java.util.concurrent.locks
Lock实现提供了比使用synchronized方法和语句可获得更广泛的锁定操作
Lock接口种的方法:
 void lock() 获取锁。
 void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock implements Lock接口
使用步骤:
    1. 在成员位置创建一个ReentrantLock对象
    2. 在可能会出现安全问题的代码前调用Lock接口种的方法Lock获取锁
    3. 在可能会出现安全问题的代码后调用Lock接口种的方法unLock释放锁
 */
public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private int ticket = 100;
    // 1. 在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();
    @Override
    public void run() {
        while(true){
            // 2. 在可能会出现安全问题的代码前调用Lock接口种的方法Lock获取锁
            l.lock();
            if (ticket>0) {
                // 提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                    // 票存在,卖票
                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    // 无论程序是否异常,否会把锁释放
                    //3. 在可能会出现安全问题的代码后调用Lock接口种的方法unLock释放锁
                    l.unlock();
                }
            }
        }
    }
}

线程状态

在这里插入图片描述
TimeWaiting线程状态图
在这里插入图片描述
Blocked状态
在这里插入图片描述

Waiting(无限等待)

等待唤醒案例代码实现

/*
等待唤醒案例:线程之间的通信
    创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,进入到WAITING状态(无限等待)
    创建一个老板线程(生产者):花了5秒做包子,做好包子后,调用notify方法,唤醒顾客吃包子
注意:
    顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能由一个在执行
    同步使用的锁对象必须保证唯一
    只有锁对象才能调用wait和notify方法
Object类种的方法
 void wait()
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
 void notify()
          唤醒在此对象监视器上等待的单个线程。
 */
public class Demo01WaitAndNotify {
    public static void main(String[] args) {
        Object obj = new Object();
        // 顾客的线程
        new Thread(){
            @Override
            public void run() {
                // 保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj){
                    System.out.println("告知老板要的包子的种类和数量");
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 唤醒之后执行的代码
                    System.out.println("开吃");
                }
            }
        }.start();
        // 老板的线程
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);//画5s做包子
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj){
                    System.out.println("告知顾客,可以吃包子");
                    obj.notify();
                }
            }
        }.start();
    }
}

进入到TimeWaiting(计时等待)有两种方式

  1. 使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
  2. 使用wait(long m)方法如果在毫秒值结束后还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
    唤醒方法:
    void notify() 唤醒在此对象监视器上等待的单个线程。有多个等待线程时随机唤醒一个
    void notifyAll() 唤醒在此对象监视器上等待的所有线程。

线程间通信——等待唤醒机制

在这里插入图片描述
在这里插入图片描述

线程池

频繁创建线程和销毁线程需要时间
在这里插入图片描述

/*
线程池:JDK1.5之后提供的
java.util.concurrent.Executors:线程池的工厂类,用来生成线程池
Executors类种的静态方法
    static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
    返回值:
        ExecutorService接口:返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
    java.util.concurrent.ExecutorService:线程池接口
        用来从线程池获取线程,调用start方法,执行线程任务
        submit(Runnable task) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
        关闭线程池的方法:
            void shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
        线程池的使用步骤:
            1. 使用线程池的工厂类Executors里面提供的newFixedThreadPool生产一个指定线程数量的线程池
            2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
            3. 调用ExecutorService中的submit,传递线程任务(实现类),开启线程,执行run方法
            4. 调用ExecutorService中的shutdown,销毁线程池(不建议使用)
 */
public class Demo01ThreadPool {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(2);
        es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行
        // 线程池会一直开启,会自动把线程归还给线程池
        es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程执行
        es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程执行
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值