Java 知识点总结之Java 并发 API(二)

标签: java  并发

6、CountDownLatch的工作原理

答:CountDownLatch采用AQS(AbstractQueuedSynchronizer)队列实现,先初始化Count,再countDown,当计数器值到达0时,表示所有任务都执行完了。

/**
 * 用CountDownLatch实现多个任务并发计算,并汇总结果
 * @author changtan.sun
 *
 */
public class MyComputeTask {
	static ConcurrentLinkedQueue<Long> sums = new ConcurrentLinkedQueue<Long>();

	public MyComputeTask() {
	}

	public long compute(int number, int part) throws Exception {
		number = number + 1;
		int parts = (number + part - 1) / part;
		CountDownLatch latch = new CountDownLatch(parts + 1);

		for (int i = 0; i < parts; i++) {
			long min = i * part;
			long max = (i + 1) * part < number ? (i + 1) * part : number;

			new Thread(new MyTask(min, max, latch)).start();
		}
		latch.countDown();
		latch.await();

		int sum = 0;
		for (Long s : sums) {
			sum += s;
		}
		return sum;
	}

	private static class MyTask implements Runnable {

		private long min;
		private long max;

		private CountDownLatch latch;

		public MyTask(long min, long max, CountDownLatch latch) {
			this.min = min;
			this.max = max;
			this.latch = latch;
		}

		@Override
		public void run() {
			long sum = 0;
			for (long i = min; i < max; i++) {
				sum += i;
			}
			sums.add(sum);
			latch.countDown();
		}

	}
}

7、wait和notify

答:(1)wait()方法使得当前线程必须要等待,等到另外一个线程调用notify()或者notifyAll()方法。

         当前的线程必须拥有当前对象的monitor,也即lock,就是锁。

         线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行。

         要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或synchronized块中。

         (2)notify()方法会唤醒一个等待当前对象的锁的线程。

         notify方法调用必须放在synchronized方法或synchronized块中。

8、用 wait-notify 写一段代码来解决生产者-消费者问题

 

 

9、什么是线程局部变量

线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。

10、用 Java 写一个线程安全的单例模式(Singleton)


public class Test4 {
	private static volatile Test4 instance;  //懒汉模式
	private Test4() {
	}
	public static Test4 getInstance() {
		if (instance == null) { //双重否定加锁
			synchronized (Test4.class) {
				if (instance == null) {
					instance = new Test4();
				}
			}
		}
		return instance;
	}
}

这里为什么要使用volatile修饰instance?主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在JVM中这句话大概做了下面3件事情:
(1)给instance分配内存
(2)调用Singleton的构造函数来初始化成员变量
(3)将instance对象指向分配的内存空间(执行完这步instance就为非null了)。
但是在JVM的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在3执行完毕、2未执行之前,被线程二抢占了,这时instance已经是非null了(但却没有初始化),所以线程二会直接返回instance,然后使用,然后顺理成章地报错。

11、Java内存逃逸

答:(1)当变量(或者对象)在方法中分配后,其指针被返回或者被全局引用(这样就会被其他过程或者线程所引用),这种现象称作指针(或者引用)的逃逸(Escape)。

(2)指针逃逸场景:全局变量赋值,方法返回值,实例引用传递

class A {  
public static B b;  
   
public void globalVariablePointerEscape() { // 给全局变量赋值,发生逃逸  
b = new B();  
}  
   
public B methodPointerEscape() { // 方法返回值,发生逃逸  
return new B();  
}  
   
public void instancePassPointerEscape() {  
methodPointerEscape().printClassName(this); // 实例引用传递,发生逃逸  
}  
}  
   
class B {  
public void printClassName(A a) {  
System.out.println(a.class.getName());  
}  
}  

(3)this逃逸

this逃逸是指在构造函数返回之前其他线程就持有该对象的引用. 调用尚未构造完全的对象的方法可能引发令人疑惑的错误, 因此应该避免this逃逸的发生.

public class ThisEscape {  
    public ThisEscape() {  
        new Thread(new EscapeRunnable()).start();  
        // ...  
    }  
      
    private class EscapeRunnable implements Runnable {  
        @Override  
        public void run() {  
            // 通过ThisEscape.this就可以引用外围类对象, 但是此时外围类对象可能还没有构造完成, 即发生了外围类的this引用的逃逸  
        }  
    }  
}  

解决方案:
public class ThisEscape {  
    private Thread t;  
    public ThisEscape() {  
        t = new Thread(new EscapeRunnable());  
        // ...  
    }  
      
    public void init() {  
        t.start();  
    }  
      
    private class EscapeRunnable implements Runnable {  
        @Override  
        public void run() {  
            // 通过ThisEscape.this就可以引用外围类对象, 此时可以保证外围类对象已经构造完成  
        }  
    }  
}  

12、final关键词

答:(1)final的作用:

final关键字提高了性能。JVM和Java应用都会缓存final变量。

final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。

使用final关键字,JVM会对方法、变量及类进行优化。

