多线程自增问题和内置锁

1 多线程自增问题

1.1一个线程自增问题

前面我们学习了多线程,那么我们就可以创建线程来帮我们干许多事,下面我们写一个简单的线程来实现最简单的加法运算吧。

写一个线程将addNum加1000次:

用面向对象的思维来写,首先要写一个声明addNum数和其加法得类,其次写一个工作类,这个类要继承Thread或者实现Runable,最后写一个测试类。

Plus声明addNum类

package ThreadAdd;

public class Plus {
    private int addNum=0;

    public int getAddNum() {
        return addNum;
    }

    public void setAddnum(int addNum) {
        this.addNum = addNum;
    }
    public void add(){
        addNum++;
    }
}

MyThread工作类

package ThreadAdd;
public class MyThread implements Runnable{
    private Plus plus;

    public MyThread() {
    }

    public MyThread(Plus plus) {
        this.plus = plus;
    }

    @Override
    public void run() {
        for(int i = 0;i < 1000; i ++){
            plus.add();
        }
    }
}

Test测试类

package ThreadAdd;

public class Test {
    public static void main(String[] args) {
        Plus plus = new Plus();
        MyThread myThread = new MyThread(plus);

        Thread thread = new Thread(myThread);

        thread.start();
        try {
            thread.join(); //join让子线程插入到main线程来,等子线程结束后在执行main线程
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("addNum的值="+plus.getAddNum());
    }
}

执行结果为

在这里插入图片描述

1.2 多个线程自增

一个线程自增问题解决了,那么现在我们再多加三个子线程,都给addNum加10000,那我们就能得到40000了,那么答案是这样吗?实验出真知,说干就干。

package ThreadAdd;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Plus plus = new Plus();
        MyThread myThread = new MyThread(plus);

        Thread thread1 = new Thread(myThread);
        Thread thread2 = new Thread(myThread);
        Thread thread3 = new Thread(myThread);
        Thread thread4 = new Thread(myThread);

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        
        thread1.join();
        thread2.join();
        thread3.join();
        thread4.join();
        System.out.println("addNum的值="+plus.getAddNum());
    }
}

按照代码来说,现在有4条线程来实现加法,将这4条都插入到main线程里面,等这4条线程都执行完执行main方法,输出addNum的值。

结果
在这里插入图片描述
结果为11744,而且如果你重新执行的话,这个数会不断变化,但是这个数就是比40000要小得多,这是为什么呢?

1.3 多线程自增问题解答

得到的数比40000要小的多,原因是:多线程是抢占式调度模型,这4个线程不是同时执行的,这4个线程分别抢CPU的执行权限,CPU会分配其时间片来执行程序,当线程1抢到CPU的执行权限时,其他三个线程等待。有可能出现一种状况:这4个线程都访问到addNum的值为500,但是现在线程1抢到CPU的执行权限,那么现在这个线程1将addNum的值加到800了,这时他执行结束,线程3抢到了CPU的执行权限,但是因为他访问的addNum的值为500,所以线程3就从500开始加,加到1000。那么线程1从500加到800和线程3从500加到1000,这其中重复加了许多次,这样就造成资源的浪费,且这个数永远要比40000小。

那么,如何解决呢?
这时就需要加一把锁,比如当线程1抢到CPU的执行权限时,它先访问临界代码块(多个线程共同执行的任务),将临界代码块加一把锁,这样这有线程1可以执行那段代码(这时线程1已将addNum加到一定值然后更新addNum值),等线程1时间片使用结束时,锁释放,其他抢到CPU执行权限的线程才可以访问临界代码块,像线程1一样加锁,执行完之后释放锁…。

具体实现

package ThreadAdd;
public class MyThread implements Runnable{
    private Plus plus;

    public MyThread() {
    }

    public MyThread(Plus plus) {
        this.plus = plus;
    }

    @Override
    public void run() {
        synchronized (plus){
            for (int i = 0; i < 10000; i++) {
                plus.add();
            }
        }
    }
}

把锁加到临界代码块就可以了

在这里插入图片描述

2 内置锁

在上面我们用了锁,那这个锁究竟是什么,具体怎么使用呢?

2.1 简介

synchronized顾名思义,就是用来进行一些同步工作的,我们常常在多线程的环境中使用到它,实现互斥的效果。

每一个java对象都可以当做一个同步锁,线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得锁的唯一途径就是进入这个锁的保护的同步代码块或方法。这里的同步代码块和同步方法,就是使用synchronized关键字标记的代码块和方法。

synchronized内置锁
解释两个概念:

  1. 临界区资源(共同数据)
    表示一种可以被多个线程使用的公共资源或共享数据,但是每一次只能有一个线程使用它。一旦临界区资源被占用,想使用该资源的其他线程必须等待。
  2. 临界区代码段(Critical Section)
    是每个线程中访问临界资源的那段代码,多个线程必须互斥地对临界区资源进行访问。线程进入临界区代码段之前,必须在进入区申请资源,申请成功之后进行临界区代码段,执行完成之后释放资源。临界区代码段(Critical Section)的进入和退出

2.2 语法

synchronized (共享对象(plus,ticket)) {//临界区代码块
    //对共对象的访问(plus)(ticket)
}

2.3 实例

实例方法上加锁

public class SynDemo1 {
    public static void main(String[] args) {
        Car a = new Car();
        Thread t1 = new Thread1(a);
        Thread t2 = new Thread2(a);
        t1.start();
        t2.start();
    }
}

class Thread1 extends Thread {
    private Car a;

    public Thread1(Car a) {
        this.a = a;
    }
    public void run() {
        a.fun1();
    }
}

class Thread2 extends Thread {
    private Car a;

    public Thread2(Car a) {
        this.a = a;
    }
    public void run() {
        a.fun2();
    }
}

class Car {
    public synchronized  void fun1() {
        synchronized (this) {
            System.out.println("开始打蜡");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("打蜡结束");
        }
    }

    public void fun2() {
        synchronized (this) {
            try {
                Thread.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("开始抛光");
            System.out.println("抛光结束");
        }
    }
}

静态方法加锁

public class SynDemo1 {
    public static void main(String[] args) {
        Car a = new Car();
        Thread t1 = new Thread1(a);
        Thread t2 = new Thread2(a);
        t1.start();
        t2.start();
    }
}

class Thread1 extends Thread {
    private Car a;

    public Thread1(Car a) {
        this.a = a;
    }
    public void run() {
        a.fun1();
    }
}

class Thread2 extends Thread {
    private Car a;

    public Thread2(Car a) {
        this.a = a;
    }
    public void run() {
        a.fun2();
    }
}

class Car {
    public synchronized static void fun1() {  //给方法上锁
            System.out.println("开始打蜡");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("打蜡结束");
    }

    public static void fun2() {
        synchronized (Car.class) {
            try {
                Thread.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("开始抛光");
            System.out.println("抛光结束");
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值