Java多线程0:核心理论

Posted 吹灭读书灯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程0:核心理论相关的知识,希望对你有一定的参考价值。

  并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能。它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰、思维缜密,这样才能写出高效、安全、可靠的多线程并发程序。本系列会从线程间协调的方式(wait、notify、notifyAll)、Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制。在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式、实现源码及其背后的原理。本文是该系列的第一篇文章,是这系列中最核心的理论部分,之后的文章都会以此为基础来分析和解释。

一、共享性

  数据共享是为什么要考虑线程安全的主要原因之一。如果所有的数据只是在当前线程内有效,那就不需要考虑线程安全问题。但是,在多线程编程中,数据共享是不可避免的。比如夫妻双方一人在柜台取钱,一人在ATM上取钱,两个取钱线程共享账户中的余额这一变量,这时候就要考虑线程安全问题了。

  举例1:以银行取钱为例说明多线程之间的数据共享

  定义一个账户Account,成员变量为账户编号和余额,以及一个取钱的方法,方法中对取钱金额做了判断,只有取的钱<=账户余额的时候,才能取钱成功。

public class Account {

    //账户编号
    private int accountNo;
    //账户余额
    private int balance;

    public Account(int accountNo, int balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    //取钱方法
    public void drawMoney(int drawMoneyCount){
        if(balance >= drawMoneyCount){
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款成功");
                this.balance = balance - drawMoneyCount;
                System.out.println("账户余额为===" + (this.balance));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }else{
            System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款失败,余额不足");
        }
    }
}

  定义一个取钱线程类:

public class Thread03 extends Thread{

    private Account account;

    private int drawMoneyCount;
    
    //初始化账户余额和取钱金额
    public Thread03(int drawMoneyCount,Account account){
        this.drawMoneyCount = drawMoneyCount;
        this.account = account;
    }

    @Override
    public void run() {
        account.drawMoney(drawMoneyCount);
    }
}

  测试,定义两个取钱线程

public class Test {
    public static void main(String[] args) {
        Account account = new Account(123456789,800);
        //取钱线程1
        Thread thread1 = new Thread03(500,account);
        thread1.setName("张三");
        //取钱线程2
        Thread thread2 = new Thread03(500,account);
        thread2.setName("李四");
        thread1.start();
        thread2.start();
    }
}

  结果:

张三取款金额为 = 500,取款成功
账户余额为===300
李四取款金额为 = 500,取款成功
账户余额为===-200

  说明:可以看到,初始账户余额为800,张三取了500,账户余额还剩300,李四取500的时候,按说已经对取钱金额进行校验,不应该取钱成功,但李四还是取出了500。账户余额还剩-300。这是因为两个取钱的线程同时进入到Account的drawMoney方法内部,校验金额的时候都是800>=500,所以都能取钱成功。

  解决方法就是对取钱方法进行同步,用synchronized修饰,此处先看一下结果,后续会详细解释synchronized锁机制。

public class Account {

    //账户编号
    private int accountNo;
    //账户余额
    private int balance;

    public Account(int accountNo, int balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    //取钱方法
    public synchronized void drawMoney(int drawMoneyCount){
        if(balance >= drawMoneyCount){
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款成功");
                this.balance = balance - drawMoneyCount;
                System.out.println("账户余额为===" + (this.balance));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }else{
            System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款失败,余额不足");
        }
    }
}

  其余都不变,看一下结果:

张三取款金额为 = 500,取款成功
账户余额为===300
李四取款金额为 = 500,取款失败,余额不足

  所以,多线程情况下对共享变量的操作,要考虑线程安全问题。

  举例1:以银行存钱取钱为例说明多线程之间的数据共享

public class Account {

    //账户编号
    private int accountNo;
    //账户余额
    private int balance;

    public Account(int accountNo, int balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    //取钱方法
    public void drawMoney(int drawMoneyCount){
        if(balance >= drawMoneyCount){
            System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款成功");
            this.balance = balance - drawMoneyCount;
            System.out.println("账户余额为===" + (this.balance));

        }else{
            System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款失败,余额不足");
        }
    }

    //存钱方法
    public void depositMoney(int depositMoneyCount){
        System.out.println(Thread.currentThread().getName() + "存钱成功,余额为 = " + (this.balance + depositMoneyCount));
        this.balance = balance + depositMoneyCount;
    }
}

 

 

public class Thread03 extends Thread{

    private Account account;

    private int drawMoneyCount;

    //初始化账户余额和取钱金额
    public Thread03(int drawMoneyCount,Account account){
        this.drawMoneyCount = drawMoneyCount;
        this.account = account;
    }

    @Override
    public void run() {
        account.drawMoney(drawMoneyCount);
    }
}

 

 

 

public class Thread04 extends Thread{

    private Account account;

    private int depositMoneyCount;

