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的更多相关文章

  1. Java并发编程实战笔记—— 并发编程2

    1.ThreadLocal Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作.因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadL ...

  2. Java并发编程实战笔记—— 并发编程4

    1.同步容器类 同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁保护复合操作. 容器上常见的复合操作包括但不限于:迭代(反复访问数据,直到遍历完容器中所有的元素为止).跳转(根据指定顺 ...

  3. Java并发编程实战笔记—— 并发编程3

    1.实例封闭 class personset{ private final Set<Person> myset = new HashSet<Person>(); public ...

  4. Java并发编程实战.笔记十一(非阻塞同步机制)

    关于非阻塞算法CAS. 比较并交换CAS:CAS包含了3个操作数---需要读写的内存位置V,进行比较的值A和拟写入的新值B.当且仅当V的值等于A时,CAS才会通过原子的方式用新值B来更新V的值,否则不 ...

  5. Java并发编程实战 01并发编程的Bug源头

    摘要 编写正确的并发程序对我来说是一件极其困难的事情,由于知识不足,只知道synchronized这个修饰符进行同步. 本文为学习极客时间:Java并发编程实战 01的总结,文章取图也是来自于该文章 ...

  6. 【ARM-Linux开发】OpenACC并行编程实战笔记

    今年运气比较好,学了cuda之后,了解到了gpu的另两种使用语言opencl和openacc,  opencl(Open Computing Language ,开放计算语言)是面向异构系统的并行编程 ...

  7. 多线程-java并发编程实战笔记

    线程安全性 编写线程安全的代码实质上就是管理对状态的访问,而且通常都是共享的,可变的状态. 一个对象的状态就是他的数据,存储在状态变量中,比如实例域或静态域.所谓共享是指一个对象可以被多个线程访问:所 ...

  8. java并发编程实战笔记---(第四章)对象的组合

    4.1设计线程安全的类 包含三个基本要素: 1.找出构成对象状态的所有变量 2.找出约束状态变量的不变性条件 2.简历对象状态的并发访问管理策略 对象的状态: 域 基本类型所有域, 引用类型包括被引用 ...

  9. java并发编程实战笔记---(第三章)对象的共享

    3.1 可见性 synchronized 不仅实现了原子性操作或者确定了临界区,而且确保内存可见性. *****必须在同步中才能保证:当一个线程修改了对象状态之后,另一个线程可以看到发生的状态变化. ...

随机推荐

  1. Django随机生成验证码图片

    PIL简介 什么是PIL PIL:是Python Image Library的缩写,图像处理的模块.主要的类包括Image,ImageFont,ImageDraw,ImageFilter PIL的导入 ...

  2. Java编程思想:File类getCanonicalFile()方法,listFile()方法

    import java.io.IOException; import java.util.*; import java.util.regex.Pattern; import java.io.File; ...

  3. mac环境下java项目无创建文件的权限

    1.问题: 先抛问题,由于刚刚换用mac环境,之前windows上开发的代码调试完毕,还未上线.之后上线部署之前,tl直连测试本地环境(mac)环境,功能无法使用,显示java.io.IOExcept ...

  4. 整型,布尔值,字符串详解,for语句 练习

    2019 年 7 月 8 日 1.将今天的课上的代码敲一遍,然后整理笔记 已完成 2.有变量name = "aleX leNb" 完成如下操作: 移除 name 变量对应的值两边的 ...

  5. Python爬虫学习代码

    [1]用一个简单的程序来显示Python的数字类型. code: class ShowNumType(object): def __init__(self): self.showInt() self. ...

  6. 从0系统学Android--1.2 手把手带你搭建开发环境

    要想进行程序开发,首先我们需要搭建开发环境,下面就开始搭建环境. 1.2.1 所需的工具 首先 Android 开发是基于 Java 的,因此你需要掌握简单的 Java 语法.会基础的 Java 语法 ...

  7. CF543B Destroying Roads 题解

    看到没有题解就贡献一波呗 分析: 这题其实就是想让我们求一个图中两条最短路的最短(好把更多的边删掉). 我们先考虑一条最短路,别问我我怎么会的显然,就是s和t跑个最短路再用n-就行. 然后就是两条喽! ...

  8. 【投票】你心目中的Excel催化剂价值有多大(附主流国内外收费插件供参考)?

    Excel催化剂开发过程中,在前期功能中,也会参考着其他的插件的功能,也略略收集了一些其他插件的功能及价格信息,在当今免费等于鸡肋的时代,为了让大家不受这个免费的错觉影响,不妨以另一种角度去假设一翻, ...

  9. MySql的数据库优化到底优啥了都??(2)

    嘟嘟在写此篇文章之前心里先默念(简单,通俗,工整)*10 吟唱完了,进入正题 3.Memory存储引擎 除了存放一个表结构相关信息的.frm文件在磁盘上,其他数据都存储在内存中.说白点哪天你数据库死机 ...

  10. angularjs compine和link的区别

    [译]ng指令中的compile与link函数解析 04 September 2014 通常大家在使用ng中的指令的时候,用的链接函数最多的是link属性,下面这篇文章将告诉大家complie,pre ...