Java SE多线程(线程安全与sychornized)

本文详细介绍Java中多线程的基础知识,包括线程调度模型、常见方法及优先级设置。重点探讨多线程并发环境下的数据安全问题,分析线程安全问题出现的原因及其解决方法。同时介绍了异步与同步编程模型的区别,以及如何正确使用synchronized关键字来确保线程安全。

若初次接触移至多线程请先移至JavaSE 多线程初次接触

1.作为了解的知识

1.1 常见的线程调度模型有哪些?
  • 抢占式调度:
    抢占式。现行进程在运行过程中,如果有重要或紧迫的进程到达(其状态必须为就绪),则现运行进程将被迫放弃处理器,系统将处理器立刻分配给新到达的进程。
  • 非抢占式:
    非抢占式让原来正在运行的进程继续运行,直至该进程完成或发生某种事件(如I/O请求),才主动放弃处理机。
1.2 和线程调度有关的方法
  • 实例方法
    void setPriority(int newPriority) 设置线程的优先级
    int getPriority() 获取线程优先级
    最低优先级1默认优先级是5最高优先级10
    优先级比较高的获取CPU时间片大概率会多一些。(但也不完全是,大概率是多的。)
  • 静态方法
    static void yield() 让位方法
    暂停当前正在执行的线程对象,并执行其他线程
    yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
    yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
    注意:在回到就绪之后,有可能还会再次抢到。

2.关于多线程并发环境下,数据的安全问题(重点)。

2.1为什么这个是重点

以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。

2.2什么时候会出现线程安全问题

满足以下三个条件,就会存在线程安全问题:

  • 多线程并发。
  • 有共享数据。
  • 共享数据有修改的行为。

2.3如何解决线程安全问题?

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题。
线程排队执行。(不能并发)。用排队执行解决线程安全问题。这种机制被称为:线程同步机制。也称为线程同步,实际上就是线程不能并发了,线程必须排队执行。使用“线程同步机制”可以解决线程安全问题。

2.4异步编程模型与同步编程模型

异步编程模型:
		线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,
		谁也不需要等谁,这种编程模型叫做:异步编程模型。
		这个就可以理解为:多线程并发(效率较高。)
		异步就是并发。

同步编程模型:
		线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行
		结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,
		两个线程之间发生了等待关系,这就是同步编程模型。
		效率较低。线程排队执行。
		同步就是排队

3.Java中三大变量

  • 实例变量:在堆中。

  • 静态变量:在方法区。

  • 局部变量:在栈中。

在以上三大变量中:
局部变量不会存在线程安全问题
原因:
局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。

实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。

4.如果使用局部变量:

建议使用:StringBuilder
因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。

ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable是线程安全的。

5.sychornized(尽我可能写的详细)

5.1sychornized理解概念

(若深入理解请先了解对象锁(monitor)机制
线程同步机制的语法是:

synchronized(){
            // 线程同步代码块。
        }

synchronized后面小括号中传的这个“数据”是相当关键的。
这个数据必须是多线程共享的数据。才能达到多线程排队。

()中写什么?
那要看你想让哪些线程同步。
假设t1、t2、t3,有3个线程,你只希望t1 t2排队,t3不需要排队。怎么办?
这时候要在()中写一个t1 t2共享的对象。而这个对象对于t3来说不是共享的。

这里的共享对象是:账户对象。
账户对象是共享的,那么this就是账户对象。
不一定是this,这里只要是多线程共享的那个对象就行。

在java语言中,任何一个对象都有“一把锁”,这把锁就是标记。(暂时把它叫做锁。)
100个对象,100把锁。1个对象1把锁。

  • synchronized有三种写法:
  1. 同步代码块
    灵活
    synchronized(线程共享对象){
    同步代码块;
    }

  2. 在实例方法上使用synchronized
    表示共享对象一定是this
    并且同步代码块是整个方法体。

  3. 在静态方法上使用synchronized
    表示找类锁。
    类锁永远只有1把。
    就算创建了100个对象,那类锁也只有一把。

对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。

首先,我们来看一个账户类

public class Account {
    // 余额
    private double balance; //实例变量。

    //对象
    Object obj = new Object(); // 实例变量。(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的。)
    public Account(double balance) {
        this.balance = balance;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }

    //取款的方法
    public void withdraw(double money){
    double before = this.getBalance();
    		synchronized (obj){ 
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        //}
        }
    }

然后我们再来看主函数(AccountThread的run方法调用withdraw函数,就不写了)

public class Test {
    public static void main(String[] args) {
        // 创建账户对象(只创建1个)
        Account act = new Account("act-001", 10000);
        // 创建两个线程
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);

        // 设置name
        t1.setName("t1");
        t2.setName("t2");
        // 启动线程取款
        t1.start();
        t2.start();
    }
}

以上代码的执行原理?

  1. 假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
  2. 假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
  3. 假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1线程执行结束,t1把同步代码块执行完后,t1会归还这把锁,此时t2拿到这把锁,随即t2占有这把锁之后,进入同步代码块执行程序。
    这样就达到了线程排队执行。

这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。

开发中应该怎么解决线程安全问题?(老师讲解)

是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。

  • 第一种方案:尽量使用局部变量代替“实例变量和静态变量”。

  • 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
    实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,
    对象不共享,就没有数据安全问题了。)

  • 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候
    就只能选择synchronized了。线程同步机制。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值