Java开发——37.多线程_(线程安全)

多线程

进程:系统分配资源的单位;
线程:处理器任务调度和执行的单位,线程直接共享进程资源。

安全隐患问题:

线程不同步问题:

​问题:会出现多个窗口买一张票的情况/卖错票(票号为0/-1)

​问题出现原因:一个线程已经运行了,但是还没有执行ticket--,后续线程也进来了,导致他们操作了同一张票号...

​解决:如果一个线程已经运行了,但是在运行的过程中阻塞了,也不会给后续线程分配CPU资源,直至该线程运行完毕。

线程安全的两种方法:

1.方式一:同步代码块(synchronized);
2.方式二:同步方法。

在这里插入图片描述

注意:同步代码块和同步方法统一的规则:不能包含过多的代码,也不能包含过少的代码,只包含存在安全隐患的代码。

1.方式一:同步代码块;

书写格式:

synchronized(同步监视器){
  //需要同步的代码块
}/**
 *   参数说明:
 *        1.需要被同步的代码:操作共享数据的代码(注意不能多包含代码,也不能少包含代码);
 *            共享数据:多个线程共同操作的变量(例如,ticket票号)
 *        2.同步监视器,俗称:锁。 任何类型的对象都能充当锁
 *           要求:所有的线程必须保证共享一把锁。
 *              注意类也是对象(在学习反射的时候会详讲),可以使用(类名.class)来同步监视器;
 *                  类只会被加载一次。
 *              注意使用继承Thread类时 慎用(this)来充当同步监视器;
 *        解释:(卫生间的锁不能从外面决定关锁/解锁,只能被进卫生间的人从里面关锁/解锁,也就是一个卫生间在同一时间的关锁/解锁只能被一个已经进入卫生间的人操作)。
 */
满足条件:

1.多个线程共用一把锁;

2.任何类型的对象都能充当同步监视器(锁);

3.需要同步的代码,不能多包也不能少包。

1.1.继承Thread类
public class PraSaleTicket {public static void main(String[] args) {Sale sale01 = new Sale();
        Sale sale02 = new Sale();
        Sale sale03 = new Sale();
​
        sale01.setName("窗口01");
        sale02.setName("窗口02");
        sale03.setName("窗口03");
​
        sale01.start();
        sale02.start();
        sale03.start();}}class Sale extends Thread{private static int ticket = 100;
    Object myLock = new Object();@Override
    public void run() {
//        synchronized (myLock) { // 不能多包含代码,也不能少包含代码
        while (true){
            synchronized (Sale.class) {//当前类为静态类,只能加载一次,所以可以让当前类作为锁
                if (ticket > 0) {try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }System.out.println(getName() + "_票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }}
}
1.2.实现Runnable接口
public class PraSaleTicket_Runnable {public static void main(String[] args) {SaleTickets sale = new SaleTickets();Thread thread = new Thread(sale);
        Thread thread2 = new Thread(sale);
        Thread thread3 = new Thread(sale);
​
        thread.setName("窗口01");
        thread2.setName("窗口02");
        thread3.setName("窗口03");
​
        thread.start();
        thread2.start();
        thread3.start();}
}class SaleTickets implements Runnable{private int ticket = 100;
    //    Object olock = new Object(); // 任何类型的对象都能充当锁。
    MyLock myLock = new MyLock();@Override
    public void run() {//    错误的存放位置:Object olock = new Object(); // 任何类型的对象都能充当锁。
        while (true) {
    //    错误的存放位置:Object olock = new Object(); // 任何类型的对象都能充当锁。synchronized(myLock) {if (ticket > 0) {//如果票号大于0,假设现在票号为1,线程1抢到了CPU,但是还没往后面走,线程2页签到了资源,线程3也抢到了...导致输出的票号出现了:1 0 -1 的现象。
                    //如果此处没有调用sleep(),也会出现重复票号的问题...
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }System.out.println(Thread.currentThread().getName() + "_票号为:" + ticket);
​
                    ticket--;
                } else {
                    break;
                }}
        }
    }
}
//任何类型的对象都能作为同步监视器。
class MyLock{}

2.方式二:同步方法;

将存在线程安全隐患的代码形成一个方法,即为同步方法。

同步方法写法:
修饰符 (static) synchronized 返回值类型 方法名(){}
注意事项:

1.多个线程共用一把锁;
2.需要同步的代码,不能多包也不能少包。
3.静态的同步方法:默认使用的同步监视器是,当前类.class;

4.非静态的同步方法:默认使用的同步监视器是,this,指代当前类。

注意此时将需要同步的方法,使用了同步方法来保证线程安全,针对买100张票的话,我们没办法终止循环,所以在循环结束后需要手动终止循环!!!

2.1.继承Thread类​
public class PraSaleTicket02 {public static void main(String[] args) {Sales sale01 = new Sales();
        Sales sale02 = new Sales();
        Sales sale03 = new Sales();
​
        sale01.setName("窗口01");
        sale02.setName("窗口02");
        sale03.setName("窗口03");
​
        sale01.start();
        sale02.start();
        sale03.start();}}class Sales extends Thread{private static int ticket = 100;@Override
    public void run() {
        while (true){
            showTicket();
        }
    }private static synchronized void showTicket(){//默认的同步监视器是当前类(Sales.class)
//        private synchronized void showTicket(){ 
//使用该方法是错的,因为这样是默认传入的同步监视器是this(也就是sale01,sale02,sale03)
//不能保证多个线程共用一把锁,所以线程是不安全的。
        if (ticket > 0) {try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }System.out.println(Thread.currentThread().getName() + "_票号为:" + ticket);
            ticket--;
        }
    }}
2.2.实现Runnable接口
public class PraSaleTicket_Runnable02 {public static void main(String[] args) {SaleTicket sale = new SaleTicket();Thread thread = new Thread(sale);
        Thread thread2 = new Thread(sale);
        Thread thread3 = new Thread(sale);
​
        thread.setName("窗口01");
        thread2.setName("窗口02");
        thread3.setName("窗口03");
​
        thread.start();
        thread2.start();
        thread3.start();}
}class SaleTicket implements Runnable{private int ticket = 100;@Override
    public void run() {while (true) {
            showTickets();
        }}private synchronized void showTickets(){//默认的同步监视器是SaleTicket.class
        if (ticket > 0) {try {
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }System.out.println(Thread.currentThread().getName() + "_票号为:" + ticket);
​
            ticket--;
        }
    }

线程安全的好处​/局限:

好处:解决线程安全问题;
局限:让线程效率低,相当于把多线程问题变成了单线程的问题。

欢迎关注微信公众号:小红的成长日记,一起学Java!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值