Java并发编程基础篇


一.进程和线程

进程: 是代码在数据集合上的一次运动活动,是系统进行资源分配和调度的基本单位。
线程: 是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。

二者关系:
一个进程中有一个或多个线程,多个线程共享进程的堆和方法区资源,但是每个线程都有自己的程序计数器和栈区域。

程序计数器: 一块内存区域,用来记录线程当前要执行的指令地址
堆: 进程中最大的一块内存,被进程中的所有线程共享
栈: 用于存储该线程的局部变量,这些局部变量是该线程私有的,除此之外还用来存放线程的调用栈帧
方法区: 用来存放类、常量及静态变量等信息,线程共享

并发与并行

并发: 是指同一个时间段内多个任务同时都在执行,并且都没有执行结束。并发任务强调在一个时间段内同时执行,而一个时间段有多个单位时间累积而成,所以并发的多个任务在单位时间内不一定同时执行。

并行: 是说在单位时间内多个任务同时在执行。

二.Java多线程的实现

1.Thread类

(1).继承Thread类

public class TicketThread extends Thread{

    Integer ticketNum;

    public TicketThread(Integer ticketNum){
        this.ticketNum = ticketNum;
    }

    /**
     * 重写Thread类run方法 业务代码写在此方法中
     */
    @Override
    public void run() {
        for (int i = 1; i <= ticketNum; i++) {
            System.out.println("正在售票,第:"+i+"张");
        }
    }

    public static void main(String[] args) {
        //使用
        int ticketNum = 5;
        TicketThread ticketThread = new TicketThread(ticketNum);
        //启动线程
        ticketThread.start();
        System.out.println("线程启动");

    }
}

执行结果:
在这里插入图片描述

(2).new Thread

	public static void main(String[] args) {
        new Thread(()->{
            System.out.println("业务代码");
        }).start();
        System.out.println("线程启动");

    }

2.Runnable接口

public class TicketRunnable implements Runnable{

    Integer ticketNum;

    public TicketRunnable(int ticketNum){
        this.ticketNum = ticketNum;
    }

    @Override
    public void run() {
        int number = 1;
        while (ticketNum>0){
            System.out.println("正在售票,第:"+number+"张");
            ticketNum--;
            number++;
        }
    }

    public static void main(String[] args) {
        //使用  将自实现的TicketRunnable实例入参Thread构造方法  启动Thread线程
        TicketRunnable ticketRunnable = new TicketRunnable(5);
        new Thread(ticketRunnable).start();
    }
}

3.Callable & Future

/**
 * 实现Callable接口
 */
public class TicketCallable implements Callable<Integer> {

