多线程

多线程的相关概念

1进程和线程

.1进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位。可以把进程简单的理解为正在操作系统中运行的一个程序.

1.2线程

线程(thread)是进程的一个执行单元.一个线程就是进程中一个单一顺序的控制流, 进程的一个执行分支。进程是线程的容器,一个进程至少有一个线程.一个进程中也可以有多个线程.

java程序中最少有两个进程并发,一个是垃圾回收器GC,一个是Main方法执行主线程

2主线程和子线程

2.1主线程

JVM 启动时会创建一个主线程,该主线程负责执行 main 方法 . 主线程就是运行 main 方法的线程

2.2子线程

Java 中的线程不孤立的,线程之间存在一些联系. 如果在 A 线程中创建了 B 线程, 称 B 线程为 A 线程的子线程, 相应的 A 线程就是 B 线程的父线程

3串行,并行,并发

3.1串行

每次只能执行一个任务,一个任务完成后,另一个任务才能执行

3.2并行

真正的“同时”运行,在同一时刻,有多个任务同时执行。(例如,在多核处理器上,有两个线程同时执行同一段代码。)可见,单核处理器是无法实现并行的,因为单核处理器无法在同一时刻执行多个任务。

3.3并发

两个或多个任务可以在重叠的时间段内启动,运行和完成

并行(多个线程同时执行) 一定是并发,并不一定意味着并发一定要求是并行(包含关系)

在这里插入图片描述

并发:1.一个处理器。2.逻辑上的同时运行

并行:2.多个处理器。2.物理上的同时运行

多线程的优点和缺点

1.优点

1.1提高吞吐率

​ 多线程编程可以使一个进程有多个并发(concurrent,即同时进行的)的操作

1.2提高响应性

Web 服务器会采用一些专门的线程负责用户的请求处理,缩短了用户的等待时间

1.3资源利用率更高

例如从磁盘读取文件的时候,大部分的CPU时间是用于等待磁盘去读取数据,在这段时间里,CPU非常空闲,通过改变操作的顺序,就能更好的使用CPU资源

2.缺点

2.1线程安全问题

多线程共享数据时,如果没有采取正确的并发访问控制措施,就可能会产生数据一致性问题,如读取脏数据(过期的数据), 如丢失数据更新。

2.1.1线程安全问题的三方面原子性可见性和有序性
原子性

​ 原子操作时不可打断/不可分割的操作

​ 1.访问读写某个共享变量时候,从其他线程看来,要么已经结束,要么尚未开始。

​ 2.访问同一组共享变量的时候,是不能交叉的。

java实现原子性操作的方式有两种:

​ 锁:具有排它性,保证共享变量某一时刻只能被一个线程访问

​ CAS指令:直接在硬件(处理器和内存上)操作,看作时硬件锁

可见性

volatile关键字可实现可见性,不能实现原子性

​ 在多线程环境下,一个线程对某个变量进行更新后,其他的线程可能无法立刻读取到这个数据的更新结果,这就是线程安全问题的另一种(可见性)

​ 多线程程序可能会因为可见性问题导致其他线程读取到脏数据

有序性

内存操作顺序相关概念:

​ 源代码顺序, 就是源码中指定的内存访问顺序.

​ 程序顺序, 处理器上运行的目标代码所指定的内存访问顺序

​ 执行顺序,内存访问操作在处理器上的实际执行顺序

​ 感知顺序,给定处理器所感知到的该处理器及其他处理器的内存访问操作的顺序

乱序:

​ 乱序是指内存访问操作的顺序看起来发生了变化

重排序:

指令重排序:
指令重排序主要是由 JVM的JIT 编译器,处理器引起的, 指程序顺序与执行顺序不一样或源码顺序与程序顺序不一致
为何要指令重排序:为了优化程序性能
何为指令重排序
例如,对于下面两条语句:
int a = 10;
int b = 20;
在计算机执行上面两句话的时候,有可能第二条语句会先于第一条语句执行所以,千万不要随意假设指令执行的顺序
何时不可以重排序如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖

