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. mybatis的插入与批量插入的返回ID的原理

    目录 背景 底层调用方法 单个对象插入 列表批量插入 完成 背景 最近正在整理之前基于mybatis的半ORM框架.原本的框架底层类ORM操作是通过StringBuilder的append拼接的,这次 ...

  2. Java连载5-标识符、关键字和字面值

    一.标识符 1.标识符定义:在java源程序中凡是可以自己命名的单词 2.标识符可以标识什么元素? (1)类名(2)方法名(3)变量名(4)接口名(5)常量名 等等 3.标识符的命名要求 (1)一个合 ...

  3. 基于SpringBoot的Web API快速开发基础框架

    其实还是很因为懒,才会有这个案例项目的产生,每次开启一个终端的小服务都要整理一次框架,造成重复的.不必要的.缺乏创造性的劳动,SO,本着可以用.用着简单的原则上传代码到Github,希望有需要的朋友直 ...

  4. Spring Boot 邮件发送的 5 种姿势!

    邮件发送其实是一个非常常见的需求,用户注册,找回密码等地方,都会用到,使用 JavaSE 代码发送邮件,步骤还是挺繁琐的,Spring Boot 中对于邮件发送,提供了相关的自动化配置类,使得邮件发送 ...

  5. [Poi2012]Festival 题解

    [Poi2012]Festival 时间限制: 1 Sec  内存限制: 64 MB 题目描述 有n个正整数X1,X2,...,Xn,再给出m1+m2个限制条件,限制分为两类: 1. 给出a,b (1 ...

  6. 树链剖分 [JLOI2014]松鼠的新家

    [JLOI2014]松鼠的新家 时间限制: 1 Sec  内存限制: 128 MB 题目描述 松鼠的新家是一棵树,前几天刚刚装修了新家,新家有n个房间,并且有n-1根树枝连接,每个房间都可以相互到达, ...

  7. 学习 Python 心得

    脚本式编程: 通过脚本参数调用解释器开始执行脚本,直到脚本执行完毕.当脚本执行完成后,解释器不再有效. 让我们写一个简单的 Python 脚本程序.所有 Python 文件将以 .py 为扩展名.将以 ...

  8. C# 与 JS 之间传值在 cshtml页面中

    @{ string It = "sss"; ; } @functions{ string Mod = "ajssaioi"; public string Itm ...

  9. 个人永久性免费-Excel催化剂功能第104波-批量选择多种类型的图形对象

    在Excel的日常操作过程中,选择绝对是一个高频的操作,之前开发过一些快速选择单元格区域的辅助功能,除了单元格区域,Excel强大之处在于,类似PhotoShop那般可以存放多种图形,并且有图层先后顺 ...

  10. 随机点名可视化界面,记录迟到人员,转exe文件

    随机点名可视化界面,记录迟到人员,转exe文件 一.介绍 对于人员采取随机点名 二.代码 import datetime import random from tkinter import * fro ...