Java 多线程测试 笔记(一)

测试 没有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

39b0871adc8c1a087adae95c49fcc40b435.jpg

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

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

线程的6个状态

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

运行起来, 看图 :

c32982acfb85c998b441ad46f6bc154a07a.jpg

虽然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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值