存储子系统重排序
存储子系统重排序是由高速缓存,写缓冲器引起的, 感知顺序与执行顺序 不一致
存储子系统重排序对象是内存操作的结果.
Load 操作
Store 操作
内存重排序的4种可能

volatile关键字、synchronized关键字都能够实现有序性

2.2上下文切换影响性能

如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换.

2.3可靠性

线程中止需要考虑对程序运行的影响.可能会由一个线程导致 JVM 意外终止,其他的线程也无法执行.

2.4线程活性问题

2.4.1锁死

等待线程由于唤醒其所需的条件永远无法成立,或者其他线程无法唤醒这个线程而一直处于非运行状态(线程并未终止)导致其任务 一直无法进展,那么我们就称这个线程被锁死。

2.4.2死锁

死锁是线程的一种常见活性故障。如果两个或者更多的线程因相互等待对方而被永远暂停(线程的生命周期状态为 BLOCKED 或者 WAITING), 那么我们就称这些线程产生了死锁(Deadlock)。 由于产生死锁的线程的生命周期状态永远是非运行状态,因此这些线程所要执行的任务也永远无法进展。死锁产生的一种典型情形如下图。

2.4.3线程饥饿

线程饥饿是指线程一直无法获得其所需的资源而导致其任务一直无法进展的一种活性故障。线程饥饿涉及的线程,其生命周期状态不一定就是 WATING 或者 BLOCKED 状态,其状态也可能是 RUNNING (这说明涉及的线程一直在申请其所需的资源),这时饥饿就演变成活锁。

2.4.4活锁

第一个是线程没有阻塞, 始终在运行中(所以叫活锁, 线程是活的, 运行中的. )
第二个特点: 程序却得不到进展, 因为线程始终重复同样的无效事情.

线程的创建和启动

1.创建的两种方式

1.创建子类继承Thread重写run方法

2.创建继承Thread子类的对象,掉用start方法

public class Thread1 {
   
    public static void main(String[] args) {
   
        String name = Thread.currentThread().getName();
        System.out.println(name);
        MyStart myStart = new MyStart();
        myStart.start();
    }
}
public class MyStart extends Thread{
   
    @Override
    public void run() {
   
        Thread.currentThread().setName("分支线程1");
        System.out.println(Thread.currentThread().getName());
    }
}

//输出
main
分支线程1

实现Runnable接口实现自定义线程类**(推荐**)
1. 自定义一个类,实现Runnable接口
2. 实现Runnable接口中唯一要实现的run方法,把线程的功能代码写到run方法中
3. 创建Thread类对象,把实现Runnable接口的自定义类对象,作为参数传入到Thread构造方法中

class TestRunnable implements Runnable {
   
 
	// 实现自定义线程类,实现Runnable中的run方法
	@Override
	public void run() {
   
		for (int i = 0; i < 10; i++) {
   
			System.out.println("当前线程为:" + Thread.currentThread());
		}
	}
 
}

public class Demo {
   
	public static void main(String[] args) {
   
 
		// 创建Thread类,调用Thread构造方法中,需要传入Runnable接口实现类对象的方法
		Thread t1 = new Thread(new TestRunnable());
		Thread t2 = new Thread(new TestRunnable());
 
		Thread t3 = new Thread(new Runnable() {
   
			@Override
			public void run() {
   
				for (int i = 0; i < 10; i++) {
   
					System.out.println("匿名内部类的匿名对象作为方法的参数," + Thread.currentThread());
				}
			}
		});
 
		t1.start();
		t2.start();
		t3.start();
	}
}

2.start方法

启动一个分支线程,在jvm中开辟一块新的空间。

start方法结束,新的栈就开辟出来了。

启动成功会自动调用run()方法,并且在分支栈底部压栈run()方法。

