欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 多线程测试 笔记(一)

程序员文章站 2022-05-04 18:19:07
...

测试 没有Synchronized的并发 结果

用比较实际的方式测试,比如说卖东西,赚钱

public class Sell implements Runnable {

    static Sell sell = new Sell();

    //商品总数
    static int num = 500;

    //收到的钱 , 每件10块钱
    static BigDecimal money = new BigDecimal(0);

    //给营业员 , 每件奖励1块钱
    static AtomicInteger reward = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            num--;
            reward.getAndIncrement();
            money = money.add(new BigDecimal(10));
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(sell);
        Thread t2 = new Thread(sell);
        t1.start();
        t2.start();
        System.out.println("剩余数量:" + num);
        System.out.println("赚到的钱:" + money);
        System.out.println("营业员钱:" + reward);
    }
}

理想情况下卖500件商品,应该可以赚到5000块钱,没有任何的措施,多次运行得到的结果如下:

剩余数量:489
赚到的钱:2170
营业员钱:309
----------------------------------------
剩余数量:485
赚到的钱:1770
营业员钱:286
----------------------------------------
剩余数量:-335
赚到的钱:10950
营业员钱:869
----------------------------------------

发现,卖出的商品和赚到的钱完全不成正比,但是商家应该会很开心,钱多了

 

用最简单的num-- 说明: num--包含三个操作

1.读取num

2.num-1

3.num值写入内存

当t1线程进行1,2步操作时,还未写入内存时,t2线程进入现场,读取得到的num将会是原值,所以造成了线程不安全的问题

 

为了数据安全加点措施

加个判断如果商品数量大于0,就继续卖钱,并在t1,t2线程,调用join()方法

Thread.join() 官方的解释

public final void join() throws InterruptedException Waits for this thread to die. Throws: InterruptedException  - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.

即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止. 也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行

说明: 主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了

public class Sell implements Runnable {

    static Sell sell = new Sell();

    //商品总数
    static int num = 500;

    //收到的钱 , 每件10块钱
    static BigDecimal money = new BigDecimal(0);

    //给营业员 , 每件奖励1块钱
    static AtomicInteger reward = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            num--;
            reward.getAndIncrement();
            money = money.add(new BigDecimal(10));
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(sell);
        Thread t2 = new Thread(sell);
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        System.out.println("剩余数量:" + num);
        System.out.println("赚到的钱:" + money);
        System.out.println("营业员钱:" + reward);
    }
}

得到稳定的结果 :

剩余数量:-1500
赚到的钱:20000
营业员钱:2000

 

数量是对的,但是透支库存了,所以通俗的在run()里,加入if判断

public class Sell implements Runnable {

    static Sell sell = new Sell();

    //商品总数
    static int num = 500;

    //收到的钱 , 每件10块钱
    static BigDecimal money = new BigDecimal(0);

    //给营业员 , 每件奖励1块钱
    static AtomicInteger reward = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            if (num > 0) {
                num--;
                reward.getAndIncrement();
                money = money.add(new BigDecimal(10));
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(sell);
        Thread t2 = new Thread(sell);
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        System.out.println("剩余数量:" + num);
        System.out.println("赚到的钱:" + money);
        System.out.println("营业员钱:" + reward);
    }
}

最后得到的稳定结果 :

剩余数量:0
赚到的钱:5000
营业员钱:500

数据正确了

 

Synchronized的两个方式

Synchronized的作用,来自orecle官方的一段翻译:

同步方法支持一种简单的策略来防止线程干扰和内存一致性错误: 如果一个对象对多线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成

简句: 能够在同一时间内最多只有一个线程执行某段代码,保证并发的安全性

 

如果一段代码被Synchronized修饰,那么该段代码会以原子的形式执行,多个线程执行时,不会相互干扰,因为他们不会同时执行

Synchronized被设置成关键字,是Java最基本的互斥同步方式,也是并发编程中必学的一点

1. 对象锁

