电子商务网站建设有哪些流程,seo网站建设是什么,网站建站网站496565,济南seo外包服务面向过程编程#xff0c;把数据和过程分别作为独立的部分考虑#xff0c;数据代表问题空间中的客体#xff0c;程序代码则用于处理这些数据#xff1b;面向对象编程#xff0c;把数据和行为都看做对象的一部分#xff0c;以符合现实世界的思维方式来编写和组织程序#…面向过程编程把数据和过程分别作为独立的部分考虑数据代表问题空间中的客体程序代码则用于处理这些数据面向对象编程把数据和行为都看做对象的一部分以符合现实世界的思维方式来编写和组织程序
对象在一项工作进行期间会不停的中断和切换线程对象的数据数据可能会在中断期间被修改和变脏这将使的并发变得不安全
线程安全当多个线程同时访问同一对象若不用考虑这些线程在运行时环境下的调度和交替执行也不需要进行额外的同步或者在调用方进行其他协调操作调用这个对象的行为都可以获得正确的结果则称这个对象是线程安全的代码本身封装了所有必要的正确性保障手段调用者不需要关心多线程的调用问题 文章目录1. Java 语言中的线程安全2. 线程安全的实现方法1. Java 语言中的线程安全
线程安全的五个级别
不可变Immutable不可变对象一定是线程安全的最直接、最纯粹的安全一旦不可变对象被正确的构建出来它永远不会在多线程中处于不一致的状态
若多线程共享的数据是一个基本数据类型只要限定为 final 类型则它是不可变的
若多线程共享的数据是一个对象由于 Java 语言暂时没有值类型需要对象自行保证其行为不会对其状态产生任何影响如 String 类型它的 subString()、replace()、concat() 都不会影响它原来的值而是返回一个新构造的对象最简单的方式是将对象中带有状态的变量都声明为 final
Java 类库 API 中不可变对象还有 java.lang.Number 的部分子类Long、Double、BigInteger、BigDecimal 等AotmicInteger、AtomicLong 是可变类型
绝对线程安全完全满足上文线程安全定义不管运行时环境如何调用者都不需要任何额外的同步措施
private static VectorInteger vector new VectorInteger();public static void main(String[] args) {while (true) {for (int i 0; i 10; i) {vector.add(i);}Thread removeThread new Thread(() - {for (int i 0; i vector.size(); i) {vector.remove(i);}});Thread printThread new Thread(() - {for (int i 0; i vector.size(); i) {System.out.println((vector.get(i)));}});removeThread.start();printThread.start();// 不要同时产生过多的线程否则会导致操作系统假死while (Thread.activeCount() 20) ;}
}运行结果
Exception in thread Thread-24207 java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 9at java.util.Vector.get(Vector.java:753)at edu.aurelius.jvm.concurrent.VectorTest.lambda$main$1(VectorTest.java:25)at java.lang.Thread.run(Thread.java:750)Vector 是一个线程安全的容器但并非绝对线程安全其 add()、get()、size() 等方法都被 synchronized 修饰了但在多线程环境下若不对调用端做额外同步限制这段代码仍不安全
线程安全的写法
Thread removeThread new Thread(() - {synchronized (vector) {for (int i 0; i vector.size(); i) {vector.remove(i);}}
});Thread printThread new Thread(() - {synchronized (vector) {for (int i 0; i vector.size(); i) {System.out.println((vector.get(i)));}}
});相对线程安全通常意义上的线程安全保障单次操作的线程安全但不保证一些特定顺序的联系调用安全见上例如 Vector、HashTable、Collections 的 synchronizedCollection() 方法包装的集合等 线程兼容对象本身不是线程安全的到哪可以通过在调用端正确的使用同步手段保证对象在并发环境中的安全通常说的不是线程安全的类型如集合类 ArrayList、HashMap 等 线程对立不管调用端使用采用同步措施都无法在多线程环境中并发使用的代码如同一个 Thread 类对象的 suspend() 和 resume() 同时在两个线程中调用一个尝试中断线程一个尝试恢复线程无论是否进行了同步线程都存在死锁的分析还有 System.setIn、System.setOut、System.runFinalizersOnExit
2. 线程安全的实现方法
只要明白了 JVM 线程安全措施的原理与运作过程如何编写并发安全的代码便不再困难
互斥同步Mutual Exclusion Synchronization在多线程并发访问共享数据时保障共享数据在同一时刻只被一条或者一些当使用信号量时线程使用互斥可以通过临界区Critical Selection、互斥量Mutex、信号量Semaphore等实现
synchronized 关键字
synchronized 经过 javac 编译会在同步快的前后分别形成 monitorenter 和 monitorexit 两个字节指令这两个字节指令通过一个 reference 类型的参数指明要锁定和解锁的对象若没有指定 reference 对象synchronized 修饰的是实例方法则锁定方法所在类的实例synchronized 修饰的是静态方法则锁定方法所在类的 Class 对象
《Java 虚拟机规范》要求执行 monitorenter 指令时首先要尝试获取对象所如果这个对象没有被锁定或者当前线程已经获得了这个对象的所则把锁的计数器的值加 1执行 monitorexit 时将锁的计数器值减 1一旦计数器值为 0锁随机被释放若获取锁失败则当前线程被阻塞等待其他线程释放
持有锁是一个重量级操作阻塞或唤醒一个线程需要操作系统来完成这会陷入用户态和核心态的转换这需要耗费很多处理器时间
java.util.concurrent.locks.Lock 接口
Lock 接口是 Java 的另一种互斥同步手段用户可以以非块结构Non-Block Structured来实现互斥同步
重入锁ReentrantLock是 Lock 接口最常见的一种实现与 synchronized 相似只是多了一些高级功能等待可中断、可实现公平锁、可绑定多个条件
等待可中断当持有锁的线程长期不释放锁正在等待的线程可以选择放弃等待这对处理较长时间的同步块很有帮助
公平锁多个线程在等待同一锁时必须按照申请锁的时间顺序来依次获得锁synchronized 的锁是非公平的 ReentrantLock 默认也是非公平的公平锁会导致 ReentrantLock 的性能急剧下降明显影响吞吐量
锁绑定多个条件一个 ReentrantLock 对象绑定多个 Condition 对象在 synchronized 中锁对象的 wait() 和它的 notify()/notifyAll() 配合可以实现一个隐含条件ReentrantLock 对象可以多次调用 newCondition() 绑定多个条件
synchronized vs. ReentrantLock
JDK 5 时 synchronized 有非常大的优化余地ReentrantLock 表现更稳定 JDK 6 时 synchronized 锁得到优化与 ReentrantLock 的性能基本持平性能不再试选择的关键因素 ReentrantLock 在功能上是 synchronized 的超集但 synchronized 是 Java 语法层面的同步更清晰简单且自动处理异常时的锁释放JVM 也更容易战队 synchronized 进行优化所以在功能皆满足情况下推荐使用 synchronized
非阻塞同步Non-Blocking Synchronized基于冲突检测的乐观并发策略不管风险先进性操作若没有其他线程争用共享数据则操作成功若共享数据被争用产生了冲突则进行补偿操作如不停尝试直到没有竞争为止这种乐观并发策略不需要阻塞挂起线程因此称为非阻塞同步
原子性处理器指令集
a. 测试并设置Test-and-Set b. 获取并增加Fetch-and-Increment c. 交换Swap d. 比较并交换Compare-and-SwapCAS e. 加载链接/条件存储Load-Linked/Store-ConditionalLL/SC
Java 最终暴露出来的是 CAS 操作通过三个操作数内存位置 V、就的预期值 A、准备设置的新值 B当且仅当 V 符合 A 时处理器才会用 B 更新 V不管是否更新成功都返回 V 的旧值
Atomic 原子自增运算
public static AtomicInteger race new AtomicInteger(0);public static void increase() {race.incrementAndGet();
}private static final int THREADS_COUNT 20;public static void main(String[] args) throws Exception {Thread[] threads new Thread[THREADS_COUNT];for (int i 0; i THREADS_COUNT; i) {threads[i] new Thread(() - {for (int i1 0; i1 10000; i1) {increase();}});threads[i].start();}while (Thread.activeCount() 1) Thread.yield();System.out.println(race);
}incrementAndGet() 的 JDK 源码
/**
* Atomically increment by one the current value.
* return the updated value
*/
public final int incrementAndGet() {for (;;) {int current get();int next current 1;if (compareAndSet(current, next))return next;}
}不断尝试将一个比当前值大一的新值赋给自己若失败则说明旧值发生了变化再次循环操作直到设置成功为止
CAS 操作的 ABA 问题当变量 V 初次读取时是 A 值准备复制时检测它还是 A但实际他已经被改成 B并改回 A 了可通过原子引用类 AtomicStampedReference 保证 CAS 的正确性通过给 V 添加版本号不过传统的互斥同步可能会更高效
无同步方案若让一段代码本来就不涉及线程共享数据那它天生就是线程安全的
可重入代码Reentrant Code又称纯代码Pure Code指在多线程的上下文语境中不涉及信号量等因素不依赖全局变量、存储在堆上的数据和公用的系统资源用到的状态量都是由参数传入不调用非可重入的方法等可重入代码是线程安全代码的真子集
线程本地存储Thread Local Storage若共享数据能保证只在同一线程中共享可将共享数据的可见范围限制在同一线程内这样就无需同步也能保证线程不出现数据争用问题
Java 语言中若一个变量被多个线程访问可使用 volatile 将之声明为易变的若一个变量只被单线程独享可以通过 ThreadLocal 类实现线程本地存储每个 Thread 对象中都有一个 ThreadLocalMap 对象以 ThreadLocal.threadLocalHashCode 为键以本地线程变量为值ThreadLocal 对象为当前线程的 Map 的访问入口 上一篇「JVM 高效并发」Java 协程
PS感谢每一位志同道合者的阅读欢迎关注、评论、赞 参考资料
[1]《深入理解 Java 虚拟机》