本系列参考资料《JAVA多线程编程核心技术》
关注公众号:雨中散步撒哈拉,回复:025
进行下载资料

[toc]

前言
想要多线程实现的现象
image.png

实际运行的过程
image.png

一、实例变量与线程安全

自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间进行交互时是很重要的一个技术点。

1. 不共享数据情况

image.png

1. 编写run方法

package com.multithreading.demo;

public class MyThreadCount extends Thread {

    private int count = 5;
    public MyThreadCount(String name){
        super();
        this.setName(name);
    }

    @Override
    public void run() {
        super.run();
        while (count > 0){
            count--;
            System.out.println("由" + currentThread().getName() + "计算, count = " + count);
        }
    }
}

2. 编写调用代码

package com.multithreading.demo;

public class MyThreadCountTest {

    public static void main(String[] args) {
        MyThreadCount a = new MyThreadCount("A");
        MyThreadCount b = new MyThreadCount("B");
        MyThreadCount c = new MyThreadCount("C");
        a.start();
        b.start();
        c.start();
    }
}

3. 执行并查看结果

image.png

查看结果发现,A,B,C三个线程,都实现了从5一直减到0,三个线程互不影响!

一共创建了3个线程,每个线程都有各自的count变量,自己减少自己的count变量的值。这样的情况就是变量不共享,此示例并不存在多个线程访问同一个实例变量的情况。

2. 共享数据情况

image.png

共享数据的情况就是多个线程可以访问同一个变量,比如在实现投票功能的软件时,多个线程可以同时处理同一个人的票数。

1. 编写run代码

package com.multithreading.demo;

public class MyThreadCount2 extends Thread {

    private int count = 5;

    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由" + currentThread().getName() + "计算, count = " + count);
    }
}

2. 编写调用代码

package com.multithreading.demo;

public class MyThreadCountTest2 {

    public static void main(String[] args) {
        MyThreadCount2 myThreadCount = new MyThreadCount2();
        Thread a = new Thread(myThreadCount, "A");
        Thread b = new Thread(myThreadCount, "B");
        Thread c = new Thread(myThreadCount, "C");
        Thread d = new Thread(myThreadCount, "D");
        Thread e = new Thread(myThreadCount, "E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

3. 执行并分析结果

image.png

线程A和B打印出的count值都是3,说明A和B同时对count进行处理,产生了“非线程安全”问题。而我们想要得到的打印结果却不是重复的,而是依次递减的。发生混乱怎么解决呢?

4. 解决混乱

通过在run方法前加入 synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。当一个线程调用run前,先判断run方法有没有被上锁,如果上锁,说明有其他线程正在调用run方法,必须等其他线程对run方法调用结束后才可以执行run方法。这样也就实现了排队调用run方法的目的,也就达到了按顺序对count变量减1的效果了。synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。

当一个线程想要执行同步方法里面的代码时,线程首先尝试去拿这把锁,如果能够拿到这把锁,那么这个线程就可以执行synchronize里面的代码。如果不能拿到这把锁,那么这个线程就会不断地尝试拿这把锁,直到能够拿到为止,而且是有多个线程同时去争抢这把锁。

本节中出现了一个术语“非线程安全”。非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。下面再用一个示例来学习一下如何解决“非线程安全”问题。

run方法添加synchronized关键词,实现线程安全!

执行并查看结果
image.png

二、i++引发的混乱

虽然println()方法在内部是同步的,但i--的操作却是在进人printIn()之前发生的,所以有发生非线程安全问题的概率。
在某些JVM中,i--的操作要分成如下3步:

  1. 取得原有i值。
  2. i值计算。
  3. i进行赋值

1. 编写run代码

package com.multithreading.demo;

public class MyThreadCount3 extends Thread {

    private int count = 5;

    @Override
    public  void run() {
        super.run();
        System.out.println("i = " + (count--) + "threadName = " + currentThread().getName() + "计算, count = " + count);
    }
}

2. 编写调用代码

package com.multithreading.demo;

public class MyThreadCountTest3 {

    public static void main(String[] args) {
        MyThreadCount3 myThreadCount = new MyThreadCount3();
        Thread a = new Thread(myThreadCount, "A");
        Thread b = new Thread(myThreadCount, "B");
        Thread c = new Thread(myThreadCount, "C");
        Thread d = new Thread(myThreadCount, "D");
        Thread e = new Thread(myThreadCount, "E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

3. 执行并分析结果

image.png

Q.E.D.


只有创造,才是真正的享受,只有拚搏,才是充实的生活。