main方法在主栈的底部,run()方法也在主栈底部是平级的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LKrlT01M-1615996725326)(C:\Users\孙晨鱼\Desktop\孙晨雨4.7\新建文件夹\笔记\JAva\图片笔记\图片\start内存图.png)]

线程的生命周期

1.生命周期的五种状态

Thread.getState()调用来获取。Thread.getState() 的返回值类型Thread.State是一个枚举类型(Enum) 。Thread.State 所定义的线程状态包括以下几种。

新建(new Thread)

创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();

就绪(runnable)

线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();

**运行(running)

**线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。

死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。

自然终止:正常运行run()方法后终止

异常终止:调用**stop()**方法让一个线程终止运行

不要试图对已经死亡的线程调用start()方法,死亡就是死亡,死亡的线程不会再次被当成线程的执行体。程序只能对处于新建状态的线程调用start()方法,对处于新建状态的线程两次调用start()也是错误的,这都会引发IllegalThreadStateException异常

堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。

正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。

正在等待:调用wait()方法。(调用motify()方法回到就绪状态)

被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)

等待状态1(waiting)

线程执行了 object.wait(), thread.join()方法会把线程转换为 WAITING 等待状态

等待状态2(timed_waiting)

与 WAITING 状态类似,都是等待状态.区别在于处于该状态的线程不会无限的等待,如果线程没有在指定的时间范围内完成期望的操作,该线程自动转换为 RUNNABLE

在这里插入图片描述

线程常用的方法

Thread.currentThread()//获取当前线程
getName()//获取当前线程名字
setName()//设置当前线程名字
isAlive()//判断线程是否存活
Thread.sleep(1000)//让当前线程睡眠
yield()//放弃cpu时间片资源,进入就绪状态,准备抢夺cpu时间片子资源,java属于抢占时间片资源
setPriority()//设置优先级
interrupt()//设置中断标志 注意:调用 interrupt()方法仅仅是在当前线程打一个停止标志,并不是真正的停止线程
isInterrupted()//使用isInterrupted()识别是否中断
setDaemon(true);//设置为守护线程 main线程结束,守护线程结束
join()//调用该方法的线程在此之前执行完毕,也就是等待该方法的线程执行完毕后再往下继续执行

JVM内存模型

在这里插入图片描述

抽象模型

在这里插入图片描述

线程同步机制

线程同步机制是一套用于协调线程之间的数据访问的机制该机制可以保障线程安全

java提供的线程同步机制包括:锁,volatile关键字,final关键字,static关键字,以及相关的APi,如Object.wait()/Object.notify()等

1.锁概念

​ 线程安全问题的产生前提是多个线程并发访问共享数据.将多个线程对共享数据的并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问锁就是复用这种思路来保障线程安全的。

​ 锁(Lock)可以理解为对共享数据进行保护的一个许可证.对于同一个许可证保护的共享数据来说,任何线程想要访问这些共享数据必须先持有该许可证。一个线程只有在持有许可证的情况下才能对这些共享数据进行访问并且一个许可证一次只能被一个线程持有;许可证线程在结束对共享数据的访问后必须释放其持有的许可证.

一线程在访问共享数据前必须先获得 锁;获得锁的线程称为锁的持有线程; 一个锁一次只能被一个线程持有.锁的持有线程在获得锁之后和释放锁之前这段时间所执行的代码称为临界区(CrititalSection).

锁具有排他性(Exclusive),即一个锁次只能被一个线程持有.这种锁称为排它锁或互斥锁(Mutex).

锁可以实现对共享数据的安全访问. 保障线程的原子性,可见性与有序性.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zq5QEsuV-1615996725333)(C:\Users\孙晨鱼\Desktop\孙晨雨4.7\新建文件夹\笔记\JAva\图片笔记\图片\锁.png)]
JVM把锁分为内部锁和显示锁内柙.内郜锁通过synchronized关键字实现;显示锁通过java.concurrent.locks.Lock接口的实现类
实现的

2.锁的作用

