草庐IT

并发编程简介

从程序到猿 2023-04-10 原文

Java 并发编程

一、 并发编程三要素

  1. 原子性:即一个不可被分割的操作。
    Java 中的原子性指:一个或多个操作要么全部执行成功,要么全部执行失败

  2. 有序性:程序执行的顺序是按照代码的先后顺序执行的。
    cpu 有可能会对指令进行重排序

  3. 可见性:当多个线程访问同一个共享变量时,如果其中一个线程对其进行了修改操作,其它线程能立即获取到最新修改的值。

二、线程的五大状态

对应cpu 的线程状态

  1. 新建状态:通过new 创建一个线程
  2. 就绪状态:调用start 方法,处于就绪状态的线程不一定立马就会执行run 方法,还需要等CPU的调度。
  3. 运行状态:cpu开始调度线程,开始执行run方法
  4. 阻塞状态:线程在执行过程中由于某些原因进入阻塞状态。如:sleep()、获取锁失败、时间片用完等
  5. 死亡状态:run方法执行完,或执行过程中遇到异常。

三、悲观锁与乐观锁

悲观锁:每次操作都会加锁,造成线程阻塞。如:synchronized、reentrantlock
乐观锁:每次操作都不会加锁,而是假定没有冲突而去完成某种操作。如果有冲突就重试,直到成功为止,不会造成线程的阻塞。虽然不会造成线程阻塞,但是频繁的重试会导致cpu的长时间被占用。

四、线程之间的协作

wait、notify、notifyAll等

五、synchronized 关键字

synchronized 是Java中的关键字,是一种同步锁,底层使用的是Monitor监视器。
它修饰的对象有一下几种:

  1. 修饰一个代码块 被修饰的代码块称为同步代码块,其作用范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象。
  2. 修饰一个方法 被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
  3. 修饰一个静态的方法 其作用范围是整个静态方法,作用的对象是这个类对象
  4. 修饰一个类 其作用的范围是synchronized后面括起来的部分,作用的对象是这个类对象。

对第4点解释:

public void test(){
	// 当前synchronized 作用的范围是 {} 的部分
	// 加锁的对象是 A.class 对象
	synchronized(A.class){

	}
}

六、CAS

CAS:Compare And Swap 比较并交换。

原理:

使用到的是cpu的一个原子操作,操作包含三个操作数,内存值、预期值、更新的值。如果内存值与预期值相同,那么就能将该值修改为更新的值。否则修改失败。

缺点:

  1. ABA 问题 可通过版本号或者时间戳解决
  2. 循环时间长对CPU的开销大 可以增加重试次数,超过重试次数则直接放弃
  3. 只能保证一个共享变量的原子操作。后续可以使用原子引用对象来解决该问题

七、Thread.State 类中定义的六种线程状态

  1. new:Thread对象已经创建,但是还没有执行。
  2. Runnable:Thread对象在java 虚拟机中运行。
  3. Block:Thread对象被阻塞。
  4. Waitting:Thread对象在等待另外一个线程的动作。
  5. Time_Waiting:Thread对象在等待另外一个线程的动作,但是有时间限制。
  6. Terminated:Thread已经完成了执行

八、Thread类的常用方法:

  • 获取和设置Thread对象信息的方法
    • getId(): 返回Thread 对象的标识符。该标识符是在线程创建时分配的一个正整数。在线程的整个生命周期中是唯一且无法改变的。
    • getName()/setName():获取、设置Thead 对象的名称。这个名称是一个String 类型。也可以在Thread的构造方法中指定。
    • getPriority()/setPriority():获取或者设置Thread对象的优先级。
    • isDaemon()/setDaemon():获取或者设置守护线程。
    • getState():返回Thread对象的状态。
  • interrupt():中断目标线程。给目标线程打上一个中断标记。
  • interrupted():判断目标线程是否被中断,但是将清除线程的中断标记。
  • isInterrupted():判断目标线程是否被中断,不会清除中断标记。
  • sleep(long ms):暂停当前线程ms时间。
  • join():暂停线程的执行,直到调用该方法的线程执行结束为止。可以使用该方法等待另外一个线程结束。
  • currentThread():Thread类的静态方法,返回实际执行该代码的Thread对象。