(2)final的内存模型

final域,编译器和处理器要遵守两个重排序规则:

1)在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

2)初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

写final域的重排序规则会要求译编器在final域的写之后,构造函数return之前,插入一个StoreStore障屏。读final域的重排序规则要求编译器在读final域的操作前面插入一个LoadLoad屏障。

(3)      final引用不能从构造函数内“逸出”

13、volatile关键字

答:(1)volatile关键字来保证可见性

1)当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

2)禁止进行指令重排序

         (2)volatile不能保证原子性

                   可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。

         (3)volatile一定程度上保证有序性

//x、y为非volatile变量  
//flag为volatile变量  
  
x = 2;        //语句1  
y = 0;        //语句2  
flag = true;  //语句3  
x = 4;         //语句4  
y = -1;       //语句5  

由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。

         (4)使用volatile必须具备以下2个条件:

对变量的写操作不依赖于当前值

该变量没有包含在具有其他变量的不变式中

    volatile boolean inited = false;

    private volatile static Singleton instance= null;

也就是最简单的形式


版权声明:本文为sunct原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sunct/article/details/79741851

智能推荐

【c++】模板和模板类

C++是一种“强类型”语言。也就是说,对于一个变量,编译器必须确切知道它是什么类型。但是,这种强类型函数在实现一些简单函数反而更麻烦。例如:求两大数的较大者,应以Max( )函数,我们需要对不同数据类型分别定义不同重载版本来实现: 我们看到,虽然我们可以通过函数重载去实现,但明显存在一些缺点: 1、所有的函数除返回类型外,函数体都相同,代码复用率低; 2、只要新类型出现,我...

[java][事务]tcc事务实战学习过程

学习项目:https://github.com/14251104246/spring-cloud-rest-tcc 下载源码,进入源码目录运行:mvn clean package Docker Compose运行 docker-compose -f infrastructure-compose.yml up -d docker-compose -f basic-ms-compose.yml up ...

[学习记录,]Mybatis入门

环境: Eclipse 2019 Tomcat 9.0 jdk1.8 开搞 首先创建工程 结构如下 导入Jar包 可在mybatis官网下载 http://www.mybatis.cn/82.html 配置文件mybatis-config.xml 事务管理有两种:JDBC和MANAGED JDBC: MANAGED 数据源类型:UNPOOLED、POOLED、JNDI 创建实体类文件User.ja...

运用for语句来判断数组中值得大小

总结: 1将if语句与数组联合起来判断输入中各组中的最大最小值; 2注意:定义的数组数量是躲多少就要输入多少组数据,少输入就无法输出;...

Bridging signals

Bridging signals Time Limit: 1000MSMemory Limit: 10000K Total Submissions: 10926Accepted: 5982 Description 'Oh no, they've done it again', cries the chief designer at the Waferland chip factory. Once ...

猜你喜欢

一天一大 leet

一天一大 leet 题目(难度:困难): 示例 抛砖引玉 官方答案 高手在民间 菜鸡的自白 20200606 题目(难度:困难): 给定一个未排序的整数数组,找出最长连续序列的长度。 要求算法的时间复杂度为 O(n)。 示例 示例 抛砖引玉 要求算法的时间复杂度为 O(n),即限制了只能循环一次; 先对数组排序 循环数组记录后一个元素等于前一个元素+1或者等于前一个元素的数量 满足条件++,不然重...

Tensorflow实现的CNN文本分类

https://blog.csdn.net/somtian/article/details/69359498 原博文, github 在这篇文章中,我们将实现一个类似于Kim Yoon的卷积神经网络语句分类的模型。 本文提出的模型在一系列文本分类任务(如情感分析)中实现了良好的分类性能,并已成为新的文本分类架构的标准基准。 本文假设你已经熟悉了应用于NLP的卷积神经网络的基础知识。 如果没有,建议...

JDBC新手入门教程

开发工具:idea 数据库:mysql jar包:junit-4.10 mysql-connector-java-8.0.18 junit-4.10下载 mysql-connector-java-8.0.18下载 注意1:jdbc的驱动因为使用的是mysql-connector-java-8.0.18,所以为(“com.mysql.cj.jdbc.Driver”),而不是(...

Lua 排序 table.sort

    正如C#中有Array.Sort(),lua中也有自己的排序方法即table.sort(table,function)。     lua中的排序默认是从大到小的排序;     传入一个方法参数,可以使排序从小到大; 打印结果:  ...

SURF算法简述及Python标记SURF特征检测实践

目录 一、SURF算法 1.算法简介 2.SURF与SIFT的具体差异 二、Python代码实践 1.测试环境 2.测试代码 3.核心函数 4.测试结果 一、SURF算法 1.算法简介 SURF(Speeded-Up Robust Features)加速稳健特征,是一种稳健的局部特征点检测和描述算法。 SURF是对SIFT算法的改进,该算子在保持 SIFT 算子优良性能特点的基础上,同时解决了 S...