Java并发编程实战笔记—— 并发编程1
1、如何创建并运行java线程
创建一个线程可以继承java的Thread类,或者实现Runnabe接口。
public class thread {
static class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("run a myThread1");
}
}
static class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("run a myThread2");
}
}
public static void main(String[] args){
MyThread1 myThread1 = new MyThread1();
myThread1.start(); Thread myThread2 = new Thread(new MyThread2());
myThread2.start();
}
}
输出:
run a myThread1
run a myThread2
或者是创建一个实现了Runnable接口的匿名类
public class thread { public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("run a myThread3");
}
}).start();
}
}
输出:
run a myThread3
2、安全性
线程安全性可能是非常复杂的,在没有充足同步的情况下,多个线程的操作执行顺序是不同的,会产生非常奇怪的结果
class Fun{
private int value;
public int getNext(){
return value++;
}
}
这个类中的getNext()是一个非线程安全的方法。如果直接在线程中调用时:
public class thread {
public static void main(String[] args) {
Fun fun = new Fun();
for (int j = 0; j < 10; j++) {
new Thread() {
@Override
public void run() {
System.out.println(fun.getNext());
}
}.start();
}
}
}
输出:
0
1
2
3
4
5
6
7
8
9
结果是未知的,每一次运行的结果都不一样,这是因为递增运算value++看上去是一个单独的操作,但实际上它包含了三个独立的操作:读取value,将value加1,将结果写入value。由于运行时可能是多个线程交替执行的这就可能会导致当线程1还没将计算结果存入value时,线程2已经启动并已经读取了value的值。
java提供了各种同步机制来协同这种访问,比如这样修改这个类:
class Fun{
private int value;
public synchronized int getNext(){
return value++;
}
}
这次main函数的输出是确定的:
0
1
2
3
4
5
6
7
8
9
一个对象是否是需要线程安全,取决于它是否被多个线程访问,这里注重的是在程序中访问对象的方式,而不是对象想要实现的功能。要使得对象是线程安全的,需要采用同步机制来协同对对象可变状态的访问。java中的主要同步机制synchronized,它提供一种独占的加锁方式,另外还有volatile类型的变量,显式锁,原子变量。
线程安全的定义:
当多个线程访问某个类的时候,不管运行时环境采取何种调度方式,并且在主调代码中不需要任何额外的同步或者协同,这个类都可以表现出正确的行为,那么就称这个类是线程安全的。
3、竞态条件
在并发编程中,由于不恰当的执行时序而出现不正确的结果,称作:竞态条件
class Fun{
private Object instance = null;
public Object getNext(){
if (instance == null)
instance = new Object();
return instance;
}
}
这是一个最常见的竞态条件类型:“先检查后执行”,通过一个可能失效的观测结果来决定下一步的动作。这里的目的是将对象初始化的操作推迟到执行的时候才初始化,并确保只被初始化一次。
假设线程A和线程B需要同时执行getNext(),A看到instance为空,从而创建一个新的Object实例,B同样需要判断instance是否为空,但是线程B到底需不需要实例化,取决于不可确定的时序,线程的调度方式,以及A需要多长时间进行初始化。
要想避免竞态条件问题,就必须在某个线程修改某个变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改完成之前或是之后读取和修改状态。
所以,假定有两个操作A和B,如果从执行A的线程来看,当另一个线程执行B时,要么等待B全部执行完,要么完全不执行B,那么A和B对彼此而言都是原子的。原子操作是指,对于访问同一个状态的所有操作来说,这个操作是以原子的方式执行操作的。
4、加锁机制
JAVA提供了一种内置的锁机制来支持原子性:同步代码块。同步代码块分为两个部分:一个作为锁得对象引用,一个作为又这个锁保护的代码块。以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步方法块的所就是方法调用所在的对象,
synchronized(lock){
//访问或修改由锁保护的共享状态
}
每个JAVA对象,都可以用一个实现同步的锁,线程进入同步代码块之前自动获得锁,并且在退出同步代码块时自动释放锁,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。
当线程A尝试获取一个由线程B持有的锁时,线程A必须等待或者阻塞,直到线程B释放这个锁。如果B不释放这个锁,A也将永远等下去。
由于一次只能由一个线程执行内置锁持有的代码块,所以由这个锁保护的代码块会以原子方式执行。
5、可见性
在单线程环境中,如果向某个变量先写入值,然后 在没有其他写入操作的情况下读取这个变量,那么总能得到正确的值,但是在多线程环境中,我们无法保证执行读操作的线程能适时的看到其他线程写入的值,有时是根本不可能的事。为了保证多个线程之间对内存写入操作得可见性,必须使用同步机制。
public class Fun {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread{
public void run(){
while (!ready)
Thread.yield();
System.out.println(number);
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 42;
ready = true;
}
}
Fun可能会永远持续循环下去,因为读线程可能永远都看不到ready得值,也可能是一直输出时0,因为可能看到了写入ready的值,没有看到number的值
6、Volatile变量
这是Java的另一种同步机制,volatile变量,用来确保将变量的更新操作通知到其他的线程,把变量声明为volatile类型之后,编译器运行时会注意到这个变量是共享的,因此不会把这个变量上的操作与其他内存操作一起进行重排序,volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此对volatile类型的变量总会返回最新写入的值。
写入volatile变量相当于退出了同步代码块,而读取volatile变量等于进入了同步代码块。
仅当volatile变量用作验证的时候,才应该使用它,例如,volatile的语义并不足以确保递增操作(count++)的原子性,除非能确保只有一个线程执行写操作,所以如果验证时对可见性进行复杂的判断,就不要使用volatile变量了。
7、发布和逸出
发布一个对象的意思就是指,是能够使对象在当前作用域之外的代码使用,比如:将一个指向该对象的引用保存到其他代码可以访问的地方,或者在某一处非私有的方法中返回该引用。在许多情况下,我们要确保对象及其内部状态不被发布,例如在对象构造完成之前就发布该对象,就会破坏线程安全性。当一个不该发布的对象被发布时,这种情况称为逸出。
首先来看一个对象是如何逸出的:
发布一个对象最简单的方法就是将对象得引用保存到一个公有的静态变量中,以便所有的类都能看见该类。
public static Set<Secret> knowSecrets;
public void initialize(){
knowSecrets = new HashSet<Secret>();
}
在initialize方法中 实例化了一个新的HashSet对象,并将对象的引用保存在了knowSecrets中以发布该对象。
当发布某个对象的时候,可能间接地发布其他对象如果讲一个Secret对象添加到集合knowSecrets中,同样也会发布这个对象,因为任何代码都可以遍历这个集合,并获得新Secrect的引用。
class Unsafe{
private String[] states = new String[]{
"AK","AL"
};
public String[] getstates(){
return states;
}
}
在这个实例中states原本是一个私有变量,因为getstates方法返回了它的引用,所以任何调用者都可以修改这个数组中的内容。
无论其他线程会对已发布的引用执行何种操作,其实都不重要,因为误用改引用的风险始终存在。
8、ThreadLocal类
ThreadLocal类是一种维持线程封闭性的规范方法,这个类能使使线程中的某个值与保存值的对象关联起来。ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。
ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享。例如,在单线程应用程序中可能会维持一个全局的数据库连接,并在程序初始化这个连接对象,从而避免在调用每个方法都要传递一个Connection对象。
当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用ThreadLocal.
Java并发编程实战笔记—— 并发编程1的更多相关文章
- Java并发编程实战笔记—— 并发编程2
1.ThreadLocal Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作.因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadL ...
- Java并发编程实战笔记—— 并发编程4
1.同步容器类 同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁保护复合操作. 容器上常见的复合操作包括但不限于:迭代(反复访问数据,直到遍历完容器中所有的元素为止).跳转(根据指定顺 ...
- Java并发编程实战笔记—— 并发编程3
1.实例封闭 class personset{ private final Set<Person> myset = new HashSet<Person>(); public ...
- Java并发编程实战.笔记十一(非阻塞同步机制)
关于非阻塞算法CAS. 比较并交换CAS:CAS包含了3个操作数---需要读写的内存位置V,进行比较的值A和拟写入的新值B.当且仅当V的值等于A时,CAS才会通过原子的方式用新值B来更新V的值,否则不 ...
- Java并发编程实战 01并发编程的Bug源头
摘要 编写正确的并发程序对我来说是一件极其困难的事情,由于知识不足,只知道synchronized这个修饰符进行同步. 本文为学习极客时间:Java并发编程实战 01的总结,文章取图也是来自于该文章 ...
- 【ARM-Linux开发】OpenACC并行编程实战笔记
今年运气比较好,学了cuda之后,了解到了gpu的另两种使用语言opencl和openacc, opencl(Open Computing Language ,开放计算语言)是面向异构系统的并行编程 ...
- 多线程-java并发编程实战笔记
线程安全性 编写线程安全的代码实质上就是管理对状态的访问,而且通常都是共享的,可变的状态. 一个对象的状态就是他的数据,存储在状态变量中,比如实例域或静态域.所谓共享是指一个对象可以被多个线程访问:所 ...
- java并发编程实战笔记---(第四章)对象的组合
4.1设计线程安全的类 包含三个基本要素: 1.找出构成对象状态的所有变量 2.找出约束状态变量的不变性条件 2.简历对象状态的并发访问管理策略 对象的状态: 域 基本类型所有域, 引用类型包括被引用 ...
- java并发编程实战笔记---(第三章)对象的共享
3.1 可见性 synchronized 不仅实现了原子性操作或者确定了临界区,而且确保内存可见性. *****必须在同步中才能保证:当一个线程修改了对象状态之后,另一个线程可以看到发生的状态变化. ...
随机推荐
- 解决Tomcat catalina.out 不断膨胀,导致磁盘占用过大的问题
到服务器上看了一下任务中心的日志情况,膨胀的很快,必须采取措施限制其增长速度. 我们采用Cronlog组件对此进行日志切分,官网http://cronolog.org/一直未能打开,只能从其它地方寻找 ...
- Unix及Linux编辑器vi/vim基本使用方法
- [译]Vulkan教程(33)多重采样
[译]Vulkan教程(33)多重采样 Multisampling 多重采样 Introduction 入门 Our program can now load multiple levels of d ...
- Excel公式中问题-记住不要忽略空格!
总结一下之前犯得愚蠢的小问题: 程序:每日报表:从DB下载数据填充到excel,包括3个sheet,sheet1:总结<模板,公式填充,数据源为sheet2,sheet3>;sheet2: ...
- [记录]一则HTTP配置文件参考记录
# cat ../conf/httpd.conf | grep -vE "^$|^#" ServerTokens OS ServerRoot "/etc/httpd&qu ...
- Excel催化剂开源第34波-SM.MS图床API调用(用POST上传multipart/form-data内容)
日常做网抓数据,都是以GET请求为主,偶尔遇到需要POST请求的,一般POST的参数只是一串字符串就可以了,通过构造字符串也很容易完成,但此次SM.MS的API接口要求是Content-Type: m ...
- 关键字static、final
final final能修饰类.修饰方法.能修饰属性. 修饰类:该类不能被继承. 修饰方法:该方法不能被重写.所以abstract和final不能同时用 修饰属性/变量:该属性/变量为常量,该值不能再 ...
- 安卓开发之详解getChildFragmentManager和getsupportFragmentManager和getFragmentManager详解
安卓开发之详解getChildFragmentManager和getsupportFragmentManager和getFragmentManager详解 getFragmentManager()所得 ...
- TensorFlow笔记-组件
张量 TensorFlow用张量这种数据结构来表示所有的数据.你可以把一个张量想象成一个n维的数组或列表.一个张量有一个静态类型和动态类型的维数.张量可以在图中的节点之间流通.其实张量更代表的就是一种 ...
- maven install时跳过测试
xl_echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!! - ...