Java多线程 - - -线程安全问题

Java- - - 多线程学习笔记(二)

线程安全问题的出现

当一个进程中的多个线程共享资源或数据的时候,就会出现安全隐患
例如,三个售票窗口同时售票,如果没有进行线程安全的处理,则会出现重票,错票等线程安全问题

package com.fff;

//实现Runnable接口
class TicketWindow implements Runnable{
    private static int ticketCount = 100;//三个线程的共享资源,用static

    //实现接口中的抽象方法
    @Override
    public void run() {
        while(true){
            if(ticketCount > 0){
                try {
                //使用sleep方法,使当前进程阻塞100ms,方便通过输出结果查看线程的状态
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":卖票---票号为:"+ticketCount);
                ticketCount--;
            }else {
                break;
            }
        }
    }
}
public class Window {
    public static void main(String[] args){
        //创建实现类的对象
        TicketWindow ticketWindow = new TicketWindow();

        //将此对象作为参数传递到Thread类的构造器中
        Thread t1 = new Thread(ticketWindow);
        Thread t2 = new Thread(ticketWindow);
        Thread t3 = new Thread(ticketWindow);

        //设置三个线程的名字
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        //通过Thread类的对象调用start()方法,启动三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述
通过输出结果可发现,三个窗口也就是三个进程在切换执行的过程中,出现错票和重票的情况,这就说明当前多线程任务是不安全的。

解决线程安全问题

在Java中,通过同步机制解决线程安全问题。

方式一:同步代码块

syncchronized(同步监视器){
//需要被同步的代码
}
说明**:操作共享数据的代码即为需要被同步的代码。**
共享数据:多个线程共同操作的变量;
同步监视器:俗称“”,任何一个类的对象都可以充当锁。
要求:多个线程必须共用同一把锁;

package com.fff;

//实现Runnable接口
class TicketWindow implements Runnable{
    private static int ticketCount = 100;//三个线程的共享资源,用static
    Object obj  = new Object();//任何一个类的对象都可以充当锁;
    //实现接口中的抽象方法
    @Override
    public void run() {
        while(true){
            synchronized (obj){
                //需要被同步的代码
                if(ticketCount > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":卖票---票号为:"+ticketCount);
                    ticketCount--;
                }else {
                    break;
                }
            }

        }
    }
}
public class Window {
    public static void main(String[] args){
        //创建实现类的对象
        TicketWindow ticketWindow = new TicketWindow();

        //将此对象作为参数传递到Thread类的构造器中
        //一下三个线程共用一个实现类的对象,因此三个线程共用一把锁
        Thread t1 = new Thread(ticketWindow);
        Thread t2 = new Thread(ticketWindow);
        Thread t3 = new Thread(ticketWindow);

        //设置三个线程的名字
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        //通过Thread类的对象调用start()方法,启动三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述
通过结果发现,没有再出现重票错票的情况,当一个线程没有执行完,哪怕是发生了阻塞,别的线程也不会执行,而是要等到当前线程执行完毕,其他的线程才会执行。
**注意:**在通过同步代码块的方式解决继承Thread类的线程安全问题时,一定要注意锁必须是唯一的。

方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的;

package com.fff;

//实现Runnable接口
class TicketWindow implements Runnable{
    private static int ticketCount = 100;//三个线程的共享资源,用static
    Object obj  = new Object();//任何一个类的对象都可以充当锁;
    //实现接口中的抽象方法
    @Override
    public void run() {
        while(true){
            this.show();
        }
    }
    private synchronized void show(){//同步监视器:this
        if(ticketCount > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":卖票---票号为:"+ticketCount);
            ticketCount--;
        }
    }
}
public class Window {
    public static void main(String[] args){
        //创建实现类的对象
        TicketWindow ticketWindow = new TicketWindow();

        //将此对象作为参数传递到Thread类的构造器中

        Thread t1 = new Thread(ticketWindow);
        Thread t2 = new Thread(ticketWindow);
        Thread t3 = new Thread(ticketWindow);

        //设置三个线程的名字
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        //通过Thread类的对象调用start()方法,启动三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}

同步方法仍然涉及到同步监视器,只不过不需要显示的声明。
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
**注意:**当我们用同步方法方式解决继承Thread线程问题时,要注意,同步方法要是静态的,保证用的是同一个锁,所有的线程共用一个同步方法,同一个锁。

方式三:Lock锁

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。
ReentrantLock类实现了Lock,可以显示加锁,释放锁。

package com.fff;

import java.util.concurrent.locks.ReentrantLock;

//实现Runnable接口
class TicketWindow implements Runnable{
    private static int ticketCount = 100;//三个线程的共享资源,用static

    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();
    //实现接口中的抽象方法
    @Override
    public void run() {
        while(true){
            try{

                //2.调用锁定方法lock()
                lock.lock();
                if(ticketCount > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":卖票---票号为:"+ticketCount);
                    ticketCount--;
                }else {
                    break;
                }
            }finally {
                //3.调用解锁方法unlock()
                lock.unlock();
            }



        }
    }
}
public class Window {
    public static void main(String[] args){
        //创建实现类的对象
        TicketWindow ticketWindow = new TicketWindow();

        //将此对象作为参数传递到Thread类的构造器中

        Thread t1 = new Thread(ticketWindow);
        Thread t2 = new Thread(ticketWindow);
        Thread t3 = new Thread(ticketWindow);

        //设置三个线程的名字
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        //通过Thread类的对象调用start()方法,启动三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}

synchronized和Lock的区别

synchronized机制在执行完相应的同步代码之后,自动的释放同步监视器;
Lock需要手动的启动同步,同时结束同步也需要手动的实现

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值