1,锁方法
public synchronized void method()
{
// todo
}
2,锁代码快
public void method()
{
synchronized(this) {
// todo
}
}
3,锁一个明确对象
public void method() {
synchronized(Object) { // todo }
}
4,锁静态方法
public synchronized static void method() {
// todo
}
静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。
二,synchronized和lock
1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题
3,简单生产者消费者
synchronized版本
public class Product {
public static void main(String[] args) {
Data data = https://www.it610.com/article/new Data();
new Thread(() -> {for (int i = 0;
i < 10;
i++) data.increment();
},"thread1").start();
new Thread(() -> {for (int i = 0;
i < 10;
i++) data.decrease();
},"thread2").start();
}
}class Data{
public static int num = 0;
public synchronized void increment(){
while (num != 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName() + "->" + num);
this.notify();
}public synchronized void decrease(){
while (num == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + "->" + num);
this.notify();
}}
JUC版本
通过lock与Condition实现
public class Product {
public static void main(String[] args) {
Data data = https://www.it610.com/article/new Data();
new Thread(() -> {for (int i = 0;
i < 10;
i++) data.increment();
},"thread1").start();
new Thread(() -> {for (int i = 0;
i < 10;
i++) data.decrease();
},"thread2").start();
}
}class Data{
public static int num = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment(){
lock.lock();
try {
while(num != 0){
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "->" + num);
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}publicvoid decrease(){
lock.lock();
try {
while (num == 0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName() + "->" + num);
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}}
锁的几种类型
1,一个类两个方法同时被synchronized修饰,锁对象 结果:买票 付款
public class LockDemo {public static void main(String[] args) {
Sale sale = new Sale();
new Thread(() -> {sale.sale();
}).start();
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}new Thread(() -> {sale.pay();
}).start();
}}class Sale{public synchronized void sale(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("买票");
}
public synchronized void pay(){
System.out.println("付款");
}
}
2,同一个类不同方法,一个被synchronized修饰,一个普通方法 是两个不同的锁,不同方法不必等被锁方法释放锁 结果 付款 买票
public class LockDemo {public static void main(String[] args) {
Sale sale = new Sale();
new Thread(() -> {sale.sale();
}).start();
new Thread(() -> {sale.pay();
}).start();
}}class Sale{public synchronized void sale(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("买票");
}
public void pay(){
System.out.println("付款");
}
}
3,一个类创建两个对象,调用类中两个被synchronized修饰的静态方法,锁的是这个类对象即class对象 结果 买票 付款
public class LockDemo {public static void main(String[] args) {
Sale sale = new Sale();
Sale sale1 = new Sale();
new Thread(() -> {sale.sale();
}).start();
new Thread(() -> {sale1.pay();
}).start();
}}class Sale{public static synchronized void sale(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("买票");
}
public static synchronized void pay(){
System.out.println("付款");
}
}
4,一个类两个方法,调用类中一个静态锁方法,一个普通锁方法,结果 付款 买票
public class LockDemo {public static void main(String[] args) {
Sale sale = new Sale();
Sale sale1 = new Sale();
new Thread(() -> {sale.sale();
}).start();
new Thread(() -> {sale1.pay();
}).start();
}}class Sale{public static synchronized void sale(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("买票");
}
publicsynchronized void pay(){
System.out.println("付款");
}
}
集合类不安全
一,List不安全 读写出现线程修改异常
public class ListUnSafe {
public static void main(String[] args) {
List list = new ArrayList();
for(int i = 1;
i <= 10;
i++) {
new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}}
Exception in thread "9" java.util.ConcurrentModificationException
解决方法:
1,使用Vector 原理,add方法使用synchronized锁 缺点,速度慢
public class ListUnSafe {
public static void main(String[] args) {
List list = new Vector();
for(int i = 1;
i <= 10;
i++) {
new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}}
2,使用Collections.synchronized构造线程安全list
public class ListUnSafe {
public static void main(String[] args) {
List list = Collections.synchronizedList(new ArrayList());
for(int i = 1;
i <= 10;
i++) {
new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}}
3,使用CopyOnWriteArrayList 原理 add方法使用lock锁
public class ListUnSafe {
public static void main(String[] args) {
List list = new CopyOnWriteArrayList();
for(int i = 1;
i <= 10;
i++) {
new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}}
二,Set不安全
public class ListUnSafe {
public static void main(String[] args) {
Set set = new HashSet();
for(int i = 1;
i <= 20;
i++) {
new Thread(() -> {set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}}
出现:Exception in thread "12" java.util.ConcurrentModificationException
解决方法:
1,使用Collections变成线程安全
public class ListUnSafe {
public static void main(String[] args) {
Set set = Collections.synchronizedSet(new HashSet());
for(int i = 1;
i <= 20;
i++) {
new Thread(() -> {set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
2,使用CopyOnWriteSet
public class ListUnSafe {
public static void main(String[] args) {
Set set = new CopyOnWriteArraySet<>();
for(int i = 1;
i <= 20;
i++) {
new Thread(() -> {set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
三,Map不安全
public class ListUnSafe {
public static void main(String[] args) {
Map map = new HashMap();
for(int i = 1;
i <= 20;
i++) {
new Thread(() -> {map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
出现:Exception in thread "12" java.util.ConcurrentModificationException
解决方法:
1,使用ConcurrentHashMap
public class ListUnSafe {
public static void main(String[] args) {
Map map = new ConcurrentHashMap();
for(int i = 1;
i <= 20;
i++) {
new Thread(() -> {map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
2,ConcurrentHashMap详解
ConcurrentHashMap结构图
文章图片
CurrentHashMap比HashMap在Entry前面加了Segment段,CocurrentHashMap通过分段锁的机制,实现了多线程写入时的线程安全,也提高了多线程情况下的访问效率。
对于JDK1.7版本的实现,ConcurrentHashMap 为了提高本身的并发能力,在内部采用了一个叫做 Segment 的结构,一个 Segment 其实就是一个类 Hash Table 的结构,Segment 内部维护了一个链表数组,我们用下面这一幅图来看下 ConcurrentHashMap 的内部结构,从下面的结构我们可以了解到,ConcurrentHashMap 定位一个元素的过程需要进行两次Hash操作,第一次 Hash 定位到 Segment,第二次 Hash 定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是 Hash 的过程要比普通的 HashMap 要长,但是带来的好处是写操作的时候可以只对元素所在的 Segment 进行操作即可,不会影响到其他的 Segment,这样,在最理想的情况下,ConcurrentHashMap 可以最高同时支持 Segment 数量大小的写操作(刚好这些写操作都非常平均地分布在所有的 Segment上),所以,通过这一种结构,ConcurrentHashMap 的并发能力可以大大的提高。
Callable使用:
public class MyCollable{
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);
new Thread(futureTask).start();
Object o = futureTask.get();
System.out.println(o);
}
}class MyThread implements Callable{@Override
public String call() throws Exception {return "hello collable";
}
}
CountDownLatch使用:减法计数器
当有任务必须要完成时使用,等待任务全部完成才继续执行
public class CountDownLatchDemo {
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() + "运行结束");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("所有线程运行结束");
}}
原理:
countDownLatch.countDown(); 数量减一
countDownLatch.await();等待计数器归零
CyclicBarrier使用:加法计数器 等待指定数量线程执行后执行
public class CyclicBarrierDemo {
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 e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore使用:信号量机制 限流使用
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(4);
for (int i = 0;
i < 6;
i++) {
new Thread(() -> {
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 {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
阻塞队列:ArrayBlockingQueue四组API
1 add()element() remove()
2 offer() poll()
3 put() take()
4 offer(带时间参数) poll(带时间参数)
API | 解释 |
add() | 添加元素到队列,当添加元素数量超过容量时会抛出异常 |
offer() | 添加元素到队列,当添加元素数量超过容量时会返回false,添加成功返回true,不抛出异常。并且可以进行超时添加,设置时长参数,当超过时间还无法成功添加元素时会退出,与put()方法不同。 |
poll() | 获取队列元素,当获取队列元素的数量超过容量时会返回null,并且可以进行超时获取,设置时长参数,当超过时间还无法成功获取元素时会退出,与take()方法不同。 |
remove() | 移除队列首个元素 |
element() | 获取队列首个元素 |
peek() | 获取队首元素 |
put() | 添加元素到队列,当添加元素数量超过容量时,会阻塞队列。一直到插入成功 |
take() | 获得队列元素,当队列为空时,会阻塞队列直到娶到元素 |
没有容量,存入一个元素后必须等到取出后才能继续存入元素
线程池:三大方法 七大参数 四种拒绝策略
线程池的作用:
1,减少资源的使用
2,提高响应的速度
3,方便管理线程
三大方法
1,Exacutors.newSingleThreadExecutor() :创建单个线程,任务被同一个线程完成
public class ExecutorsDemo {
public static void main(String[] args) {ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0;
i < 10;
i++) {
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行任务");
});
}
}
}pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
pool-1-thread-1执行任务
2,Executors.newFixedThreadPool(5):创建固定大小线程池,即同时有5个并发
public class ExecutorsDemo {
public static void main(String[] args) {ExecutorService service = Executors.newFixedThreadPool(5);
try {
for (int i = 0;
i < 10;
i++) {
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行任务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
service.shutdown();
}
}
}pool-1-thread-2执行任务
pool-1-thread-2执行任务
pool-1-thread-2执行任务
pool-1-thread-2执行任务
pool-1-thread-2执行任务
pool-1-thread-2执行任务
pool-1-thread-1执行任务
pool-1-thread-5执行任务
pool-1-thread-4执行任务
pool-1-thread-3执行任务
3,Executors.newCachedThreadPool():创建可伸缩大小的线程池
public class ExecutorsDemo {
public static void main(String[] args) {ExecutorService service = Executors.newCachedThreadPool();
try {
for (int i = 0;
i < 10;
i++) {
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行任务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
service.shutdown();
}
}
}
pool-1-thread-1执行任务
pool-1-thread-3执行任务
pool-1-thread-4执行任务
pool-1-thread-4执行任务
pool-1-thread-1执行任务
pool-1-thread-2执行任务
pool-1-thread-5执行任务
pool-1-thread-6执行任务
pool-1-thread-7执行任务
pool-1-thread-3执行任务
七大参数
三大方法原理都是调用ThreadPoolExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
*if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
*pool
* @param keepAliveTime when the number of threads is greater than
*the core, this is the maximum time that excess idle threads
*will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
*executed.This queue will hold only the {@code Runnable}
*tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
*creates a new thread
* @param handler the handler to use when execution is blocked
*because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:
*{@code corePoolSize < 0}
*{@code keepAliveTime < 0}
*{@code maximumPoolSize <= 0}
*{@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
*or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue 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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize:核心线程大小
maximumPoolSize:最大线程大小
keepAliveTime:存活时间
unit:时间单元
workQueue:阻塞队列
threadFactory:线程工厂用于创建线程
handler:拒绝策略
文章图片
自定义线程池:创建线程池建议使用ThreadPoolExecutor
public class ExecutorsDemo {
public static void main(String[] args) {ExecutorService service = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 0;
i < 10;
i++) {
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + "执行任务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
service.shutdown();
}
}
}
四大拒绝策略
1,默认AbortPolicy:超出则不处理并且抛出异常
2,CallerRunPolicy:从哪来的哪个线程执行
3,DiscardPolicy:队列满了,不处理但不会抛出异常
4,DiscardOldPolicy:尝试和第一个线程进程,不会抛出异常
最大线程数如何创建
1,cpu密集型 :电脑为几核就定义为几,保证最高效率 ,方法:Runtime.getRuntime().availableProcessors()获得当前电脑cpu核数
2,IO密集型:尽量创建大于程序中十分消耗IO的线程数,防止阻塞
四大函数式接口
lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口,简化编程模型
1,function:一个输入参数一个输出参数
文章图片
public class Demo {
public static void main(String[] args) {
//Function function = new Function() {
//@Override
//public String apply(String s) {
//return s;
//}
//};
//lambda表达式方式 输出输入的值
Function function = (str) -> {return str;
};
System.out.println(function.apply("sss"));
}
}
2,Predicate:断定型接口,有一个输入,返回值为boolean类型
文章图片
public class Demo {
public static void main(String[] args) {
//Predicate predicate = new Predicate() {
//@Override
//public boolean test(String o) {
//return o.equals("aaa");
//}
//};
Predicate predicate = (str) -> {return str.equals("aaa");
};
System.out.println(predicate.test("aaa"));
}
}
3,Consumer消费型接口:只有输入没有返回值
文章图片
public class Demo {
public static void main(String[] args) {
//Consumer consumer = new Consumer() {
//@Override
//public void accept(String s) {
//System.out.println(s);
//}
//};
Consumer consumer = (str) -> {
System.out.println(str);
};
consumer.accept("aaa");
}
}
4,Suppliy供给型接口:只有返回值没有输入
文章图片
public class Demo {
public static void main(String[] args) {
//Supplier supplier = new Supplier() {
//@Override
//public String get() {
//return "aaa";
//}
//};
Supplier supplier = () -> {return "aaa";
};
System.out.println(supplier.get());
}
}
Stream流式计算
存储 + 计算
计算交给stream
public class Demo {
public static void main(String[] args) {User user1 = new User(1, "a", 21);
User user2 = new User(2, "b", 22);
User user3 = new User(3, "c", 25);
User user4 = new User(4, "d", 28);
User user5 = new User(5, "e", 16);
List list = Arrays.asList(user1,user2,user3,user4,user5);
list.stream()
.filter((u) -> {return u.getYear() > 20;
})
.filter((u) -> {return u.getId() % 2 == 0;
})
.forEach(System.out::println);
}
}
输出:
User{id=2, name='b', year=22}
User{id=4, name='d', year=28}
ForkJoin:jdk1.7以后出来,并行执行任务提高效率,工作窃取当自己线程执行完毕而相邻线程还未执行完毕时候会帮助执行。大数据量下使用
异步回调:web页面与后端异步交互一般采用ajax,java内部也有异步回调类
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "111";
});
System.out.println("执行了异步操作");
future.thenAccept((result) -> {
System.out.println(result);
});
System.out.println("异步操作执行完毕");
future.exceptionally((e) -> {e.printStackTrace();
return null;
});
Thread.sleep(200);
//new Thread(() -> {
//System.out.println("被监控的任务");
//future.complete("任务完成");
//}).start();
//System.out.println(future.get());
}
}
JMM:java内存模型,不存在的东西,一个模型!约定
JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化
文章图片
在Java中,不同线程拥有各自的私有工作内存,当线程需要读取或修改某个变量时,不能直接去操作主内存中的变量,而是需要将这个变量读取到线程的工作内存的变量副本中,当该线程修改其变量副本的值后,其它线程并不能立刻读取到新值,需要将修改后的值刷新到主内存中,其它线程才能从主内存读取到修改后的值。
文章图片
JMM的八种交互操作(每个操作都为原子操作)
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
对八种操作的规则
1,不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
2,不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
3,不允许一个线程将没有assign的数据从工作内存同步回主内存
4,一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
5,一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
6,如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
7,如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
8,对一个变量进行unlock操作之前,必须把此变量同步回主内存
Volatile是java虚拟机提供的轻量级的同步机制
volatile在java语言中是一个关键字,用于修饰变量。被volatile修饰的变量后,表示这个变量在不同线程中是共享,编译器与运行时都会注意到这个变量是共享的,因此不会对该变量进行重排序。
volatile关键字保证可见性、有序性。但不保证原子性。
1,保证可见性
2 ,不可保证原子性
3,禁止指令重排
关于JMM的一些同步的约定
1,线程解锁前,必须把共享变量立刻读到主存
2,线程加锁前,必须把共享变量读到线程工作内存中
3,线程加锁解锁是同一把锁
1,保证可见性
public class JMMDemo {
//不适用volatile程序就会一直循环,volatile保证了变量可见
private staticvolatileint num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (num == 0) {}
}).start();
Thread.sleep(3);
num = 1;
System.out.println(num);
}
}
2 ,不可保证原子性
使用volatile不能保证操作原子性
public class VODemo2 {
private volatile static int num = 0;
public static void main(String[] args) {
for (int i = 0;
i < 20;
i++) {
new Thread(() -> {
for (int j = 0;
j < 1000;
j++) {
add();
}
}).start();
}while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(num);
}public static void add() {
num++;
}
}
问题:如何在不使用synchronized和lock的情况下保证原子性
解决:使用原子类解决原子性问题
文章图片
public class VODemo2 {
private volatile static AtomicInteger num = new AtomicInteger();
public static void main(String[] args) {
for (int i = 0;
i < 20;
i++) {
new Thread(() -> {
for (int j = 0;
j < 1000;
j++) {
add();
}
}).start();
}while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(num);
}public static void add() {
num.getAndIncrement();
}
}
问题:原子类的底层原理
单例模式
1,饿汉式单例:上来就实例化单例类,缺点是会浪费不必要的资源,当类中有资源类时在没使用到时就会直接加载。
//饿汉式单例
public class HungrySingle {private byte[] temp = new byte[1024];
private byte[] temp2 = new byte[1024];
private byte[] temp3 = new byte[1024];
private byte[] temp4 = new byte[1024];
private HungrySingle(){}private final static HungrySingle single = new HungrySingle();
public static HungrySingle getSingle() {
return single;
}}
2,DCL懒汉式单例模式
public class LazySingle {
private LazySingle(){}
private LazySingle lazySingle;
//双重锁定的懒汉式单例 简称DCL单例
public LazySingle getLazySingle(){
if (lazySingle == null) {
synchronized (LazySingle.class){
if (lazySingle == null) {
new LazySingle();
}
}
}
return lazySingle;
}
}
问题:DCL单例存在问题,即new LazySingle()不是一个原子性操作,可能会由于指令重排造成错误
解决:加上volatile
public class LazySingle {
private LazySingle(){}
private volatile static LazySingle lazySingle;
//双重锁定的懒汉式单例 简称DCL单例
public LazySingle getLazySingle(){
if (lazySingle == null) {
synchronized (LazySingle.class){
if (lazySingle == null) {
new LazySingle();
}
}
}
return lazySingle;
}
}
静态内部类实现
//静态内部类实现单例模式
public class InnerClassSingle {private InnerClassSingle(){}public InnerClassSingle getSingle(){
return Inner.single;
}public static class Inner{
private static final InnerClassSingle single = new InnerClassSingle();
}
}
使用这些单例并不安全因为使用反射能够破坏
//静态内部类实现单例模式
public class InnerClassSingle {private InnerClassSingle(){}public InnerClassSingle getSingle(){
return Inner.single;
}public static class Inner{
private static final InnerClassSingle single = new InnerClassSingle();
}public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
InnerClassSingle single = new InnerClassSingle();
//通过反射获得无参构造方法
Constructor constructor = InnerClassSingle.class.getDeclaredConstructor(null);
//破坏私有无参构造器
constructor.setAccessible(true);
InnerClassSingle single2 = constructor.newInstance();
System.out.println(single);
System.out.println(single2);
}}结果:创建的两个对象不是同一个,单例失效
innnerClass.InnerClassSingle@1b6d3586
innnerClass.InnerClassSingle@4554617c
使用枚举:枚举本身就是一个class
public enumEnumSingle {
INSTNCE;
public EnumSingle getInstnce(){
return INSTNCE;
}}
当我们使用反射去破坏枚举的单例时会抛出异常:显示没有无参构造方法
public enumEnumSingle {
INSTNCE;
public EnumSingle getInstnce(){
return INSTNCE;
}public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle single = EnumSingle.INSTNCE;
Constructor constructor = EnumSingle.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
EnumSingle single1 = constructor.newInstance();
System.out.println(single);
System.out.println(single1);
}
}
Exception in thread "main" java.lang.NoSuchMethodException: EnumSingle.EnumSingle.()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at EnumSingle.EnumSingle.main(EnumSingle.java:17)
追究枚举源码本身其存在一个的构造方法,当我们修改反射获得构造方法中的参数后,再使用反射去破坏单例时,会显示无法使用反射去破环单例的错误
public enumEnumSingle {
INSTNCE;
public EnumSingle getInstnce(){
return INSTNCE;
}public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle single = EnumSingle.INSTNCE;
Constructor constructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumSingle single1 = constructor.newInstance();
System.out.println(single);
System.out.println(single1);
}
}Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at EnumSingle.EnumSingle.main(EnumSingle.java:19)
深入理解CAS
什么是CAS
CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。
缺点:
1,使用自旋锁,循环耗时
2,一次性只能保证一个共享变量的原子性
3,ABA问题
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//当期望值和当前值一样时交换
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger);
}
}
原子类原理:内部有Unsafe类,Unsafe类时java用于与内存进行交互的类
文章图片
CAS:ABA问题
考虑如下操作:
并发1:获取出数据的初始值是A,后续计划实施CAS乐观锁,期望数据仍是A的时候,修改才能成功
并发2:将数据修改成B
并发3:将数据修改回A
并发1:CAS乐观锁,检测发现初始值还是A,进行数据修改
上述并发环境下,并发1在修改数据时,虽然还是A,但已经不是初始条件的A了,中间发生了A变B,B又变A的变化,此A已经非彼A,数据却成功修改,可能导致错误,这就是CAS引发的所谓的ABA问题。
ABA问题的优化
ABA问题导致的原因,是CAS过程中只简单进行了“值”的校验,再有些情况下,“值”相同不会引入错误的业务逻辑(例如库存),有些情况下,“值”虽然相同,却已经不是原来的数据了。
优化方向:CAS不能只比对“值”,还必须确保的是原来的数据,才能修改成功。
常见实践:“版本号”的比对,一个数据一个版本,版本变化,即使值相同,也不应该修改成功。
例如:使用带有版本号的原子类,能够避免ABA问题,对应的思想:乐观锁
public class CASDemo {
public static void main(String[] args) {//使用带版本号的Integer
//注意:如果约定的类型是包装类,注意对象的引用类型
AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(1,1);
new Thread(() -> {
System.out.println("A版本号是:" + atomicStampedReference.getStamp());
//try {
//TimeUnit.SECONDS.sleep(2);
//} catch (InterruptedException e) {
//e.printStackTrace();
//}atomicStampedReference.compareAndSet(1,2,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("B的版本号是:" + atomicStampedReference.getStamp());
},"a").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}atomicStampedReference.compareAndSet(2,3,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("C的版本号是:" + atomicStampedReference.getStamp());
},"b").start();
}
}
各种锁的理解
文章图片
1,公平锁,非公平锁
文章图片
2,乐观锁,悲观锁
文章图片
- 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
- 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
文章图片
4,可重入锁和非可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
5,独享锁和共享锁
独享锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和JUC中Lock的实现类就是互斥锁。
共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。
具体参考博客:https://blog.csdn.net/weixin_44777669/article/details/109427045
查询死锁方法:在Teminal下
1,
文章图片
2,
文章图片
【java学习|JUC并发编程汇总彻底搞懂JUC】
推荐阅读
- Quartz学习笔记(一) 初遇篇
- HTTP Client 学习笔记 (一) 初遇篇
- LeetCode|【刷题1】LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置 java题解
- 牛逼!IDEA 护眼方案来了。。
- 感悟|Java后端学习体系(韩顺平)
- 笔记|初识Java Bean
- java|一个抽奖的例子演示线程的同步,暂停和恢复
- c++|Python 什么时候会被取代()
- 编程语言|Canvas渲染会取代DOM吗()