锁可以实现对共享数据的安全访问. 保障线程的原子性,可见性与有序性.

​ 1.锁是通过互斥保障原子性. 一个锁只能被一个线程持有,这就保证临界区的代码一-次只能被一个线程执行.使得临界区代码所执行的操作自然而然的具有不可分割的特性,即具备了原子性.

​ 2.可见性的保障是通过写线程冲刷处理器的缓存和读线程刷新处理器缓存这两个动作实现的.在java平台中锁的获得隐含着刷新处理器缓存的动作,锁的释放隐含着冲刷处理器缓存的动作.

​ 3.锁能够保证有序性写线程在临界区所执行的在读线程所执行的临界区看来像是完全按照源码顺序执行的

注意:
使用锁保障线程的安全性,必须满足以下条件:
这些线程在访问共享数据时必须使用同一个锁
即使是读取共享数据的线程也需要使用同步锁

3.锁相关概念

3.1可重入性

可重入性(Reentrancy)描述这样一个问题:

​ 一个线程持有该锁的时候能再次(多次)申请该锁

void mehtodA(){
	//申请A锁
	methodB();
	//释放A锁
}
void mehtodB(){
	//申请A锁
	。。。。。。
	//释放A锁
}

如果一个线程持有一个锁的时候还能够继续成功申请该锁,称该锁是可重入的,否则就称该锁为不可重入的

3.2锁的征用和调度

Java 平台中内部锁属于非公平锁,显示Lock锁既支持公平锁又支持非公平锁

3.3锁的粒度

一个锁可以保护的共享数据的数量大小称为锁的粒度.锁保护共享数据量大称该锁的粒度粗,否则就称该锁的粒度细.锁的粒度过粗会导致线程在申请锁时会进行不必要的等待,锁的粒度过细会增加锁调度的开销.

4.内部锁synchronized

如果线程锁不同不能实现同步

如果想要线程同步必须使用同一个锁对象

Java中的每个对象都有一个与之关联的内部锁(Intrinsic lock).这种锁也称为监视器(Monitor),这种内部锁是一种排他锁, 可以保障原子性,可见性与有序性.
内部锁是通过synchronized关键字实现的synchronized关键字修饰代码块,修饰该方法.
修饰代码块的语法:

synchronized(对象锁){
   
	同步代码块,可以在同步代码块种访问共享数据
}
修饰实例方法就称为同步实例方法
修饰静态方法称称为同步静态方法

public class Test01 {
   
    public static void main(String[] args) {
   
        Test01 obj = new Test01();
        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj.mm();
            }
        }).start();

        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj.mm();
            }
        }).start();
    }
    public void mm(){
   
        synchronized (this) {
   
            for (int i = 0; i < 100; i++) {
   
                System.out.println(Thread.currentThread().getName() + " ----> " + i);
            }
        }
    }
}
//先获得到cpu执行器的执行完,另一个才执行
1)假设Thread-0线程获得CPU执行权,调用obj对象的mm()方法执行方法体,先获得this对象obj的锁执行for循环
    
2)假设Thread-0在执行for循环期间,thread-1线程得了CPU执行权调用obj对象的mm()方法执行方法体,先获this对象obj的锁见在Thread-0线程持有this对象obj的锁,synchronized内部锁是排他锁,只能被一个线程持Thread-1进入等待区等待this对象obj的锁
    
3)当Thread-0线程重新获得CPU执行权,把for循环执行完即执行完同步代码块Thread-0线程会释放this对象obj的锁
    
4)等待区的Thread-1线程获得this对象obj的锁

public class Test01 {
   
    public static void main(String[] args) {
   
        Test01 obj1 = new Test01();
        Test01 obj2 = new Test01();
        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj1.mm();
            }
        }).start();

        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj2.mm();
            }
        }).start();
    }
    public void mm(){
   
        synchronized (this) {
   
            for (int i = 0; i < 100; i++) {
   
                System.out.println(Thread.currentThread().getName() + " ----> " + i);
            }
        }
    }
}
//交替执行的代码,因为this指向的锁不是同一个
public class Test01 {
   