包括方法锁(默认锁对象为this当前实例对象) 和 同步代码块锁(自定义指定锁对象)

2. 类锁

指synchronized修饰静态的方法或指定锁为Class对象

 

<1> 测试 对象锁

public class Sell implements Runnable {

    static Sell sell = new Sell();

    //商品总数
    static int num = 500;

    //收到的钱 , 每件10块钱
    static BigDecimal money = new BigDecimal(0);

    //给营业员 , 每件奖励1块钱
    static AtomicInteger reward = new AtomicInteger(0);

    @Override
    public void run() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                if (num > 0) {
                    num--;
                    reward.getAndIncrement();
                    money = money.add(new BigDecimal(10));
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(sell);
        Thread t2 = new Thread(sell);
        t1.start();
        t2.start();

        //保证t1和t2都运行结束,如果没有这段t1和t2没有运行结束就会被打印出去了
        while (t1.isAlive() || t2.isAlive()){

        }

        System.out.println("剩余数量:" + num);
        System.out.println("赚到的钱:" + money);
        System.out.println("营业员钱:" + reward);
    }
}

最后得到的稳定结果 :

剩余数量:0
赚到的钱:5000
营业员钱:500

 

再复杂一点,比如在一个方法里很多个synchronized块A,B,C,D,执行的方式不是串行的,双双配对方式执行

这时候,我们就需要自己创建对象,充当每一对不同控制的锁对象synchronized(Object){...},这种方式很是理想化,容易出错,也很复杂,而且jdk也提供了更高的线程安全控制api,用他们会更合适一些

分享利用IDEA进行多线程的调试,打上断点,然后点击小红点,有红线划着的All 和 Thread

Java 多线程测试 笔记(一)

All : 进入断点时,就会把整个JVM停下来,包括其他的线程,都会一起停下来

Thread: 只会把当前线程停下来

线程的6个状态

  • 初始(NEW):新创建一个线程对象,但还没有调用start()方法
  • 运行(RUNNABLE):线程中将就绪(ready)和运行中(running)两种状态都称为“运行”,线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready) ,就绪状态的线程在获得CPU时间片后变为运行中状态(running)
  • 阻塞(BLOCKED):表示线程阻塞于锁
  • 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)
  • 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回
  • 终止(TERMINATED):表示该线程已经执行完毕

运行起来, 看图 :

Java 多线程测试 笔记(一)

虽然2也有状态显示,但是状态的名称不是那么的复合java官方的名词,选择3进入调试,获取更明了的线程生命周期状态

 

普通方法锁

public class Sell implements Runnable {

    static Sell sell = new Sell();

    //商品总数
    static int num = 500;

    //收到的钱 , 每件10块钱
    static BigDecimal money = new BigDecimal(0);

    //给营业员 , 每件奖励1块钱
    static AtomicInteger reward = new AtomicInteger(0);

    @Override
    public void run() {
        selling();
    }

    public synchronized void selling() {
        System.out.println(" 运行开始:" + Thread.currentThread().getName());
        for (int i = 0; i < 1000; i++) {
            if (num > 0) {
                num--;
                reward.getAndIncrement();
                money = money.add(new BigDecimal(10));
            }
        }
        System.out.println(" 运行结束:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(sell);
        Thread t2 = new Thread(sell);
        t1.start();
        t2.start();

        //保证t1和t2都运行结束,如果没有这段t1和t2没有运行结束就会被打印出去了
        while (t1.isAlive() || t2.isAlive()) {

        }

        System.out.println("剩余数量:" + num);
        System.out.println("赚到的钱:" + money);
        System.out.println("营业员钱:" + reward);
    }
}

运动结果 和之前一样: 

 运行开始:Thread-0
 运行结束:Thread-0
 运行开始:Thread-1
 运行结束:Thread-1
剩余数量:0
赚到的钱:5000
营业员钱:500

 

下面继续测试,学习....

转载于:https://my.oschina.net/u/3829444/blog/3026233