    //初始化账户余额和存钱金额
    public Thread04(int depositMoneyCount, Account account){
        this.depositMoneyCount = depositMoneyCount;
        this.account = account;
    }

    @Override
    public void run() {
        account.depositMoney(depositMoneyCount);
    }
}
public class Thread04 extends Thread{

    private Account account;

    private int depositMoneyCount;

    //初始化账户余额和存钱金额
    public Thread04(int depositMoneyCount, Account account){
        this.depositMoneyCount = depositMoneyCount;
        this.account = account;
    }

    @Override
    public void run() {
        account.depositMoney(depositMoneyCount);
    }
}

 

 

public class Test {
    public static void main(String[] args) {
        Account account = new Account(123456789,0);
        //存钱线程
        Thread thread1 = new Thread04(200,account);
        thread1.setName("张三");
        //取钱线程
        Thread thread2 = new Thread03(100,account);
        thread2.setName("李四");
        thread1.start();
        thread2.start();
    }
}

 

 

张三存钱成功,余额为 = 200
李四取款金额为 = 100,取款失败,余额不足

 

public class Account {

    //账户编号
    private int accountNo;
    //账户余额
    private int balance;

    public Account(int accountNo, int balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    //取钱方法
    public synchronized void drawMoney(int drawMoneyCount){
        if(balance >= drawMoneyCount){
            System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款成功");
            this.balance = balance - drawMoneyCount;
            System.out.println("账户余额为===" + (this.balance));

        }else{
            System.out.println(Thread.currentThread().getName() + "取款金额为 = " + drawMoneyCount + ",取款失败,余额不足");
        }
    }

    //存钱方法
    public synchronized void depositMoney(int depositMoneyCount){
        System.out.println(Thread.currentThread().getName() + "存钱成功,余额为 = " + (this.balance + depositMoneyCount));
        this.balance = balance + depositMoneyCount;
    }
}

 

 

张三存钱成功,余额为 = 200
李四取款金额为 = 100,取款成功
账户余额为===100

 

 

 

 

 

 

 

 

 

 

public class ShareData {
    private int num;

    public int getNum() {
        return num;
    }

    public void addNum(){
        try {

            Thread.sleep(100);
            for(int i = 1; i <= 10; i++) {
                System.out.println("    " + Thread.currentThread().getName() + "进入方法时间===" + System.currentTimeMillis() + ",共享的num===" + (num + 1) + ",该线程对应i===" + i);
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "的num===" + num);
    }
}

 

public class Thread01 extends Thread{
    private ShareData shareData;

    public Thread01(ShareData shareData) {
        this.shareData = shareData;
    }

    @Override
    public void run() {
        shareData.addNum();
    }
}

 

public class Test {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        Thread thread1 = new Thread01(shareData);
        Thread thread2 = new Thread01(shareData);
        Thread thread3 = new Thread01(shareData);
        Thread thread4 = new Thread01(shareData);
        Thread thread5 = new Thread01(shareData);
        Thread thread6 = new Thread01(shareData);
        Thread thread7 = new Thread01(shareData);

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();
        thread6.start();
        thread7.start();

    }
}

 

 

 