    public static void main(String[] args) {
   
        Test01 obj1 = new Test01();
        Test01 obj2 = new Test01();
        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj1.mm();
            }
        }).start();

        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj2.mm();
            }
        }).start();
        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                sm();
            }
        }).start();
    }
    public static final Object obj = new Object();
    public void mm(){
   
        synchronized (obj) {
   
            for (int i = 0; i < 100; i++) {
   
                System.out.println(Thread.currentThread().getName() + " ----> " + i);
            }
        }
    }
    public static void sm(){
   
        synchronized (obj) {
   
            for (int i = 0; i < 100; i++) {
   
                System.out.println(Thread.currentThread().getName() + " ----> " + i);
            }
        }
    }
}
//使用的锁对象都是OBJ常量,OBJ值固定,获得的锁一样。只有当一个执行完,下一个获得cpu资源才能执行

使用synchronized修饰同步实例方法,默认锁对象是this

public class Test02 {
   
    public static void main(String[] args) {
   
        Test02 obj1 = new Test02();
        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj1.mm();
            }
        }).start();

        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj1.mm();
            }
        }).start();
    }
    public void mm(){
   
        synchronized (this) {
   
            for (int i = 0; i < 100; i++) {
   
                System.out.println(Thread.currentThread().getName() + " ----> " + i);
            }
        }
    }
    public synchronized void sm(){
   
            for (int i = 0; i < 100; i++) {
   
                System.out.println(Thread.currentThread().getName() + " ----> " + i);
            }
    }
}
//使用的锁对象都是obj1是同一个对象

使用synchronized修饰同步静态方法,默认锁对象是当前类的运行时类对象,有人称之为类锁

public class Test02 {
   
    public static void main(String[] args) {
   
        Test02 obj1 = new Test02();
        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj1.mm(); //使用的锁对象是Test02.class
            }
        }).start();

        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj1.sm();  //使用的锁对象是Test02.class
            }
        }).start();
    }
    public void mm(){
   
        //使用的锁对象是Test02.class
        synchronized (Test02.class) {
   
            for (int i = 0; i < 100; i++) {
   
                System.out.println(Thread.currentThread().getName() + " ----> " + i);
            }
        }
    }
    //同步静态方法,默认使用的锁对象是本类Test02.class
    public synchronized static void sm(){
   
            for (int i = 0; i < 100; i++) {
   
                System.out.println(Thread.currentThread().getName() + " ----> " + i);
            }
    }
}

5.同步代码块和同步方法应该如何选择?

如果采用同步方法,那么整个方法体将会被同步,假设进入方法需要准备三秒,才会执行,那么两个对象调用就需要准备6秒,如果使用同步代码块,准备时间只需要三秒。

比较两个程序:

public class Test03 {
   
    public static void main(String[] args) {
   
        Test03 obj1 = new Test03();
        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj1.mm(); 
            }
        }).start();

        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj1.mm();  
            }
        }).start();
    }

    public void mm(){
   
        try {
   
            System.out.println("begin");
            Thread.sleep(3000);
            synchronized (this) {
   
                for (int i = 0; i < 100; i++) {
   
                    System.out.println(Thread.currentThread().getName() + " ----> " + i);
                }
            }
            System.out.println("end");
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    }
}
public class Test03 {
   
    public static void main(String[] args) {
   
        Test03 obj1 = new Test03();
        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj1.sm();
            }
        }).start();

        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                obj1.sm();
            }
        }).start();
    }

   
    public synchronized static void sm(){
   
        try {
   
            System.out.println("begin");
            Thread.sleep(3000);
            synchronized (Test03.class) {
   
                for (int i = 0; i < 100; i++) {
   
                    System.out.println(Thread.currentThread().getName() + " ----> " + i);
                }
            }
            System.out.println("end");
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    }
}

6.脏读

