进程:一个活动的程序叫做进程
线程:CPU操作程序的最小执行单元(一个进程至少包括一个线程)
Java默认开启两个线程:main线程和GC线程
对于Java而言,开启线程有三种方式,Thread、Runable、Callable。Callable和前两者相比可以有返回值,在企业开发中更加常用。
Java真的可以直接开启线程吗?答案是:不可以
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 添加至进程组
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
// 启动native本地方法开启线程,本质是调用c++与CPU交互,Java不能直接与硬件交互
private native void start0();
并发:单核CPU,模拟出多条线程,CPU快速轮流执行
并行:多核CPU,开启多条线程,CPU同时执行
public static void main(String[] args) {
// 获取CPU核数(CPU密集型、IO密集型)
System.out.println(Runtime.getRuntime().availableProcessors());
}
线程状态
wait/sleep区别
自不同的类
new Object.wait() new Thread.sleep()
关于锁的释放
wait()会释放锁;sleep()方法,是死死地抱着锁睡觉的,不会释放锁
使用范围不同
wait()必须在同步代码块中执行,sleep()方法可以在任何地方执行
是否需要捕获异常
sleep()方法需要捕获异常、wait()方法不需要捕获异常
public class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"C").start();
}
}
class Ticket{
private int totalNum = 30;
private int saledNum = 0;
synchronized void sale(){
saledNum ++;
if(totalNum - saledNum >= 0){
System.out.println("线程" + Thread.currentThread().getName() + "卖了" + saledNum + "张票,剩余" + (totalNum - saledNum) +"张票");
}
}
}
Lock接口的所有实现类
// 可重入锁(普通锁)
ReentrantLock
// 读锁
ReentrantReadWriteLock.ReadLock
// 写锁
ReentrantReadWriteLock.WriteLock
Lock锁的用法
// 以可重入锁为例
Lock lock = new ReentrantLock();
// 加锁
lock.lock();
try{
// 业务代码
}catch(){
}finally{
// 解锁
lock.unlock();
}
ReentrantLock构造方法
// 无参构造方法(默认非公平锁)
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock锁分类
非公平锁
不遵循先来后到的原则,我可以插队(ReentrantLock锁默认为非公平锁)
公平锁
遵循先来后到的原则
ReentrantLock案例
@SuppressWarnings("all")
public class ReentrantLockDemo {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"C").start();
}
}
class Ticket2{
private int totalNum = 30;
private int saledNum = 0;
Lock lock = new ReentrantLock();
void sale(){
lock.lock();
try {
saledNum ++;
if(totalNum - saledNum >= 0){
System.out.println("线程" + Thread.currentThread().getName() + "卖了" + saledNum + "张票,剩余" + (totalNum - saledNum) +"张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
线程通信问题:
生产者消费者问题。生产线程操作完毕后通知消费线程去消费,不满足生产条件时 生产线程处于等待状态。消费线程消费完成后,通知生产线程。
如果是多个线程之间通信,不进行合适的判断,则会出现虚假唤醒的情况
详细虚假换新案例见 com/geek/demo03/ProductAndConsumer.java
虚假唤醒:
进程可以被唤醒,但是如果唤醒后不能被通知或者中断,这种唤醒被称作虚假唤醒。如果条件不满足则进程应该继续等待,这个等待的逻辑应该处于循环中,就会避免虚假唤醒的状况发生。
while(num != 0){
// 进入等待状态
this.wait();
}
class Data2{
private int num = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 生产操作
void increment() throws InterruptedException {
// Condition对象操作进程的等待和唤醒
lock.lock();
try {
while(num != 0){
// 进入等待状态
condition.await();
}
num ++;
System.out.println(Thread.currentThread().getName() + "-->" + num);
// 通知其他线程,我生产完毕
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 消费操作
void decrement() throws InterruptedException {
lock.lock();
try {
while(num == 0){
// 进入等待状态
condition.await();
}
num --;
System.out.println(Thread.currentThread().getName() + "-->" + num);
// 通知其他线程,我消费完毕
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
@SuppressWarnings("all")
class Data3{
private int num = 1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
void printA(){
lock.lock();
try {
while (num != 1){
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "--->" + "AAA");
num = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void printB(){
lock.lock();
try {
while (num != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "--->" + "BBB");
num = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void printC(){
lock.lock();
try {
while (num != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "--->" + "CCC");
num = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
什么是锁?锁的对象是什么?
锁的对象有两种,一种是对象(调用普通方法),一种是Class(调用static方法)
在并发操作的情况下,写入ArrayList会出现ConcurrentModificationException 并发修改异常,因此ArrayList是线程不安全的。
public static void main(String[] args) {
// ConcurrentModificationException,会出现并发修改异常
List list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list.toString());
}).start();
}
}
解决方案:
1.使用Vector类来代替ArrayList
List<String> list = new Vector<>();
Vector
出现在JDK1.1中,而ArrayList
出现在JDK1.2中,可见并不推荐开发者使用Vector
类来代替ArrayList
。Vector
本质是使用synchronized
关键字修饰代码块,效率比较低。
// Vector的add()方法
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
2.是ArrayList集合变成线程安全的
Collections.synchronizedList(new ArrayList<>());
3.使用JUC
并发集合CopyOnWriteArrayList
实现
CopyOnWriteArrayList
底层原理为读写分离思想。使用transient volatile关键字修饰Array,使用ReentrantLock对对象加锁,在写入前先复制一份,将元素插入到复制的list中,然后再将插入后的list写入到原来的list中。
private transient volatile Object[] array;
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
//Set set = new HashSet<>();
//Set set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
}).start();
}
}
Map<String, String> map = new ConcurrentHashMap<>();
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask<>(new MyThread());
new Thread(futureTask).start();
Integer result = (Integer) futureTask.get();
System.out.println(result);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("调用call()方法...");
return 1;
}
}
//CountDownLatch减法计数器
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " go out");
//执行减 1 操作
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待计数器归零,然后执行下面的代码。否则程序将阻塞在这里。
countDownLatch.await();
System.out.println("close door");
}
//加法计数器
//集齐七颗龙珠,满足条件后召唤神龙
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("召唤神龙成功..."));
for (int i = 0; i < 7; i++) {
final int temp = i;
new Thread(()->{
System.out.println("集齐了" + temp + "颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
//信号量
//需求:模拟抢车位游戏,6辆汽车抢占3个车位
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
final int temp = i;
new Thread(()->{
try {
//得到资源
semaphore.acquire();
System.out.println("汽车" + temp + "抢到了车位...");
TimeUnit.SECONDS.sleep(2);
System.out.println("汽车" + temp + "离开了车位...");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放资源
semaphore.release();
}
},"汽车" + i).start();
}
}
原理:
semaphore.acquire()
得到了资源,那些没有抢占到资源的线程进入等到状态
semaphore.release()
释放资源,会将当前的信号量释放,然后唤醒等待的线程
应用:
@SuppressWarnings("all")
public class ReadWriteLockDemo {
public static void main(String[] args) throws InterruptedException {
MyMap myMap = new MyMap();
for (int i = 1; i <= 6; i++) {
final int temp = i;
new Thread(()->{
myMap.write(temp, UUID.randomUUID().toString().substring(0,5));
},"线程" + i).start();
}
for (int i = 1; i <= 6; i++) {
final int temp = i;
new Thread(()->{
myMap.read(temp);
},"线程" + i).start();
}
}
}
class MyMap{
volatile Map<Integer,String> map = new HashMap<>();
public Map<Integer, String> getMap() {
return map;
}
void read(Integer key){
System.out.println(Thread.currentThread().getName() + "读取的key为:" + key);
String result = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取key的结果为:" + result);
}
void write(Integer key,String value){
System.out.println(Thread.currentThread().getName() + "执行写操作,key为:" + key + ",value为:" + value);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "执行写操作完毕");
}
}
在没有加读写锁的情况下会出现 写操作会出现插队的情况
见程序输出结果
线程1读取的key为:1
线程2读取的key为:2
线程5读取的key为:5
线程5读取key的结果为:null
线程1读取key的结果为:null
线程2读取key的结果为:null
线程3读取的key为:3
线程6读取的key为:6
线程6读取key的结果为:null
线程4读取的key为:4
线程4读取key的结果为:null
线程3读取key的结果为:null
线程3执行写操作,key为:3,value为:ddbfe
线程6执行写操作,key为:6,value为:65a8c
线程6执行写操作完毕
线程2执行写操作,key为:2,value为:1fb69
线程2执行写操作完毕
线程1执行写操作,key为:1,value为:19e63
线程1执行写操作完毕
线程5执行写操作,key为:5,value为:e11d5
线程5执行写操作完毕
线程3执行写操作完毕
线程4执行写操作,key为:4,value为:18faa
线程4执行写操作完毕
从上面的输出结果可以观察到 线程3执行写操作,key为:3,value为:ddbfe,线程3执行了部分写操作后被线程6插进来,执行了线程6的写操作,出现了线程之间插队的情况。
@SuppressWarnings("all")
public class ReadWriteLockDemo2 {
public static void main(String[] args) throws InterruptedException {
//创建减法计数器对象
CountDownLatch latch = new CountDownLatch(6);
MyMap1 myMap = new MyMap1();
for (int i = 1; i <= 6; i++) {
final int temp = i;
new Thread(()->{
myMap.write(temp, UUID.randomUUID().toString().substring(0,5));
//减法计数器减 1
latch.countDown();
},"线程" + i).start();
}
// 此行代码以后的程序进入阻塞状态,直到减法计数器减到 0 为止,然后开始执行下面的代码
latch.await();
for (int i = 1; i <= 6; i++) {
final int temp = i;
new Thread(()->{
myMap.read(temp);
},"线程" + (i + 6)).start();
}
}
}
class MyMap1{
volatile Map<Integer,String> map = new HashMap<>();
//创建读写锁对象
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public Map<Integer, String> getMap() {
return map;
}
void read(Integer key){
//给读锁加锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取的key为:" + key);
String result = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取key的结果为:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
//给读锁解锁
readWriteLock.readLock().unlock();
}
}
void write(Integer key,String value){
//给写锁加锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "执行写操作,key为:" + key + ",value为:" + value);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "执行写操作完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
//给写锁解锁
readWriteLock.writeLock().unlock();
}
}
}
程序输出结果
线程5执行写操作,key为:5,value为:09193
线程5执行写操作完毕
线程1执行写操作,key为:1,value为:53164
线程1执行写操作完毕
线程2执行写操作,key为:2,value为:941be
线程2执行写操作完毕
线程6执行写操作,key为:6,value为:c1e11
线程6执行写操作完毕
线程4执行写操作,key为:4,value为:ed9f3
线程4执行写操作完毕
线程3执行写操作,key为:3,value为:94442
线程3执行写操作完毕
线程8读取的key为:2
线程8读取key的结果为:941be
线程10读取的key为:4
线程7读取的key为:1
线程11读取的key为:5
线程12读取的key为:6
线程12读取key的结果为:c1e11
线程9读取的key为:3
线程9读取key的结果为:94442
线程11读取key的结果为:09193
线程7读取key的结果为:53164
线程10读取key的结果为:ed9f3
从以上输出结构可以看出,写线程(线程1-6)在执行写操作时由于加了写锁,没有出现插队的情况。读线程(线程7-12)加了读锁,虽然出现了插队的情况,但是并不影响实际需求(允许这样的情况发生)
读类型线程与写类型线程共存问题:
锁类型:
方式 | 抛出异常 | 有返回值(不抛异常) | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(,) |
移除 | remove() | poll() | take() | poll(,) |
检测队列元素 | element() | peek() | 无 | - |
SynchronousQueue同步队列
特点:put一个元素后必须take该元素后才能 put下一个元素
public static void main(String[] args) {
SynchronousQueue<String> queue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "put " + "1");
queue.put("1");
System.out.println(Thread.currentThread().getName() + "put " + "2");
queue.put("2");
System.out.println(Thread.currentThread().getName() + "put " + "3");
queue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "take " + queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "take " + queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "take " + queue.take());
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
由于线程的创建和回收这两个过程是非常消耗性能的,利用池化思想,事先创建好一些线程放入线程池中,使用完之后线程池再回收线程,达到减小资源开销的目的。
public static void main(String[] args) {
//创建只有单条线程的线程池
//ExecutorService threadPool = Executors.newSingleThreadExecutor();
//创建固定大小的线程池
//ExecutorService threadPool = Executors.newFixedThreadPool(5);
//创建可伸缩大小的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
} finally {
//关闭线程池
threadPool.shutdownNow();
}
}
public ThreadPoolExecutor(int corePoolSize, //核心线程数量
int maximumPoolSize, //最大线程数量
long keepAliveTime, //超过该时间没人调用就释放资源
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂,这个参数一般不动
RejectedExecutionHandler handler //拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
try {
for (int i = 1; i <= 9; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
} finally {
//关闭线程池
threadPool.shutdownNow();
}
}
/**
* 四种拒绝策略:
* AbortPolicy:队列满了则抛出异常
* CallerRunsPolicy:队列满了谁叫你调的我则 谁去执行(交个main线程去执行)
* DiscardPolicy:队列满了,则直接抛弃,不会抛出异常
* DiscardOldestPolicy:队列满了,则会判断最早执行的线程有没有空闲,如有空闲则交个它执行
*/
概念:JMM全称为java 内存模型,是一个不存在的约定。java内存模型分为:线程工作内存和主内存
约定:
概念:volatile是java虚拟机提供的轻量级的同步机制(synchronized为重量级同步机制,volatile没有synchronized强大)
特点:
如果线程B修改了主存中的共享变量,而线程A对主存共享变量没有可见性,没有及时同步主存中共享变量,就会导致数据不一致问题,如下图所示:
/**
* @author :
* @date : 2020/6/14
* @description : Volatile关键字的使用(保证共享变量的内存可见性)
*
* 反例
* private static int num = 0;
* 由于num没有用volatile关键字修饰,在主存与线程的工作内存 不可见
* 主线程将num重新赋值为2,但是由于线程1内存不可见的问题,不能同步到num值的变化,因此程序进入死循环状态\
*
* 正例
* private static volatile int num = 0;
* 解决思路:让线程1知道主存中的共享变量发生了变化
* 解决方案:使用volatile关键字修饰 共享变量num
*/
@SuppressWarnings("all")
public class VolatileDemo {
private static volatile int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (num == 0){
}
},"线程1").start();
TimeUnit.SECONDS.sleep(3);
num = 2;
System.out.println("num值为:" + num);
}
}
即一个线程在执行过程中(8个步骤)是不能被打扰,不能被分割的,要么全部成功,要么全部失败。
/**
* @author :
* @date : 2020/6/14
* @description :
* 线程原子性:线程在执行过程中不能被打断,要么全部成功,要么全部失败
* volatile关键字不保证线程的原子性操作
*/
public class VolatileACIDDemo {
private static volatile int num = 0;
public static void main(String[] args) {
//开启20条线程
for (int i = 0; i < 20; i++) {
//每条线程循环调用1000次 add()方法,预期结果 num = 20000
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
//java程序中最少有2条线程在执行(main线程和GC垃圾回收线程)
//如果活跃的线程数大于2,说明除了main线程和GC线程外还有线程在工作,则进行礼让,即不执行while循环以下的程序
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println("num值为 " + num);
}
private static void add() {
num ++;
}
}
如何实现不使用lock锁和synchronized关键字来保证线程的原子操作?
使用juc包中的原子类就可以实现。这些原子类底层是直接与操作系统交互的,直接在内存中修改值,方法都为原生的native方法。
/**
* @author : 赵静超
* @date : 2020/6/14
* @description :
* 线程原子性:线程在执行过程中不能被打断,要么全部成功,要么全部失败
* volatile关键字不保证线程的原子性操作
*
* 解决方案:
* 要求不使用Lock锁和Synchronized关键字来实现线程的原子操作
* 使用juc包中的原子类可以解决该问题
*/
public class VolatileACIDDemo {
//private static volatile int num = 0;
private static AtomicInteger num = new AtomicInteger(1);
public static void main(String[] args) {
//开启20条线程
for (int i = 0; i < 20; i++) {
//每条线程循环调用1000次 add()方法,预期结果 num = 20000
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
//java程序中最少有2条线程在执行(main线程和GC垃圾回收线程)
//如果活跃的线程数大于2,说明除了main线程和GC线程外还有线程在工作,则进行礼让,即不执行while循环以下的程序
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println("num值为 " + num);
}
private static void add() {
//num ++;
num.getAndIncrement();
}
}
**概念:**计算机并不是按照你写的程序的顺序去执行的
**指令重排过程:**源代码—>编译器优化的重排—>指令并行会重排—>内存系统也会重排—>程序执行
**指令重排约定:**编译器在进行指令重排的时候会考虑数据之间的依赖问题
举例:
int x = 1; //1
int y = 2; //2
x = x + 5; //3
y = x * x; //4
//我们所期望的程序执行顺序是1 2 3 4顺序执行
//但是计算机的执行过程可能是2 1 3 4,也有可能是1 3 2 4
//但是绝对不可能出现3 1 4 2的这种情况,因为处理器首先会考虑数据之间的依赖问题,首先先声明变量x,才会进行
// x = x + 5操作
变量a b x y初始值为0
正常的执行顺序及结果
线程A | 线程B |
---|---|
x = a | y = b |
b = 1 | a = 1 |
正常的执行结果为:x = 0 :y = 0;
但是由于指令重排机制,会出现异常的执行顺序
线程A | 线程B |
---|---|
b = 1 | a = 1 |
x = a | y = b |
异常的执行结果为 x = 1; y = 1
**总结:**volatile关键字可以保证内存可见性,但是不保证线程的原子性操作(可以使用juc的原子类来保证线程的原子性操作),由于volatile的内存屏障机制,可以避免指令重排现象。
自旋锁:
**CAS过程:**比较当前工作内存中的值和主存中的值是否相等,如果相等是期望的值,则执行操作。
AtomicInteger atomicInteger = new AtomicInteger(2020);
//int expect, int update
//expect为期望值,update为想要更新的值,如果期望值与初始值相等,则将想要更新的值赋值给初始值。
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
缺点:
ABA问题
线程1和线程2操作共享变量A,线程1期望A是1然后希望将其赋值为2。此刻线程2先与线程1拿到共享变量A的值,将其赋值为3,之后由将其赋值为1,此刻共享变量的值为1。但是此刻共享变量的值已经不再是初始值的那个1了。此时线程1 不知道线程2已经改变了共享变量的值。这就是ABA问题。
线程2可以对操作共享变量的值,但是这个操作得让线程1知道。(联想MySQL表中的version字段,每次操作该记录,version值就加1)
/**
* @author : Jeffersonnn
* @date : 2020/6/14 17:22
* @description :
* CAS(compare and swap)即比较并交换
*/
@SuppressWarnings("all")
public class CASDemo {
private static AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<>(1,1);
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicReference.getStamp();
System.out.println("a ---> " + stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicReference.compareAndSet(1, 2, atomicReference.getStamp(), atomicReference.getStamp() + 1);
System.out.println("a1 ---> " + atomicReference.getStamp());
atomicReference.compareAndSet(2, 1, atomicReference.getStamp(), atomicReference.getStamp() + 1);
System.out.println("a2 ---> " + atomicReference.getStamp());
},"a").start();
new Thread(()->{
int stamp = atomicReference.getStamp();
System.out.println("b ---> " + stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicReference.compareAndSet(1, 2, stamp, stamp + 1);
System.out.println("b1 ---> " + atomicReference.getStamp());
},"b").start();
}
}
公平锁:对资源的使用很公平,不能插队,保证先来后到的有序性。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁:对资源的使用不公平,可以插队(Lock锁和Synchronized关键字默认都是非公平锁实现的)
public ReentrantLock() {
sync = new NonfairSync();
}
**概念:**拿到了外面的锁后,就可以拿到里面的锁了(里面的锁是自动获得的)
/**
* @author : Jeffersonnn
* @date : 2020/6/14 19:24
* @description :
* 自定义实现自旋锁
*/
public class SpinLockDemo {
//传入一个Thread,初始值为null
AtomicReference<Thread> atomicReference = new AtomicReference<Thread>();
//自旋锁加锁
public void myLock(){
System.out.println(Thread.currentThread().getName() + " lock");
//获取当前的线程
Thread thread = Thread.currentThread();
while ( ! atomicReference.compareAndSet(null,thread)){
}
}
//自旋锁解锁
public void myUnLock(){
System.out.println(Thread.currentThread().getName() + " unLock");
//获取当前的线程
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
}
}