多线程
8锁现象
1(标准):普通方法的synchronized
package com.wh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁现象
* 1:标准情况下,两个线程先打印 发短信还是打电话?
*/
public class xian01 {
public static void main(String[] args) {
Phone phone = new Phone();
//A,B两个线程,永远是先发短信再打电话
//因为A,B两个线程用的是同一把锁,phone对象
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者
//两个方法是同一把锁,谁先拿到谁执行
public synchronized void sendSms(){
System.out.println("发短信");
}
//synchronized 锁的对象是方法的调用者
public synchronized void call(){
System.out.println("打电话");
}
}
结果:
2(标准加延迟)
package com.wh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁现象
* 1:标准情况下,两个线程先打印 发短信还是打电话?
* 2: 发短信延迟了4秒后,谁先打印
*/
public class xian01 {
public static void main(String[] args) {
Phone phone = new Phone();
//A,B两个线程,永远是先发短信再打电话
//因为A,B两个线程用的是同一把锁,phone对象
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者
//两个方法是同一把锁,谁先拿到谁执行
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//synchronized 锁的对象是方法的调用者
public synchronized void call(){
System.out.println("打电话");
}
}
结果:
还是先发短信再打电话–————开始就是短信先获取的锁,所以只能等短信完事,释放锁后,打电话才能获取锁,再执行。
3:与普通方法
package com.wh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁现象
* 3:先打印hello 还是发短信 (开启两个线程的是同一个对象)
*/
public class xian02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.hello();
},"B").start();
}
}
class Phone2{
//synchronized 锁的对象是方法的调用者
//两个方法是同一把锁,谁先拿到谁执行
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//synchronized 锁的对象是方法的调用者
public synchronized void call(){
System.out.println("打电话");
}
//这里没有锁,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
结果:
分析:hello方法没有锁,再主方法延迟1秒后,执行hello方法。而发短信在自己方法里延迟了4秒,所以hello先执行,发短信后执行。
4:两个对象两把锁
package com.wh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁现象
* 4: 两个对象情况下,发短信和打电话两个同步方法,谁先执行
* 分析:两把锁,谁延迟时间少,谁先执行
*/
public class xian02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone2{
//synchronized 锁的对象是方法的调用者
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//synchronized 锁的对象是方法的调用者
public synchronized void call(){
System.out.println("打电话");
}
//这里没有锁,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
5:静态的同步方法
package com.wh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁现象
* 5: 增加两个静态的同步方法,只有一个对象,先打印发短信还是打电话?
*/
public class xian03 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
//Phone3唯一的一个Class对象
class Phone3 {
//synchronized 锁的对象是方法的调用者
//static 静态方法
//类一加载就有了!锁的是Class
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//synchronized 锁的对象是方法的调用者
public static synchronized void call() {
System.out.println("打电话");
}
}
结果:
6:多对象情况下----静态的同步方法
package com.wh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁现象
* 6: 两个对象情况下,增加两个静态同步方法,先打印打电话?发短信?
*/
public class xian03 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
//Phone3唯一的一个Class对象
class Phone3 {
//synchronized 锁的对象是方法的调用者
//static 静态方法
//类一加载就有了!锁的是Class
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//synchronized 锁的对象是方法的调用者
public static synchronized void call() {
System.out.println("打电话");
}
}
结果:
7: 同一个对象的普通同步方法和静态同步方法
package com.wh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁现象
* 7:一个普通同步方法,一个静态同步方法,一个对象 先打印打电话?发短信?
*/
public class xian04 {
public static void main(String[] args) {
//两个对象的Class类模板只有一个,static,锁的是Class
Phone4 phone = new Phone4();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone4 {
//静态的同步方法
//锁的是Class类模板
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通的同步方法
//锁的是调用者
public synchronized void call() {
System.out.println("打电话");
}
}
连个方法不是同一把锁。
8: 不同对象的普通同步方法和静态同步方法
package com.wh.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁现象
* 7:一个普通同步方法,一个静态同步方法,一个对象 先打印打电话?发短信?
*/
public class xian04 {
public static void main(String[] args) {
//两个对象的Class类模板只有一个,static,锁的是Class
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone4 {
//静态的同步方法
//锁的是Class类模板
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通的同步方法
//锁的是调用者
public synchronized void call() {
System.out.println("打电话");
}
}
结果:
小结
new—>指的是this–>具体的一个手机
static–>指的是Class–>唯一的一个模板
传统的生产者消费者问题
package com.wh.pc;
/**
* 生产者消费者问题
*/
public class Sc {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for(int i=0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//等待,业务,通知
class Data{
private int number=0;
//+1
public synchronized void increment() throws InterruptedException {
if(number!=0){
//等待
this.wait();
}
number++;
this.notifyAll();
System.out.println("增=="+Thread.currentThread().getName()+"-->"+number);
//通知其他线程,我+1完毕了
}
//-1
public synchronized void decrement() throws InterruptedException {
if(number==0){
//等待
this.wait();
}
number--;
//通知其他线程,我-1完毕了
this.notifyAll();
System.out.println("减=="+Thread.currentThread().getName()+"-->"+number);
}
}
问题: 此时是2个线程,保持一个加一个减;当线程多了呢,比如四个线程
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for(int i=0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for(int i=0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"c").start();
new Thread(()->{
for(int i=0;i<10;i++){
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"d").start();
}
class Data{
private int number=0;
//+1
public synchronized void increment() throws InterruptedException {
while (number!=0){
//等待
this.wait();
}
number++;
this.notifyAll();
System.out.println("增=="+Thread.currentThread().getName()+"-->"+number);
//通知其他线程,我+1完毕了
}
//-1
public synchronized void decrement() throws InterruptedException {
while (number==0){
//等待
this.wait();
}
number--;
//通知其他线程,我-1完毕了
this.notifyAll();
System.out.println("减=="+Thread.currentThread().getName()+"-->"+number);
}
}
总结:
if判断为空时,线程阻塞,这时if判断就完成了,当线程被唤醒后,继续执行线程剩余的操作
while判断为空时,线程阻塞,这时while循环没有完成,当线程被唤醒后,先进行while判断
JUC的生产者消费者问题
Lock
package com.wh.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* JUC下的生产者消费者
*/
public class JUC_Sc {
public static void main(String[] args) {
Sc2 sc2 = new Sc2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
sc2.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
sc2.decrement();
}
}, "B").start();
}
}
class Sc2 {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//+1
public void increment() {
lock.lock();
try {
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "-->" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement() {
lock.lock();
try {
while (number == 0) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "-->" + number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
问题:此时是2个线程,保持一个加一个减;当线程多了呢,比如四个线程?
public class JUC_Sc {
public static void main(String[] args) {
Sc2 sc2 = new Sc2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
sc2.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
sc2.decrement();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
sc2.increment();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
sc2.decrement();
}
}, "D").start();
}
}
精准唤醒(Condition)
package com.wh.pc;
import java.util.Enumeration;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* JUC下 Condition精准唤醒
*/
public class JUC_Condi_Sc {
public static void main(String[] args) {
Sc3 sc3 = new Sc3();
new Thread(()->{
for(int i=0;i<10;i++){
sc3.printA();
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;i++){
sc3.printB();
}
},"B").start();
new Thread(()->{
for(int i=0;i<10;i++){
sc3.printC();
}
},"C").start();
}
}
class Sc3 {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();//A
private Condition condition2 = lock.newCondition();//B
private Condition condition3 = lock.newCondition();//C
private int number = 1; //1->A 2->B 3->C
public void printA() {
lock.lock();
try {
while (number != 1) {
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "-->" + number);
number++;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (number != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "-->" + number);
number++;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (number != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "-->" + number);
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
callable
类似于Runnable接口,Runnable没有返回值也不能抛出异常
Callable特点
1:可以有返回值
2:可以抛出异常
3:方法不同,Runnable–>run() Callable–>call()
问题:怎么用Thread.start()运行Callable?
根据Thread构造方法可知,Thread只能塞入Runnable接口或者其实现类,所以找找有没有实现类跟Callable有关。
new Thread(new Runnable()).start()
new Thread(new FutureTask<V>).start()
new Thread(new FutureTask<V>(Callable)).start()
package com.wh.testCallable;
import com.wh.test.Test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 1:怎么才能通过new Thread().start()执行Callable
* Runnable----(实现类)--->FutureTask------>FutureTask(Callable)
*
* Callable细节
* 1:有缓存
* 2:结果可能需要等待,会阻塞
*/
public class TCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//怎么启动Callable?
//new Thread(new Runnable()).start()
//new Thread(new FutureTask<V>).start()
//new Thread(new FutureTask<V>(Callable)).start()
TestCallable testCallable = new TestCallable();
FutureTask<Integer> futureTask = new FutureTask<>(testCallable);//适配类
new Thread(futureTask).start();
new Thread(futureTask).start();//结果会被缓存,效率高
Integer result = futureTask.get();//获取Callable的返回结果 可能会产生阻塞<--等待结果返回 一般放在最后或者使用异步通讯
System.out.println(result);
}
}
class TestCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");//当有有个线程调用,会打印几个call
return 123;
}
}
call只运行一次,有缓存。
JUC常用辅助类
CountDownLatch
允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助
package com.wh.testCountDownLatch;
import java.util.concurrent.CountDownLatch;
//计数器
public class CountDownLatchDemo {
public static void main(String[] args) {
//总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for(int i=1;i<=6;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"--> go out");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
System.out.println("close door");
}
}
还没等6个人都出去,门就关上了,这样有问题。需要等6个人都出去,门才能关上。
package com.wh.testCountDownLatch;
import java.util.concurrent.CountDownLatch;
//计数器(减法)----常见面试问题
//在多线程情况下,怎么才能保证主线程在所有子线程执行完毕后,才执行完成?
//用CountDownLatch 减法计数器,初始值设置子线程个数,主线程用countdownlatch.await() 等待计数器归0,主线程继续执行。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for(int i=1;i<=6;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"--> go out");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("close door");
}
}
CycliBarrier(加法计数器)
是通过await()来计数,跟CountDownLatch不同
package com.wh.testCycliBarrier;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 集齐七龙珠召唤神龙
*/
public class CycliBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("集齐7颗龙珠召唤神龙");
});
for(int i=0;i<7;i++){
//lambda能操作到i么?
final int tem=i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集了第"+tem+"颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},i+":线程").start();
}
System.out.println("主线程结束");
}
}
Semapore(信号量)
acquire() :获得,架设如果已经满了,等待,等待被释放为止
release() :释放,会将当前的信号量释放+1,然后唤醒等待的线程
作用: 多个资源互斥的使用 ! 并发限流,控制最大的线程数
package com.wh.testSemaphore;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数 ,停车位 限流
Semaphore semaphore = new Semaphore(10);
for (int i = 0; i < 20; i++) {
new Thread(()->{
//acquire()得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//release() 释放
semaphore.release();
}
},i+"=线程").start();
}
}
}
Volatile
1:保证可见性
2:不保证原子性
3:禁止指令重排
保证可见性 和JMM挂钩
JMM:java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步约定
1:线程解锁前,必须把共享变量立刻
刷回主存
2:线程加锁前,必须读取主存中的最新值
到工作内存中
3:加锁和解锁是同一把锁
线程 工作内存
主内存
Java内存模型规定了所有的变量都存储在主内存
(Main Memory)中。每条线程还有自己的工作内存
(Working Memory,可与前面讲的处理器高速缓存类比),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝
,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量
。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成
流程
图中,store和write写反了
主内存与工作内存的8种操作(4组)
lock unlock
read load
use assign
store write
lock(锁定)
:作用于主内存的变量
,它把一个变量标识为一条线程独占的状态。
unlock(解锁)
:作用于主内存的变量
,它把一个处于锁定状态的变量释放出来,放后的变量才可以被其他线程锁定。
read(读取)
:作用于主内存的变量
,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
load(载入)
:作用于工作内存的变量
,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use(使用)
:作用于工作内存的变量
,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
assign(赋值)
:作用于工作内存的变量
,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store(存储)
:作用于工作内存的变量
,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
write(写入)
:作用于主内存的变量
,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
如果要把一个变量从主内存复制到工作内存,那就要顺序
地执行read和load操作,如果要把变量从工作内存同步回主内存,就要顺序
地执行store和write操作。注意,Java内存模型只要求上述两个操作必须按顺序执行,而没有保证是连续执行
。也就是说,read与load之间、store与write之间是可插入其他指令的,如对主内存中的变量a、b进行访问时,一种可能出现顺序是read a、read b、load b、load a。除此之外,Java内存模型还规定了在执行上述8种基本操作时必须满足如下规则:
保证可见性
package com.wh.testVolatile;
import java.util.concurrent.TimeUnit;
public class volatileDemo {
private static int num=0;
public static void main(String[] args) {
new Thread(()->{ //线程1 可能对主内存的变化可能不知道
while (num==0){
}
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println("end==>"+num);
}
}
此时虽然num被main主线程修改为1,但子线程并未接收到num变化的通知,仍然处于死循环。
解决办法: 加volatile
不保证原子性
package com.wh.testVolatile;
/**
* 原子性
*
*/
public class volatileDemo2 {
private static int num=0;
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
private static void add(){
num++; //num++不是原子性操作
}
}
加上volatile后
若不加synchronized和lock,怎么保证原子性
使用原子类
原子类为什么这么高级?
package com.wh.testVolatile;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 原子性->不保证
*/
public class volatileDemo2 {
//原子类 Integer
private volatile static AtomicInteger num = new AtomicInteger();
// private volatile static int num=0;
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) { //main gc都占一个线程
Thread.yield();
}
System.out.println(num);
}
private static void add() {
//num++; //不是一个原子操作
num.getAndIncrement(); //AtomicInteger +1方法 ,用的CAS操作
}
}
结果:
原子类的底层都直接和操作系统挂钩,在内存中修改值Unsafe是一个很特殊的存在
禁止指令重排
指令重排:你写的程序,计算机并不是按照你写的顺序去执行的
源代码–>编译器优化的重排–>指令并行也可能会重排–>内存系统也会重排–>执行
总结:
volatile可以保证可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排现象的产生。
单例模式
构造器私有
1:饿汉式
package com.wh.testDanLi;
/**
* 饿汉式
*/
public class DanLiDemo1 {
//可能会造成空间浪费
private byte[] data=new byte[1024*1024];
private byte[] data1=new byte[1024*1024];
private final static DanLiDemo1 danli = new DanLiDemo1();
private DanLiDemo1() {
}
public static DanLiDemo1 getInstance() {
return danli;
}
}
2:懒汉式(不成熟写法)
package com.wh.testDanLi;
/**
* 懒汉式
*/
public class DanLiDemo2 {
private DanLiDemo2(){
System.out.println(Thread.currentThread().getName()+"==>ok");
}
private static DanLiDemo2 danLiDemo2;
public static DanLiDemo2 getInstance(){
if(null == danLiDemo2){
danLiDemo2=new DanLiDemo2();
}
return danLiDemo2;
}
//多线程并发
public static void main(String[] args){
for (int i = 0; i < 10; i++) {
new Thread(()->{
DanLiDemo2.getInstance();
}).start();
}
}
}
结果不一定:null判断多个线程同时进入,并且new对象了。
2:懒汉式-双重检测锁 (DCL懒汉式)
package com.wh.testDanLi;
/**
* 懒汉式
*/
public class DanLiDemo2 {
private DanLiDemo2(){
System.out.println(Thread.currentThread().getName()+"==>ok");
}
private static DanLiDemo2 danLiDemo2;
public static DanLiDemo2 getInstance(){
if(null == danLiDemo2){
synchronized (DanLiDemo2.class){
if(null == danLiDemo2){
danLiDemo2=new DanLiDemo2();
}
}
}
return danLiDemo2;
}
//多线程并发
public static void main(String[] args){
for (int i = 0; i < 10; i++) {
new Thread(()->{
DanLiDemo2.getInstance();
}).start();
}
}
}
一定安全么?
package com.wh.testDanLi;
/**
* 懒汉式
*/
public class DanLiDemo2 {
private DanLiDemo2(){
System.out.println(Thread.currentThread().getName()+"==>ok");
}
private volatile static DanLiDemo2 danLiDemo2;
public static DanLiDemo2 getInstance(){
if(null == danLiDemo2){
synchronized (DanLiDemo2.class){
if(null == danLiDemo2){
danLiDemo2=new DanLiDemo2();//不是原子性操作
/**
* 1: 分配内存空间
* 2:执行构造方法,初始化对象
* 3:把这个对象指向这个空间
*
* 期望执行顺序:123
* 若为:132 当A线程执行到3;B线程也进入执行,此时一看对象已经指向一个空间,danLiDemo2!=null,则直接return
* 此时danLiDemo2还没完成构造
*
*/
}
}
}
return danLiDemo2;
}
//多线程并发
public static void main(String[] args){
for (int i = 0; i < 10; i++) {
new Thread(()->{
DanLiDemo2.getInstance();
}).start();
}
}
}
3:静态内部类
package com.wh.testDanLi;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* 静态内部类
*/
public class DanLiDemo3 {
private DanLiDemo3(){
}
public static DanLiDemo3 getInstance(){
return InnerClass.danLiDemo3;
}
public static class InnerClass{
private static final DanLiDemo3 danLiDemo3=new DanLiDemo3();
}
}
利用反射破坏前三种(用第二种示范)
package com.wh.testDanLi;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
DanLiDemo2 instance = DanLiDemo2.getInstance();
Constructor<DanLiDemo2> declaredConstructor = DanLiDemo2.class.getDeclaredConstructor(null);
//设置Accessible的访问标志位为Ture,就可以通过反射获取私有变量的值,在访问时会忽略访问修饰符的检查
declaredConstructor.setAccessible(true);
DanLiDemo2 danLiDemoe = declaredConstructor.newInstance();
System.out.println(danLiDemoe==instance);
}
}
能否解决反射的破坏?
三重检测锁
package com.wh.testDanLi;
/**
* 懒汉式
*/
public class DanLiDemo2 {
private DanLiDemo2(){
synchronized (DanLiDemo3.class){
if(null !=danLiDemo2){
throw new RuntimeException("不要试图利用反射来破化异常");
}
System.out.println(Thread.currentThread().getName()+"==>ok");
}
}
private volatile static DanLiDemo2 danLiDemo2;
public static DanLiDemo2 getInstance(){
if(null == danLiDemo2){
synchronized (DanLiDemo2.class){
if(null == danLiDemo2){
danLiDemo2=new DanLiDemo2();//不是原子性操作
/**
* 1: 分配内存空间
* 2:执行构造方法,初始化对象
* 3:把这个对象指向这个空间
*
* 期望执行顺序:123
* 若为:132 当A线程执行到3;B线程也进入执行,此时一看对象已经指向一个空间,danLiDemo2!=null,则直接return
* 此时danLiDemo2还没完成构造
*
*/
}
}
}
return danLiDemo2;
}
//多线程并发
public static void main(String[] args){
for (int i = 0; i < 10; i++) {
new Thread(()->{
DanLiDemo2.getInstance();
}).start();
}
}
}
此时懒汉式已经是三重检测锁了
若此时测试的两个对象都是通过反射来的呢?
又被破坏了单例
还能否解决上面反射的破坏?
通过标识位
package com.wh.testDanLi;
/**
* 懒汉式
*/
public class DanLiDemo2 {
//通过标志位
private static boolean wh=false;
private DanLiDemo2(){
synchronized (DanLiDemo3.class){
if(!wh){
wh=true;
}else {
throw new RuntimeException("不要试图利用反射来破化异常");
}
System.out.println(Thread.currentThread().getName()+"==>ok");
}
}
private volatile static DanLiDemo2 danLiDemo2;
public static DanLiDemo2 getInstance(){
if(null == danLiDemo2){
synchronized (DanLiDemo2.class){
if(null == danLiDemo2){
danLiDemo2=new DanLiDemo2();//不是原子性操作
/**
* 1: 分配内存空间
* 2:执行构造方法,初始化对象
* 3:把这个对象指向这个空间
*
* 期望执行顺序:123
* 若为:132 当A线程执行到3;B线程也进入执行,此时一看对象已经指向一个空间,danLiDemo2!=null,则直接return
* 此时danLiDemo2还没完成构造
*
*/
}
}
}
return danLiDemo2;
}
//多线程并发
public static void main(String[] args){
for (int i = 0; i < 10; i++) {
new Thread(()->{
DanLiDemo2.getInstance();
}).start();
}
}
}
不通过反编译的情况下,是找不到这个标识位的。标识位还可以加密,但还存在被解密的情况。
假设反编译找到了标识位:
package com.wh.testDanLi;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) throws Exception {
// DanLiDemo2 instance = DanLiDemo2.getInstance();
Field wh = DanLiDemo2.class.getDeclaredField("wh");
wh.setAccessible(true);
Constructor<DanLiDemo2> declaredConstructor = DanLiDemo2.class.getDeclaredConstructor(null);
//设置Accessible的访问标志位为Ture,就可以通过反射获取私有变量的值,在访问时会忽略访问修饰符的检查
declaredConstructor.setAccessible(true);
DanLiDemo2 danLiDemoe = declaredConstructor.newInstance();
wh.set(danLiDemoe,false);
DanLiDemo2 instance = declaredConstructor.newInstance();
System.out.println(danLiDemoe==instance);
}
}
因此:道高一尺,魔高一丈
4:枚举(最优方案)
枚举也是一个类,反射是没办法破坏枚举的
假设想通过反射破坏枚举,看看会不会出现如图的错误提示
package com.wh.testDanLi;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum DanLiDemo4 {
INSTANCE;
public DanLiDemo4 getInstance(){
return INSTANCE;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
DanLiDemo4 instance1 = DanLiDemo4.INSTANCE;
Constructor<DanLiDemo4> declaredConstructor = DanLiDemo4.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
DanLiDemo4 instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
错误提示是枚举类中没有这个空参构造,不是我们预期的那个错误提示。
但通过target可以看到是存在空参构造的(它骗了我们)
现在我们需要反编译一下这个DanLiDemo4.class文件
根据反编译,也可以看到,有空参构造(反编译也骗了我们)
那么现在用jad反编译工具
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: DanLiDemo4.java
package com.wh.testDanLi;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public final class DanLiDemo4 extends Enum
{
public static DanLiDemo4[] values()
{
return (DanLiDemo4[])$VALUES.clone();
}
public static DanLiDemo4 valueOf(String name)
{
return (DanLiDemo4)Enum.valueOf(com/wh/testDanLi/DanLiDemo4, name);
}
private DanLiDemo4(String s, int i)
{
super(s, i);
}
public DanLiDemo4 getInstance()
{
return INSTANCE;
}
public static void main(String args[])
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException
{
DanLiDemo4 instance1 = INSTANCE;
Constructor declaredConstructor = com/wh/testDanLi/DanLiDemo4.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
DanLiDemo4 instance2 = (DanLiDemo4)declaredConstructor.newInstance(new Object[0]);
System.out.println(instance1);
System.out.println(instance2);
}
public static final DanLiDemo4 INSTANCE;
private static final DanLiDemo4 $VALUES[];
static
{
INSTANCE = new DanLiDemo4("INSTANCE", 0);
$VALUES = (new DanLiDemo4[] {
INSTANCE
});
}
}
这里可以看到是有参构造,两个参数
那么现在
错误信息跟反射提示的一致
CAS(compareAndSet 比较并交换)
比较当前工作内存中值,如果这个值是期望的,那么执行操作。如果不是,就一直循环。
优点:自带原子性
缺点:
1:循环会耗时
2:一次性只能保证一个共享变量的原子性
3:ABA问题
package com.wh.testCAS;
import java.util.concurrent.atomic.AtomicInteger;
//CAS: compareAndSet 比较并交换
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2021);
// 期望,更新
//public final boolean compareAndSet(int expect, int update)
//如果我期望的值达到了,那么就更新,否则,就不更新
boolean b = atomicInteger.compareAndSet(2021, 2022);
System.out.println(b);
System.out.println(atomicInteger.get());
boolean b2 = atomicInteger.compareAndSet(2021, 2022);
System.out.println(b2);
System.out.println(atomicInteger.get());
}
}
Unsafe类
其中valueoffset就是内存地址的偏移值
AtomicInteger 的getAndIncrement()
this:当前对象
valueoffset:内存地址偏移值
var5:取出当前对象在内存地址中的值
compareAndSwapInt: 如果var1对象在内存地址的偏移的值等于var5,那么就让var5+1。
ABA问题(狸猫换太子)
1:原始值–>蛋糕
2:线程a: 如果它是蛋糕,那么他就更新这个值
现程b: 先执行了,它把这个值先换成了面包又换成了蛋糕(蛋糕->面包->蛋糕)
,然后b走了,而线程a对此不知情。
3:当线程a执行时候,此蛋糕已经不是原来的蛋糕了
类似于上图:
左: 期望A=1的话,改成2
右: 先执行了,期望A=1的话,先改成3,再改成1
package com.wh.testCAS;
import java.util.concurrent.atomic.AtomicInteger;
//ABA问题
public class CASDemo1 {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2021);
//======================捣乱的线程=====================
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2022, 2021));
System.out.println(atomicInteger.get());
//======================捣乱的线程=====================
//======================期望的线程=====================
System.out.println(atomicInteger.compareAndSet(2021, 666));
System.out.println(atomicInteger.get());
}
}
不希望这样,希望谁改动了之后,告诉我。
举例:对于平时的sql来说,用的是乐观锁:给自己上一把锁,判断锁没被动过,就去修改值。
怎么解决ABA? 原子引用
原子引用
AtomicReference
AtomicStampedReference(带版本号的原子操作)
AtomicStampedReference
带时间戳的原子引用
AtomicStampedReference 在正常业务操作,这里面比较的都是一个个对象。
错误演示(有坑)
package com.wh.testCAS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
//CAS compareAndSet 比较并交换 带时间戳的原子引用
public class CASDemo2 {
public static void main(String[] args) {
//两个参数 1:初始化的值 2:时间戳
AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(2021,1);
new Thread(()->{
//获得版本号
int stamp = reference.getStamp();
System.out.println("a1-->"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = reference.compareAndSet(2021, 2022, reference.getStamp(), reference.getStamp() + 1);
System.out.println("a2-->"+b);
System.out.println("a2-->"+reference.getStamp());
boolean b1 = reference.compareAndSet(2022, 2021, reference.getStamp(), reference.getStamp() + 1);
System.out.println("a3-->"+b1);
System.out.println("a3-->"+reference.getStamp());
},"A").start();
new Thread(()->{
int stamp = reference.getStamp();
System.out.println("b1-->"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = reference.compareAndSet(2021, 666, stamp, stamp+ 1);
System.out.println("b2-->"+b);
},"B").start();
}
}
结果:
先(延时未到之前)
后
结果并不是预期那样:预期的是a都成功,b2失败,查看原因
原因分析
而现在代码里面的是Integer,还超过了127,并且AtomicStampedReference 比较用的是==
所以每次比较都是新的Integer对象的地址值。所以结果并不是预期的那样。
正确演示
package com.wh.testCAS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
//CAS compareAndSet 比较并交换 带时间戳的原子引用
public class CASDemo2 {
public static void main(String[] args) {
//两个参数 1:初始化的值 2:时间戳
AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(60,1);
new Thread(()->{
//获得版本号
int stamp = reference.getStamp();
System.out.println("a1-->"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = reference.compareAndSet(60, 61, reference.getStamp(), reference.getStamp() + 1);
System.out.println("a2-->"+b);
System.out.println("a2-->"+reference.getStamp());
boolean b1 = reference.compareAndSet(61, 60, reference.getStamp(), reference.getStamp() + 1);
System.out.println("a3-->"+b1);
System.out.println("a3-->"+reference.getStamp());
},"A").start();
new Thread(()->{
int stamp = reference.getStamp();
System.out.println("b1-->"+stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = reference.compareAndSet(60, 88, stamp, stamp+ 1);
System.out.println("b2-->"+b);
},"B").start();
}
}
结果:
各种锁的理解
公平锁,非公平锁
公平锁:非常公平,不能够插队,必须先来后到! 非公平锁:非常不公平,可以插队(默认都是非公平锁)
例如:2个任务,A需要3秒,B要3小时,B先执行了,那么A要是不可以插队,得等3小时才能轮到它。
怎么设置公平/非公平锁
可重入锁
所有的锁都是可重入锁,也可叫递归锁。
Synchronized
package com.wh.typeLock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 可重入锁
*/
public class LockDemo1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
new Thread(()->{
phone.sendSms();
},"B").start();
}
}
class Phone{
public synchronized void sendSms(){
System.out.println(Thread.currentThread().getName()+"-->Sms");
call(); //这里也有锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"-->call");
}
}
结果
不管怎么执行,都是两个A,两个B紧挨着,也就是说sendSms()执行之后再执行call(),没事释放锁。如果释放锁的话,第一ASms出现后,第一BSms就可能再第二个Acall出现前出现。
Lock
细节问题:针对lock锁,它是需要成对的(lock,unlock) 否则就会死锁
package com.wh.typeLock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 可重入锁
*/
public class LockDemo2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(() -> {
phone.sendSms();
}, "A").start();
new Thread(() -> {
phone.sendSms();
}, "B").start();
}
}
class Phone2 {
Lock lock = new ReentrantLock();
public void sendSms() {//当前25行
lock.lock(); //细节问题 26行和34行的lock unlock是一对 26行进来,会拿到2个锁(会把call的锁也拿到)
//锁必须配对,否则就会死锁(例如:2个lock,1个unlock,就会死锁)
try {
System.out.println(Thread.currentThread().getName() + "-->Sms");
call(); //这里也有锁
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call() {//当前38行
lock.lock(); //39 和45是一对
try {
System.out.println(Thread.currentThread().getName() + "-->call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
自旋锁
不断的循环迭代,知道成功为止
package com.wh.TestSpinlock;
import java.util.concurrent.atomic.AtomicReference;
public class SpinlockDemo1 {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(thread.currentThread().getName() + "-->mylock");
//自旋锁 while 和 do while 一样
while (!atomicReference.compareAndSet(null, thread)) {
System.out.print(thread.getName()+"---自旋");
}
System.out.println();
}
public void myUnLock() {
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(thread.currentThread().getName() + "===>myUnlock");
}
}
由图可以看出,当A先获取锁后,B也也想
获取锁,但此时发现不为null,所以B自旋
,指导A休眠结束,释放锁后,B才加锁再释放锁。
读写锁(ReadWriteLock)
未加锁
package com.wh.testReadWriteLock;
import java.util.HashMap;
import java.util.Map;
/**
* ReadWriteLock 读写锁
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 5; i++) {
final int tem=i;
new Thread(()->{
myCache.put(tem+"",tem);
},String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
final int tem=i;
new Thread(()->{
myCache.get(tem+"");
},String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache{
private volatile Map<String,Object> map=new HashMap<>();
//存
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"准备写入"+key);
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入OK");
}
//取
public void get(String key){
System.out.println(Thread.currentThread().getName()+"准备读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
}
}
结果
并不是 0准备写入
之后立马就是 0写入OK
由此可知(未加锁)写的情况,多个线程同时进行。并不是我们期望的那个只有一个线程写。
加锁
ReentrantReadWriteLock
- 读-读 可以共存
- 读-写 不能共存
- 写-写 不能共存
package com.wh.testReadWriteLock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock 读写锁
* 读-读 可以共存
* 读-写 不能共存
* 写-写 不能共存
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
for (int i = 0; i <= 10; i++) {
final int tem = i;
new Thread(() -> {
myCache.put(tem + "", tem);
}, String.valueOf(i)).start();
}
for (int i = 0; i <= 10; i++) {
final int tem = i;
new Thread(() -> {
myCache.get(tem + "");
}, String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
//存
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "准备写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入OK");
}
//取
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "准备读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
}
}
class MyCacheLock {
private volatile Map<String, Object> map = new HashMap<>();
//读写锁 更加细粒度的控制
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//存 写入的时候,只希望同时只有一个线程写
public void put(String key, Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "准备写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
//取 读,所有人都可以读!
//读加锁是为了防止写(读加锁的时候,无法写),并不是防止脏读,脏读是写的时候读(读到其他未提交的数据)
public void get(String key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "准备读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
死锁
package com.wh.testSiLock;
import java.util.concurrent.TimeUnit;
public class SiLockDemo1 {
public static void main(String[] args) {
String lockA = "LockA";
String lockB = "LockB";
new Thread(new MyThread(lockA, lockB), "A").start();
new Thread(new MyThread(lockB, lockA), "B").start();
}
}
class MyThread implements Runnable {
private String LockA;
private String LockB;
public MyThread(String lockA, String lockB) {
this.LockA = lockA;
this.LockB = lockB;
}
@Override
public void run() {
synchronized (LockA) {
System.out.println(Thread.currentThread().getName() + "--lock:" + LockA + "===>get" + LockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LockB) {
System.out.println(Thread.currentThread().getName() + "--lock:" + LockB + "===>get" + LockA);
}
}
}
}
产生死锁:
解决方案
1:使用jps -l
定位进程号
2:使用jstack 进程号
找到死锁问题
面试:工作中,排查问题
1:日志
2:堆栈信息