6.1什么是脏赌

出现读取属性值出现了一些意外, 读取的是中间值,而不是修改之后 的值

6.2脏读原因

对共享数据的修改与对共享数据的读取不同步

6.3解决办法

不仅对修改数据的代码块进行同步,还要对读取数据的代码

7.线程执行异常会自动释放锁对象

public class Test04 {
   
    public static void main(String[] args) {
   
        Test04 test04 = new Test04();
        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                test04.sm();
            }
        }).start();
        new Thread(new Runnable(){
   
            @Override
            public void run() {
   
                test04.mm();
            }
        }).start();
    }
    private synchronized  void sm() {
   
        for (int i = 0 ; i < 100 ; i++){
   
            if (i == 50){
   
                Integer.parseInt("abc");
            }
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
    private synchronized  void mm() {
   
        for (int i = 0 ; i < 100 ; i++){
   
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }

}
//结果 。。。。代表省略
Thread-0--->0
Thread-0--->1
Thread-0--->2
。。。。。。。。。。
Thread-0--->48
Thread-0--->49
Thread-1--->0
Thread-1--->1
。。。。。。。。。
Thread-1--->96
Thread-1--->97
Thread-1--->98
Thread-1--->99
Exception in thread "Thread-0" java.lang.NumberFormatException: For input string: "abc"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Integer.parseInt(Integer.java:615)
	at sys.Test04.sm(Test04.java:26)
	at sys.Test04.access$000(Test04.java:7)
	at sys.Test04$1.run(Test04.java:13)
	at java.lang.Thread.run(Thread.java:748)

8.死锁

public class Test05 {
   
    public static void main(String[] args) {
   
        subThread thread1 = new subThread();
        thread1.setName("a");
        subThread thread2 = new subThread();
        thread2.setName("b");
        thread1.start();
        thread2.start();
    }
    static class subThread extends Thread{
   
        private static final Object lock1 = new Object();
        private static final Object lock2 = new Object();

        @Override
        public void run() {
   
            if ("a".equals(Thread.currentThread().getName())){
   
                synchronized (lock1){
   
                    System.out.println("a获得lock1,还需要获得lock2");
                    synchronized (lock2){
   
                        System.out.println("a获得lock1和lock2");
                    }
                }
            }
            if ("b".equals(Thread.currentThread().getName())){
   
                synchronized (lock2){
   
                    System.out.println("b获得lock2,还需要获得lock1");
                    synchronized (lock1){
   
                        System.out.println("b获得lock1和lock2");
                    }
                }
            }
        }
    }
}

//输出结果     程序无法结束。

a获得lock1,还需要获得lock2
b获得lock2,还需要获得lock1

//也有可能程序执行结束 a线程获得了lock1也获得了lock2

9.轻量级同步锁:volatile关键字

9.1volatile关键字的作用

volatile的作用可以强制线程从公共内存中读取变量的值,而不是从工作内存中读取

9.2volatile与synchronized 比较

​ 1.volatile 关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好; volatile 只能修饰变量,而synchronized可以修饰方法,代码块.随着 JDK新版本的发布,synchronized的执行效率也有较大的提升在开发中使用sychronized的比率还是很大的.

2.多线程访问volatile变量不会发生阻塞,而synchronized可能会阻塞

  1. volatile 能保证数据的可见性,但是不能保证原子性;

    synchronized可以保证原子性,也可以保证可见性

  2. 关键字volatile 解决的是变量在多个线程之间的可见性;

    synchronized关键字解决多个线程之间访问公共资源的同步性

9.3.常用原子类进行自增操作

​ 我们知道i++操作不是原子操作,除了使用Synchronized进行同步外,也可以使用AtomicInteger/AtomicLong原子类进行实现

10.CAS

CAS(Compare And Swap)是由硬件实现的
CAS可以将read- modify - write这类的操作转换为原子操作.
i++自增操作包括三个子操作:
从主内存读取i变量值,对i的值加1,再把加1之后的值保存到主内存

CAS原理:在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)上一样就更新.
在这里插入图片描述

10.1CAS的ABA问题

CAS实现原子操作背后有-个假设:共享变量的当前值与当前线程提供的期望值相同,就认为这个变量没有被其他线程修改过.实际上这种假设不一定总是成立.如有共享变量count=0
A线程对count;值修改为10
B线程对count;值修改为20
C线程对count;值修改为0

​ 当前线程看到count变量的值现在是0,现在是否认为count变量的值没有被其他线程更新呢?这种结果是否能够接受??

这就是CAS中的ABA问题,即共享变量经历了A->B->A 的更新.
是否能够接收ABA问题跟实现的算法有关.

​ 如果想要规避ABA问题,可以为共享变量引|入一个修订号(时间戳),每次修改共享变量时,相应的修订号就会增加1. ABA变量更不新过程变量: [A,0] ->[B,1]->[A,2],每次对共享变量的修改都会导致修订号的增加,通过修订号依然可以准确判断变量是否被其他线程修改过. AtomicStampedReference类就是基于这种思想产生的.

11.原子变量类

​ 原子变量类基于CAS实现的,当对共享变量进行read-modify-write更新操作时,通过原子变量类可以保障操作的原子性与可见性.对变量的read-modify-write更新操作是指当前操作不是一一个简单的赋值,而是变量的新值依赖变量的旧值,如自增操作i++.由于voltile只能保证可见性,无法保障原子性,原子变量类内部就是借助-个Volatile变量,并且保障了该变量的read-notify-write操作的原子性,有时把原子变量类看作增强的volatile变量.原子变量类有12个,如:

在这里插入图片描述

11.1AtomicLong

模拟计数请求

package Thread.atomics;

/**
 * 肥蛋
 * 2020/12/13
 */

import java.util.concurrent.atomic.AtomicLong;

/**
 * 单例模式模拟计数器*/
public class register {
   
    //定义私有单例变量
    private static final register REGISTER = new register();
    //构造方法私有化
    public register() {
   
    }
    //提供一个返回本类单例变量的方法
    public static register getRegister(){
   
        return REGISTER;
    }

    //使用原子类变量保存请求总数,成功数,失败数
    private final AtomicLong requestCount = new AtomicLong(0);
    private final AtomicLong requestSuccessCount = new AtomicLong(0);
    private final AtomicLong requestFailCount = new AtomicLong(0);

    //有新的请求
    public void setNewRequest(){
   
        requestCount.incrementAndGet();
    }
    //处理成功的请求
    public void setRequestSuccessCount(){
   
        requestSuccessCount.incrementAndGet();
    }
    //处理失败的请求
    public void setRequestFailCount(){
   
        requestFailCount.incrementAndGet();
    }

    //查看总记录,成功,失败
    public long getRequestCount(){
   
        return requestCount.get();
    }
    public long getRequestSuccessCount (){
   
        return requestSuccessCount.get();
    }
    public long getRequestFailCount(){
   
        return requestFailCount.get();
    }
}


package Thread.atomics;

import java.util.Random;

/**
 * 肥蛋
 * 2020/12/13
 */
public class test {
   
    public static void main(String[] args) {
   
        //通过线程模拟请求,在实际应用中可以在ServletFilter中调用Indicator计数器的相关方法
        for (int i = 0 ; i < 1000; i ++){
   
            new Thread(new Runnable(){
   
                @Override
                public void run() {
   
                    register.getRegister().setNewRequest();
                    int num = new Random().nextInt(10000);
                    if (num % 2 == 0){
   
                        register.getRegister().setRequestFailCount();
                    }else {
   
                        register.getRegister().setRequestSuccessCount();
                    }
                }
            }).start();
        }
        System.out.println(register.getRegister().getRequestCount());
        System.out.println(register.getRegister().getRequestFailCount());
        System.out.println(register.getRegister().getRequestSuccessCount()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值