一篇文章理清Java并发脉络

1 篇文章 0 订阅
1 篇文章 0 订阅

Java并发

  见过并发编程的代码,但是对并发编程稀里糊涂的,一篇文章带你理清并发的脉络。

一、概述

为什么要用并发?

1、CPU运行速度高,I/O、网络传输等速度慢,并发可以充分压榨CPU性能。
2、一个服务要处理多个请求端的响应。

并发为什么会带来一致性问题?

  一项技术的出现,是为了解决一个大问题,但是往往会带来一些新的小问题。使用并发导致的就是数据一致性问题。这我们都知道,但是到底为什么会导致数据不一致呢?
在这里插入图片描述
  是基于jvm的内存模型,变量是保存在主内存中的(可以大概理解为jvm的堆空间),线程在使用变量时,会将变量从主内存拷贝到工作内存中(可以大概理解为jvm的栈空间),多个java线程对同一个变量进行拷贝-更改-写回主内存,就会导致一致性问题。

  基于以上的点,学习并发我们需要解决的就是三大问题:
  1、怎么进行并发编程。
  2、怎样保证线程安全。
  3、线程交互。

二、并发编程

传统的并发编程(用于理解底层实现,基本不用)

  最原始的方式,就是创建任务(实现Runnable接口/实现Callable接口),创建线程(实例化Thread)运行。

  • 实现Runnable接口,创建线程Thread运行
// 定义任务-Runnable
class Task1 implements Runnable {
    @Override
    public void run() {
        // 具体业务
        System.out.println();
    }
}
public static void main(String[] args) {
    // 创建线程-Runnable
    Thread t1 = new Thread(new Task1());
    t1.start();
}
  • 实现Runnable接口,不过是在创建线程时,使用Lambda表达式简化任务创建
public static void main(String[] args) {
    // 创建线程-Runnable2
    Thread t2 = new Thread(() -> {
        // 具体业务
        System.out.println();
    });
}
  • 实现Callable接口
    其中FutureTask继承了Runnable,可以传递给Thread用于创建线程。
// 定义任务2
class Task2 implements Callable {

    @Override
    public Object call() throws Exception {
        return null;
    }
}
public static void main(String[] args) {
    // 创建线程-Callable
    FutureTask<String> ft = new FutureTask<>(new Task2());
    Thread t3 = new Thread(ft);
    t3.start();
}
  • 继承Thread类(java只能继承一个父类,因此此方法更不推荐)
class MyThread extends Thread {
    @Override
    public void run() {
        // 具体业务
        System.out.println();
    }
}

使用Executor工具类

  以上传统方法,线程Thread是需要自己创建并启动的,需要自己管理线程的生命周期。因此除非简单的功能,一般不使用。

  java中提供了Executor下的一系列工具类,用于管理Thread线程,常用的就ExecutorService、ScheduledExecutorService,可以将任务提交给工具类,使用工具类来管理线程的启停等。下面介绍这两个工具类怎样使用:

使用ExecutorService(最常用)
// 定义任务
class Task1 implements Runnable {
    @Override
    public void run() {
        // 具体业务
        System.out.println();
    }
}

public static void main(String[] args) {
	ExecutorService executor = Executors.newFixedThreadPool(10);
	// 提交已创建的任务
    executor.submit(new Task1());

    // 使用Lambda表达式提交任务
    executor.submit(() -> {
        // 具体业务
    });

    // 使用Lambda表达式提交带有返回值的任务
    Future<String> future = executor.submit(() -> {
        // 具体业务
        return "返回结果";
    });    
}
使用ScheduledExecutorService

  ScheduledExecutorService是带有定时功能的线程处理器。

public static void main(String[] args) {
	ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

    // 每1秒执行一次任务
    executorService.schedule(() -> {
        // ...
        return "Hello world";
    }, 1, TimeUnit.SECONDS);

    // 每1秒执行一次任务,并带有返回值
    Future<String> future = executorService.schedule(() -> {
        // ...
        return "Hello world";
    }, 1, TimeUnit.SECONDS);
}