九、Callable

  • 接口。有简单类型参数,与call() 方法的返回类型相对应
  • 声明了call()。执行器运行任务时,该方法会被执行器执行。必须返回声明中指定类型的对象。
  • call() 方法可以抛出任何一种校验异常。可以实现自己的执行器并重载afterExecute() 方法来处理这些异常`
package com.yj.juc;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        Thread.sleep(500);
        return "call 的返回值";
    }
}


package com.yj.juc;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        // 启动线程执行任务
        new Thread(futureTask).start();
        // 需要同步阻塞获取到任务的结果
        System.out.println(futureTask.get());
        System.out.println("main1 方法执行结束");
    }
}

package com.yj.juc;

import java.util.concurrent.*;

public class Main2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                5,
                5,
                1000,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10)
        ){
            @Override
            protected void afterExecute(Runnable r, Throwable t) {
//                super.afterExecute(r, t);
                System.out.println("call 执行结束"+t);
            }
        };

        Future<String> future = threadPoolExecutor.submit(new MyCallable());
        String result = future.get();
        System.out.println(result);
        threadPoolExecutor.shutdown();
    }
}

// Main2 的打印结果:
call 的返回值
call 执行结束null

十、生产者与消费者模型

生产者、消费者是一个常见的多线程模型。

解释:

多个生产者线程往内存队列中存放数据;多个消费者线程从内存队列中取数据。

前提条件:

  1. 加锁,内存队列本身要加锁,才能实现队列的安全。
  2. 阻塞,当内存队列满了,生产者放不进去数据,就会阻塞。当队列为空时,消费者取不到数据,就会被阻塞。
  3. 唤醒通知,消费者消费之后,要通知生产者生产新的数据。反之生产者生产消息之后,要通知消费者消费

如何阻塞?
办法1:线程自己阻塞自己,也就是生产者、消费者线程各自调用wait和notify
办法2:用一个阻塞队列,当娶不到或者放不进去数据的时候,入队、出队操作阻塞、

如何唤醒、通知?
办法1:使用wait和notify。必须在Synchronized中使用
办法2:Condition机制。Lock 锁

案例:

任务队列:

package com.yj.juc.demo01;

/**
 * 任务队列
 */
public class MyQueue {
    // 存放任务的队列
    private String [] queue;
    // 添加时元素的下标
    private Integer putIndex = 0;
    // 获取时元素的下标
    private Integer getIndex = 0;
    // 元素的个数
    private Integer size = 0;

    // 锁对象
    private static final Object lock = new Object();


    public MyQueue(Integer size){
        this.queue = new String[size];
    }

    /**
     * 当前方法存在的问题:
     * 1、在单线程的情况下没有问题
     * 2、多线程的情况可能会出现多个线程同时阻塞,然后同时被唤醒。那么添加的元素就有可能超过队列的长度。
     * 解决方案:
     * 1、唤醒之后重新获取锁,获取成功则添加元素
     * @param element
     */
    public synchronized void put(String element){
        // 如果队列中的元素已经存满,则阻塞
        if (size == queue.length){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        queue[putIndex++] = element;
        ++size;
        putIndex = putIndex % queue.length;
        notify();
    }

    public synchronized String get(){
        if (size == 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        String result = queue[getIndex++];
        --size;
        getIndex = getIndex % queue.length;
        notify();
        return result;
    }
}

消费者:

package com.yj.juc.demo01;

import java.util.Random;

public class MyConsumer implements Runnable {

    private MyQueue myQueue;

    public MyConsumer(MyQueue myQueue){
        this.myQueue = myQueue;
    }


    @Override
    public void run() {
        while (true){
            try {
                String result = myQueue.get();
                System.out.println("消费者消费消息:" + result);
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

生产者:

package com.yj.juc.demo01;

import java.util.Random;

public class MyProvider implements Runnable {

    private MyQueue myQueue;

    public MyProvider(MyQueue myQueue){
        this.myQueue = myQueue;
    }


    @Override
    public void run() {
        int index = 0;
        while (true){
            try {
                String temp = "生产者生产消息"+index++;
                myQueue.put(temp);
                System.out.println(temp);
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

当前存在的问题:在多线程情况下有可能会通知多个线程。
解决方案:
在通知唤醒之后,尝试获取锁,获取成功才能执行对应的操作。

package com.yj.juc.demo01;

public class MyQueue2 extends MyQueue {

    // 存放任务的队列
    private String [] queue;
    // 添加时元素的下标
    private Integer putIndex = 0;
    // 获取时元素的下标
    private Integer getIndex = 0;
    // 元素的个数
    private Integer size = 0;

    // 锁对象
    private static final Object lock = new Object();


    public MyQueue2(Integer size){
        super(size);
    }

    /**
     * 当前方法存在的问题:
     * 1、在单线程的情况下没有问题
     * 2、多线程的情况可能会出现多个线程同时阻塞,然后同时被唤醒。那么添加的元素就有可能超过队列的长度。
     * 解决方案:
     * 1、唤醒之后重新获取锁,获取成功则添加元素
     * @param element
     */
    public synchronized void put(String element){
        // 如果队列中的元素已经存满,则阻塞
        if (size == queue.length){
            try {
                wait();
                put(element);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            put1(element);
            notify();
        }

    }

    private void put1(String element) {
        queue[putIndex++] = element;
        ++size;
        putIndex = putIndex % queue.length;
    }

    public synchronized String get(){
        if (size == 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return get();
        }else{
            String result = get1();
            notify();
            return result;
        }
    }

    private String get1() {
        String result = queue[getIndex++];
        --size;
        getIndex = getIndex % queue.length;
        return result;
    }
}

当前代码还存在的问题:
notify()通知唤醒是随机唤醒的。

十一、interrupt() 和 interruptedException()

interrupt():用来打断正在睡眠的线程,如wait()、sleep()
interruptedException():只有在方法上声明了interruptException 异常,被打断的话就会抛出异常。

十二、轻量级阻塞和重量级阻塞

轻量级阻塞:能够被中断的阻塞称为轻量级阻塞。对应的线程状态为WAITING;
重量级阻塞:synchronized 不能被中断的阻塞称为重量级阻塞,对应的状态为Block;

十三、线程的interrupt、interrupted 、isInterrupted 区别?

总结:
1、interrupt:置线程的中断状态
2、isInterrupt:线程是否中断
3、interrupted:返回线程的上次的中断状态,并清除中断状态

十四、线程如何优雅关闭

14.1、stop、destory方法

这两个方法会强制杀死线程,比如线程运行到了一半被强制杀死?
问题:
这些方法不建议使用,因为强制杀死线程,则线程中所使用的资源,例如文件资源、网络连接等都无法关闭。
因此,一个线程一旦运行中,则应该尽量让他运行完,合理的释放资源,不要强行关闭。
如果是一个不断循环运行的线程,就需要用到线程通信机制,让主线程通知其退出。

14.2、使用守护线程

因为当用户线程执行完之后,守护线程也会结束。

14.3、设置关闭的标志位

通过标志位的方式,停止正在循环运行的线程

public class MyThread extends Thread{

private boolean flag = true;

@Override
public void run(){
	while(flag){
		// do ...
	}
}

public void stop(){
	this.flag = false;
}

}

但是当前代码存在一个问题:
如果Mythread 在while 循环里被阻塞了,例如里面调用了Object.wait(),则永远都无法执行到while(false),也就会一直无法退出。此时就可以用到interrupt()或interruptException()。

有关并发编程简介的更多相关文章

  1. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  2. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  3. HBase Region 简介和建议数量&大小 - 2

    Region是HBase数据管理的基本单位,region有一点像关系型数据的分区。region中存储这用户的真实数据,而为了管理这些数据,HBase使用了RegionSever来管理region。Region的结构hbaseregion的大小设置默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的RegionServer,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的RegionServer。RegionSplit时机:当1个region中的某个Store下所有StoreFile

  4. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  5. ruby - 如何以编程方式删除实例上的 "singleton information"以使其编码(marshal)? - 2

    我创建了一个由于“在运行时执行的单例元类定义”而无法编码的对象(这段代码的描述是否正确?)。这是通过以下代码执行的:#defineclassXthatmyusesingletonclassmetaprogrammingfeatures#throughcallofmethod:break_marshalling!classXdefbreak_marshalling!meta_class=class我该怎么做才能使对象编码正确?是否可以从对象instance_of_x的classX中“移除”单例组件?我真的需要一个建议,因为我们的一些对象需要通过Marshal.dump序列化机制进行缓存。

  6. Ruby 元编程问题 - 2

    我正在查看Ruby日志记录库Logging.logger方法并从sourceatgithub提出问题与这段代码有关:logger=::Logging::Logger.new(name)logger.add_appendersappenderlogger.additive=falseclass我知道类 最佳答案 这实际上删除了方法(当它实际被执行时)。这是确保close不会被调用两次的保障措施。看起来好像有嵌套的“class 关于Ruby元编程问题,我们在StackOverflow上找到一

  7. ruby-on-rails - 获取并发布相同匹配项的请求 - 2

    在我的路线文件中我有:match'graphs/(:id(/:action))'=>'graphs#(:action)'如果是GET请求(工作)或POST请求(不工作),我想匹配它我知道我可以使用以下方法在资源中声明POST请求:post'/'=>:show,:on=>:member但是我怎样才能为比赛做到这一点呢?谢谢。 最佳答案 如果你同时想要POST和GETmatch'graphs/(:id(/:action))'=>'graphs#(:action)',:via=>[:get,:post]编辑默认值可以设置如下match'g

  8. ruby - Paperclip:以编程方式分配图像并设置其名称 - 2

    使用Paperclip,我想从这样的URL抓取图像:require'open-uri'user.photo=open(url)问题是我最后得到一个像“open-uri20110915-4852-1o7k5uw”这样的文件名。有什么方法可以更改user.photo上的文件名?作为一个额外的变化,Paperclip将我的文件存储在S3上,所以如果我可以在初始分配中设置我想要的文件名就更好了,这样图像就会上传到正确的S3key。像这样:user.photo=open(url),:filename=>URI.parse(url).path 最佳答案

  9. ruby - 如何以编程方式检查证书是否已被吊销? - 2

    我正在开发一个xcode自动构建系统。在执行一些预构建验证时,我想检查指定的证书文件是否已被撤销。我了解securityverify-cert验证其他证书属性但不验证吊销。我如何检查撤销?我正在用Ruby编写构建系统,但我对任何语言的想法都持开放态度。我阅读了这个答案(Openssl-Howtocheckifacertificateisrevokedornot),但指向底部的链接(DoesOpenSSLautomaticallyhandleCRLs(CertificateRevocationLists)now?)进入的Material对我的目的来说有点过于复杂(用户上传已撤销的证书是一

  10. ruby - 如何保持我不常用的编程语言技能 - 2

    关闭。这个问题是off-topic.它目前不接受答案。想改进这个问题吗?Updatethequestion所以它是on-topic用于堆栈溢出。关闭11年前。Improvethisquestion我不经常使用ruby​​-通常它加起来相当于每两个月或更长时间编写一次脚本。我的大部分编程都是使用C++进行的,这与ruby​​有很大不同。由于我与ruby​​之间的差距如此之大,我总是忘记语言的基本方面(比如解析文本文件和其他简单的东西)。我想每天练习一些基本的东西,我想知道是否有一些我可以订阅的网站,并且会向我发送当天的Ruby问题或类似的东西。有人知道这样的站点/Internet服务吗?

随机推荐