    /**
     * 重写call方法
     * @return
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception {
        Thread.sleep(1000);
        return new Random().nextInt(100);
    }


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //实例化自定义TicketCallable
        TicketCallable ticketCallable = new TicketCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(ticketCallable);
        //启动新线程
        new Thread(futureTask).start();
        //打印线程返回结果
        System.out.println(futureTask.get()+"");
    }
}

Java是单继承,使用Runnable和Callable方式创建线程的方式更显灵活,Callable接口能返回线程执行结果

三.线程状态

public enum State {

        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
线程状态释义
NEW线程刚被创建,还未启动
RUNNABLE线程正在正常运行中,当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等,这个状态下发生的等待一般是其他系统资源,而不是锁,Sleep等
BLOCKED线程阻塞,一个正在阻塞等待一个监视器锁的线程处于这一状态
WAITING无限等待,这个状态指线程拥有了某个锁之后,调用了wait方法,等待其他线程/锁的拥有者notify/notifyAll唤醒该线程继续下一步操作
TIMED_WAITING计时等待,一般出现在调用了wait(long)、join(long)、sleep等情况下
TERMINATED线程执行完毕

请添加图片描述

四.线程安全

1.并发过程中常见的问题:

(1).线程安全问题:
请添加图片描述
多个线程同时操作共享变量1时,会出现线程1更新共享变量1的值,但是其他线程获取到的是共享变量没有被更新之前的值。

(2).共享内存不可见性问题
请添加图片描述
Java内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间或者叫做工资内存,线程读写变量时操作的是自己工作内存中的变量。
在这里插入图片描述
上图中所示是一个双核 CPU 系统架构,每个核有自己的控制器和运算器,其中控制器包含一组寄存器和操作控制器,运算器执行算术逻辅运算。CPU的每个核都有自己的一级缓存,在有些架构里面还有一个所有CPU都共享的二级缓存。 那么Java内存模型里面的工作内存,就对应这里的 Ll或者 L2 缓存或者 CPU 的寄存器

1、线程A首先获取共享变量X的值,由于两级Cache都没有命中,所以加载主内存中X的值,假如为0。然后把X=0的值缓存到两级缓存,线程A修改X的值为1,然后将其写入两级Cache,并且刷新到主内存。线程A操作完毕后,线程A所在的CPU的两级Cache内和主内存里面的X的值都是l。

2、线程B获取X的值,首先一级缓存没有命中,然后看二级缓存,二级缓存命中了,所以返回X=1;到这里一切都是正常的,因为这时候主内存中也是X=l。然后线程B修改X的值为2,并将其存放到线程2所在的一级Cache和共享二级Cache中,最后更新主内存中X的值为2,到这里一切都是好的。

3、线程A这次又需要修改X的值,获取时一级缓存命中,并且X=l这里问题就出现了,明明线程B已经把X的值修改为2,为何线程A获取的还是l呢?这就是共享变量的内存不可见问题,也就是线程B写入的值对线程A不可见。

2.synchronized & volatile

synchronized: 进入到synchronized块的内存语义是把synchronized块内使用的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量是就不会从线程的工作内存中读取,而是从主内存中获取。退出synchronized块的内存语义是把synchronized块内对共享变量的修改刷新到主内存。会造成上下文切换的开销,独占锁,降低并发性。synchronized可以修饰代码块和方法。

volatile: 该关键字可以确保对一个变量的更新对其他线程的可见性。当一个变量被声明为volatile时,线程在写入变量时不会把值缓存到寄存器或其他地方,而是把值刷新回主内存。当其他线程读取该共享变量时会从主内存重新获取,而不是使用当前线程的工作内存中的值。volatile的内存语义和synchronized有相似之处,具体来说就是,当线程写入了volatile变量值时就等价于线程退出synchronized同步块(把写入工作内存的变量值同步到主内存),读取volatile变量值时就相当于进入同步块(先清空本地内存变量值,再从主内存获取最新值)。但是不能保证原子性,volatile修饰的属性是线程不安全的。

举个栗子:
开启两个线程售票

public class TicketRunnable implements Runnable{

    private Integer ticketNum;

    public TicketRunnable(int ticketNum){
        this.ticketNum = ticketNum;
    }

    @SneakyThrows
    @Override
    public void run() {
        while (true){
            if(ticketNum > 0){
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName()+"正在售票,第:"+ticketNum--+"张");
            }else{
                break;
            }
        }
    }

    public static void main(String[] args) {
        //使用  将自实现的TicketRunnable实例入参Thread构造方法  启动Thread线程
        Integer num = 10;
        TicketRunnable ticketRunnable1 = new TicketRunnable(num);
        Thread thread1 = new Thread(ticketRunnable1);
        Thread thread2 = new Thread(ticketRunnable1);

        thread1.start();
        thread2.start();
    }
}

打印结果:可以看有重复售票和多售的情况,这就是多线程的不安全性
在这里插入图片描述
使用synchronized 改造一下:

public class TicketRunnable implements Runnable{

    private Integer ticketNum;

    Object lock = new Object();

    public TicketRunnable(int ticketNum){
        this.ticketNum = ticketNum;
    }

    @Override
    public void run() {
        while (true){
            synchronized (lock){
                if(ticketNum > 0){
                    try {
                        Thread.sleep(100);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在售票,第:"+ticketNum--+"张");
                }else{
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        //使用  将自实现的TicketRunnable实例入参Thread构造方法  启动Thread线程
        Integer num = 10;
        TicketRunnable ticketRunnable1 = new TicketRunnable(num);
        Thread thread1 = new Thread(ticketRunnable1,"窗口1");
        Thread thread2 = new Thread(ticketRunnable1,"窗口2");
        Thread thread3 = new Thread(ticketRunnable1,"窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

打印结果:
在这里插入图片描述

3.Lock锁

import java.util.concurrent.locks.ReentrantLock;

public class TicketRunnable implements Runnable{

    private Integer ticketNum = 10;

    java.util.concurrent.locks.Lock lock = new ReentrantLock();

    public TicketRunnable(int ticketNum){
        this.ticketNum = ticketNum;
    }

    @Override
    public void run() {
        while (true){
            try {
                //加锁
                lock.lock();
                if(ticketNum > 0){
                    try {
                        Thread.sleep(100);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在售票,第:"+ticketNum--+"张");
                }else{
                    break;
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                //释放锁
                lock.unlock();
            }


        }

    }

    public static void main(String[] args) {
        //使用  将自实现的TicketRunnable实例入参Thread构造方法  启动Thread线程
        Integer num = 10;
        TicketRunnable ticketRunnable1 = new TicketRunnable(num);
        Thread thread1 = new Thread(ticketRunnable1,"窗口1");
        Thread thread2 = new Thread(ticketRunnable1,"窗口2");
        Thread thread3 = new Thread(ticketRunnable1,"窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

参考:
https://www.cnblogs.com/zsql/p/11144688.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值