三、怎样保证数据安全

volatile关键字

volatile的实现原理

  基于概述中jvm内存模型,线程是将变量从主内存拷贝到工作内存,操作后再赋值到主内存,volatile保持一致原理是,对变量做以下两条限制:
1、要求线程修改变量后,必须立即写回主内存。(非volatile变量可能会等其他操作后,再写回主内存)
2、要求线程在使用变量时,必须使用最新的值。(在使用时,强制刷新一次)

  以上两条就能保证volatile变量在一个线程中的修改,立即在另个线程可见。

volatile的使用场景

  基于以上条件,volatiltile并不能完全保证一致性,例如多个线程同时从主内存获取到变量,然后更新到主内存。因此只有在以下场景,才应该使用volatile:
1、运算结果不依赖当前变量的值,或者只有单一线程会修改。(使用volatile变量作为锁标志)
2、不与其他变量参与不变约束。

  其他的一些书里讲到的什么64位double、long类型的特殊情况,基本不会出现或者新版jvm已优化,日常开发完全不用考虑。

synchronized关键字

synchronized的实现原理

  使用字节码指令,在synchronized同步的代码块前后锁定对象。

synchronized的使用场景

  目前版本jdk,synchronized的性能与Lock相差无几,所以选择时无需权衡性能因素。synchronized胜在简洁,并且是手动释放锁,Lock需要主动编码释放锁。因此能使用synchronized,就使用synchronized。

Lock

  Lock的功能更强大也更灵活,如果关键字满足不了,就需要上Lock了。
  Lock是一个接口,与Executor一样,它的多个实现类,提供了不同功能的锁。锁的基本操作如下,获取锁lock(获取不到时线程会被阻塞),释放锁unlock。还提供了其他的常用api,例如tryLock(),lock的非阻塞版本,尝试立即获取锁,成功就返回true,tryLock(long timeout, TimeUnit timeUnit) 等待制定事件,然后放弃获取Lock

Lock lock = ...; 
lock.lock();
try {
    // access to the shared resource
} finally {
    lock.unlock();
}

  Lock的实现类有 ReentrantLock可重入锁,ReentrantReadWriteLock可重入读写锁,StampedLock java8引入的支持乐观锁。

四、线程之间的协作

  学会以上部分,就能大多数RPC调用中涉及的异步及安全问题了,因为RPC调用主要是多线程分别处理不同任务,任务之间的交互极少。
  但是对于个别业务场景,依赖于多任务调度,就需要线程之间的协作了,例如线程A需要等待线程B执行完才能处理。
也就是wait和notify操作。

public class Data {
    private String packet;
    
    // true 时,生产者发送数据,消费者等待
    // false 时,消费者消费数据,生产者等待
    private boolean transfer = true;
 
    public synchronized String receive() {
        while (transfer) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); 
            }
        }
        transfer = true;
        
        String returnPacket = packet;
        notifyAll();
        return returnPacket;
    }
 
    public synchronized void send(String packet) {
        while (!transfer) {
            try { 
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); 
            }
        }
        transfer = false;
        
        this.packet = packet;
        notifyAll();
    }
}

public static void main(String[] args) {

	// 定义需要生产消费的包裹
	String packets[] = {
          "First packet",
          "Second packet",
          "Third packet",
          "Fourth packet",
          "End"
        };

	// 创建线程,发送包裹
	Thread sendThread = new Thread(() -> {
        for (String packet : packets) {
            data.send(packet);

            // Thread.sleep() 模拟耗时长的业务操作
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); 
            }
        }
    });
    
	// 创建线程,接受包裹
	Thread receiveThread = new Thread(() -> {
        for(String receivedMessage = load.receive();
	          !"End".equals(receivedMessage);
	          receivedMessage = load.receive()) {
	            
	         //Thread.sleep() 模拟耗时长的业务操作
	         try {
	             Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
	         } catch (InterruptedException e) {
	             Thread.currentThread().interrupt(); 
	         }
        }
    });

	// 启动线程
	sendThread.start();
	receivedMessage.start();
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值