    Thread-0进入方法时间===1553238253266,共享的num===1,该线程对应i===1
    Thread-2进入方法时间===1553238253266,共享的num===1,该线程对应i===1
    Thread-1进入方法时间===1553238253266,共享的num===1,该线程对应i===1
    Thread-3进入方法时间===1553238253266,共享的num===1,该线程对应i===1
    Thread-1进入方法时间===1553238253266,共享的num===4,该线程对应i===2
    Thread-2进入方法时间===1553238253266,共享的num===3,该线程对应i===2
    Thread-0进入方法时间===1553238253266,共享的num===2,该线程对应i===2
    Thread-0进入方法时间===1553238253267,共享的num===8,该线程对应i===3
    Thread-0进入方法时间===1553238253267,共享的num===9,该线程对应i===4
    Thread-0进入方法时间===1553238253267,共享的num===10,该线程对应i===5
    Thread-0进入方法时间===1553238253267,共享的num===11,该线程对应i===6
    Thread-0进入方法时间===1553238253267,共享的num===12,该线程对应i===7
    Thread-0进入方法时间===1553238253267,共享的num===13,该线程对应i===8
    Thread-0进入方法时间===1553238253267,共享的num===14,该线程对应i===9
    Thread-0进入方法时间===1553238253267,共享的num===15,该线程对应i===10
Thread-0的count===15
    Thread-5进入方法时间===1553238253267,共享的num===16,该线程对应i===1
    Thread-5进入方法时间===1553238253267,共享的num===17,该线程对应i===2
    Thread-5进入方法时间===1553238253267,共享的num===18,该线程对应i===3
    Thread-2进入方法时间===1553238253267,共享的num===7,该线程对应i===3
    Thread-4进入方法时间===1553238253267,共享的num===6,该线程对应i===1
    Thread-1进入方法时间===1553238253266,共享的num===6,该线程对应i===3
    Thread-3进入方法时间===1553238253266,共享的num===5,该线程对应i===2
    Thread-1进入方法时间===1553238253268,共享的num===22,该线程对应i===4
    Thread-4进入方法时间===1553238253268,共享的num===21,该线程对应i===2
    Thread-2进入方法时间===1553238253268,共享的num===20,该线程对应i===4
    Thread-5进入方法时间===1553238253267,共享的num===19,该线程对应i===4
    Thread-6进入方法时间===1553238253267,共享的num===18,该线程对应i===1
    Thread-5进入方法时间===1553238253268,共享的num===27,该线程对应i===5
    Thread-2进入方法时间===1553238253268,共享的num===26,该线程对应i===5
    Thread-4进入方法时间===1553238253268,共享的num===25,该线程对应i===3
    Thread-1进入方法时间===1553238253268,共享的num===24,该线程对应i===5
    Thread-3进入方法时间===1553238253268,共享的num===23,该线程对应i===3
    Thread-1进入方法时间===1553238253268,共享的num===32,该线程对应i===6
    Thread-4进入方法时间===1553238253268,共享的num===31,该线程对应i===4
    Thread-2进入方法时间===1553238253268,共享的num===30,该线程对应i===6
    Thread-5进入方法时间===1553238253268,共享的num===29,该线程对应i===6
    Thread-6进入方法时间===1553238253268,共享的num===28,该线程对应i===2
    Thread-5进入方法时间===1553238253268,共享的num===37,该线程对应i===7
    Thread-2进入方法时间===1553238253268,共享的num===36,该线程对应i===7
    Thread-4进入方法时间===1553238253268,共享的num===35,该线程对应i===5
    Thread-1进入方法时间===1553238253268,共享的num===34,该线程对应i===7
    Thread-3进入方法时间===1553238253268,共享的num===33,该线程对应i===4
    Thread-1进入方法时间===1553238253268,共享的num===42,该线程对应i===8
    Thread-4进入方法时间===1553238253268,共享的num===41,该线程对应i===6
    Thread-2进入方法时间===1553238253268,共享的num===40,该线程对应i===8
    Thread-5进入方法时间===1553238253268,共享的num===39,该线程对应i===8
    Thread-6进入方法时间===1553238253268,共享的num===38,该线程对应i===3
    Thread-5进入方法时间===1553238253269,共享的num===47,该线程对应i===9
    Thread-2进入方法时间===1553238253269,共享的num===46,该线程对应i===9
    Thread-4进入方法时间===1553238253268,共享的num===45,该线程对应i===7
    Thread-1进入方法时间===1553238253268,共享的num===44,该线程对应i===9
    Thread-3进入方法时间===1553238253268,共享的num===43,该线程对应i===5
    Thread-1进入方法时间===1553238253269,共享的num===52,该线程对应i===10
    Thread-4进入方法时间===1553238253269,共享的num===51,该线程对应i===8
    Thread-2进入方法时间===1553238253269,共享的num===50,该线程对应i===10
Thread-2的count===55
    Thread-5进入方法时间===1553238253269,共享的num===49,该线程对应i===10
Thread-5的count===56
    Thread-6进入方法时间===1553238253269,共享的num===48,该线程对应i===4
    Thread-6进入方法时间===1553238253271,共享的num===58,该线程对应i===5
    Thread-6进入方法时间===1553238253271,共享的num===59,该线程对应i===6
    Thread-6进入方法时间===1553238253271,共享的num===60,该线程对应i===7
    Thread-4进入方法时间===1553238253269,共享的num===55,该线程对应i===9
Thread-1的count===53
    Thread-3进入方法时间===1553238253269,共享的num===53,该线程对应i===6
    Thread-4进入方法时间===1553238253271,共享的num===62,该线程对应i===10
    Thread-6进入方法时间===1553238253271,共享的num===61,该线程对应i===8
Thread-4的count===63
    Thread-3进入方法时间===1553238253271,共享的num===63,该线程对应i===7
    Thread-3进入方法时间===1553238253271,共享的num===66,该线程对应i===8
    Thread-6进入方法时间===1553238253271,共享的num===65,该线程对应i===9
    Thread-6进入方法时间===1553238253272,共享的num===68,该线程对应i===10
Thread-6的count===68
    Thread-3进入方法时间===1553238253271,共享的num===67,该线程对应i===9
    Thread-3进入方法时间===1553238253272,共享的num===70,该线程对应i===10
Thread-3的num===70

 

以上是关于Java多线程0:核心理论的主要内容,如果未能解决你的问题,请参考以下文章

Java 并发编程:核心理论

Java 并发编程:核心理论

《Java多线程编程核心技术一》--- 快速认识线程

Java多线程具体解释

线程池

java多线程