1.传统Synchronized锁
本质是队列。
并发编程要将资源类进行解耦
/**
* 真正的并发编程要降低耦合,将线程类视为一个单独的资源类正常编写属性和方法
* 不附加其余操作
*/
public class SellTicketMethod02 {
public static void main(String[] args) {
//并发:多个线程操作一个资源类
Ticket ticket = new Ticket();
new Thread(()->{
for(int i=1;i<110;++i){
ticket.sell();
}
},"A").start();
new Thread(()->{
for(int i=1;i<110;++i){
ticket.sell();
}
},"B").start();
new Thread(()->{
for(int i=1;i<110;++i){
ticket.sell();
}
},"C").start();
}
}
//资源类
class Ticket{
public static int tickets = 100;//剩余票数,用静态来表示共享资源
private boolean loop=true;
public synchronized void sell() {
if(tickets>0)
System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余票数:" + (--tickets));
}
}
2.JUC中的Lock锁
Lock是一个接口有三个实现类
ReentrantLock:可重入锁(常用)
ReentrantReadWriteLock.ReadLock:读锁
ReentrantReadWriteLock.WriteLock:写锁
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
lock()方法为加锁,unlock()方法为解锁
2.1 使用Lock锁
/使用lock
1)new ReentrantLock();创建一个锁
2)lock.lock();加锁
3)finally-> lock.unlock();解锁
/**
* 真正的并发编程要降低耦合,将线程类视为一个单独的资源类正常编写属性和方法
* 不附加其余操作
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Lock进行同步
*/
public class SellTicketMethod02 {
public static void main(String[] args) {
//并发:多个线程操作一个资源类
Ticket ticket = new Ticket();
new Thread(()->{for(int i=1;i<110;++i) ticket.sell();},"A").start();
new Thread(()->{for(int i=1;i<110;++i) ticket.sell();},"B").start();
new Thread(()->{for(int i=1;i<110;++i) ticket.sell();},"C").start();
}
}
//资源类
//使用lock
//1.new ReentrantLock();
//2.lock.lock();//加锁
//3. finally-> lock.unlock();//解锁
class Ticket{
private int tickets = 100;//剩余票数
//使用juc进行同步
//默认无参则为非公平锁,传入true则为公平锁
//公平锁:先来后到
//非公平锁:可以插队
Lock lock=new ReentrantLock();
public synchronized void sell() {
lock.lock();//加锁
try {
//业务代码
if(tickets>0){
System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余票数:" + (--tickets));
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//解锁
}
}
}
2.2 Synchronized和Lock区别
1)Synchronized内置的java关键字,Lock是一个java类
2)Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
3)Synchronized会自动释放锁,Lock需要手动释放,如果不释放锁就会死锁
4)Synchronized 线程1获得锁后阻塞,线程2会一直等待,而Lock锁不一定会等待下去(lock.tryLock())
5)Synchronized 是可重入锁,不可以中断的,非公平锁;Lock,可重入锁,可以判断锁,可以自己设置为公平锁
5)Synchronized 适合锁少量的代码同步问题;Lock,较为灵活,适合锁大量的同步代码
2.3 锁是什么,如何判断锁的是谁(八锁问题)
问题1. 标准情况下,两个线程先打印发短信还是打电话?答案:1/发短信 2/打电话
问题2. senSms()延迟4秒,两个线程先打印发短信还是打电话?答案:1/发短信 2/打电话
**原因:**问题1和问题2是因为锁的存在,synchronized的锁的对象是方法的调用者,由于两个线程都是用的同一个对象因此是同一个锁,谁先拿到先执行
public class Lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{phone.senSms();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone.call();},"B").start();
}
}
class Phone{
public synchronized void senSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendSms");
}
public synchronized void call(){
System.out.println("call");
}
}
问题3添加一个普通方法,senSms()延迟4秒,两个线程先打印发短信还是hello?答案: 1/hello 2/发短信
问题4 senSms()延迟4秒,实例化两个对象,两个线程分别调用两个对象的发短信和打电话方法,请问是先打印发短信还是打电话?答案:1/打电话 2/发短信
原因问题3因为普通方法没有锁即不受锁的影响,问题四,因为有两个对象,不是同一个锁
//八锁(关于锁的八个问题)
//3.添加一个普通方法,senSms()延迟4秒,两个线程先打印发短信还是hello?答案 1/hello 2/发短信
//4.senSms()延迟4秒,实例化两个对象,两个线程分别调用两个对象的发短信和打电话方法,请问是先打印发短信还是打电话?1/打电话 2/发短信
//问题一和问题二是因为锁的存在,synchronized的锁的对象是方法的调用者,由于两个线程都是用的同一个对象因此是同一个锁,谁先拿到先执行
public class Lock8_2 {
public static void main(String[] args) throws InterruptedException {
//两个对象
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
try {
phone1.senSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone2.call();},"B").start();
}
}
class Phone2{
public synchronized void senSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendSms");
}
public synchronized void call(){
System.out.println("call");
}
//没有锁,因此不受锁的影响
public void hello(){
System.out.println("hello");
}
}
问题5.发短信和打电话放均改为静态方法,senSms()延迟4秒,一个对象,两个线程分别调用发短信还是打电话,请问是先打印发短信还是打电话?答案 1/发短信 2/打电话
问题6.发短信和打电话放均改为静态方法,senSms()延迟4秒,两个对象,两个线程分别调用,请问是先打印发短信还是打电话?答案 1/发短信 2/打电话
**原因:**因为静态方法的锁加在Class上,无论几个对象都仅有一个Class,因此只有一个锁,先到先得
/八锁(关于锁的八个问题)
//5.发短信和打电话放均改为静态方法,senSms()延迟4秒,一个对象,两个线程分别调用发短信还是打电话,请问是先打印发短信还是打电话?答案 1/发短信 2/打电话
//6.发短信和打电话放均改为静态方法,senSms()延迟4秒,两个对象,两个线程分别调用,请问是先打印发短信还是打电话?答案 1/发短信 2/打电话
public class Lock8_3 {
public static void main(String[] args) throws InterruptedException {
//两个对象
//不论几个对象仅有一个Class对象
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
try {
phone1.senSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone2.call();},"B").start();
}
}
//静态方法的锁是锁在Class上
class Phone3{
public static synchronized void senSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendSms");
}
public static synchronized void call(){
System.out.println("call");
}
//没有锁,因此不受锁的影响
public void hello(){
System.out.println("hello");
}
}
问题7.发短信为静态方法,打电话为普通同步方法,senSms()延迟4秒,一个对象,两个线程分别调用发短信还是打电话,请问是先打印发短信还是打电话?答案 1/打电话 2/发短信
问题8.发短信和打电话放均改为静态方法,senSms()延迟4秒,两个对象,两个线程分别调用,请问是先打印发短信还是打电话?答案 1/打电话 2/发短信
**原因:**因为静态同步方法和普通同步方法是不同的锁
//7.发短信为静态方法,打电话为普通同步方法,senSms()延迟4秒,一个对象,两个线程分别调用发短信还是打电话,请问是先打印发短信还是打电话?答案 1/打电话 2/发短信
//因为是不同的锁
//8.发短信和打电话放均改为静态方法,senSms()延迟4秒,两个对象,两个线程分别调用,请问是先打印发短信还是打电话?答案 1/打电话 2/发短信
public class Lock8_4 {
public static void main(String[] args) throws InterruptedException {
//两个对象
//不论几个对象仅有一个Class对象
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
try {
phone1.senSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{phone2.call();},"B").start();
}
}
//静态方法的锁是锁在Class上
class Phone4{
public static synchronized void senSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("sendSms");
}
public synchronized void call(){
System.out.println("call");
}
//没有锁,因此不受锁的影响
public void hello(){
System.out.println("hello");
}
}
**总结:**普通同步方法锁的是对象,静态同步方法锁的是Class
2.4 线程通信,生产者和消费者问题
2.4.1传统Synchronized的生产者和消费者问题
下面代码如果有多个消费者和生产者则会出问题,问题出在仅使用if判断进行线程等待出现了虚假等待问题,wait()要在循环当中
/**
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* A和B线程操作同一个变量 num=0;
*/
public class A {
public static void main(String[] args) {
//创建资源对象
Data data = new Data();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.increament();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.decreament();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
}
}
//等待,业务,通知
//资源类
class Data{
private int num=0;
//+1
public synchronized void increament() throws InterruptedException {
if(num!=0){
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知其他线程我+1结束了
this.notifyAll();
}
//+1
public synchronized void decreament() throws InterruptedException {
if(num==0){
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知其他线程-1结束了
this.notifyAll();
}
}
解决多生产者和多消费问题
package com.lbc.juc;
/**
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* A和B线程操作同一个变量 num=0;
*/
public class A {
public static void main(String[] args) {
//创建资源对象
Data data = new Data();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.increament();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.decreament();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
}
}
//等待,业务,通知
//资源类
class Data{
private int num=0;
//+1
public synchronized void increament() throws InterruptedException {
while (num!=0){
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知其他线程我+1结束了
this.notifyAll();
}
//+1
public synchronized void decreament() throws InterruptedException {
while (num==0){
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知其他线程-1结束了
this.notifyAll();
}
}
2.4.2JUC的生产者和消费者问题
如果Lock替换了synchronized方法和语句的使用,则将Condition替换对象监视方法的使用。
class BoundedBuffer<E> {
Final Lock lock = new ReentrantLock();
最终条件 notFull = lock.newCondition();
最终条件 notEmpty = lock.newCondition();
最终对象[]项目=新对象[100];
int putptr、takeptr、计数;
公共无效put(E x)抛出InterruptedException {
锁.lock();尝试 {
while (count == items.length)
notFull.await();
项目[putptr] = x;
if (++putptr == items.length) putptr = 0;
++计数;
notEmpty.signal();
} 最后 { lock.unlock(); }
}
public E take() 抛出 InterruptedException {
锁.lock();尝试 {
而(计数==0)
notEmpty.await();
E x = (E) 项目[takeptr];
if (++takeptr == items.length) takeptr = 0;
- 数数;
notFull.signal();
返回x;
} 最后 { lock.unlock(); }
}
}
生产者消费者代码实现
/**
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* A和B线程操作同一个变量 num=0;
*/
public class B {
public static void main(String[] args) {
//创建资源对象
Data2 data = new Data2();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.increament();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.decreament();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.decreament();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.increament();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"D").start();
}
}
//等待,业务,通知
//资源类
class Data2{
private int num=0;
//使用Lock实现
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
//+1
public void increament() throws InterruptedException {
lock.lock();
try {
while (num!=0){
//等待
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
condition.signalAll();
//通知其他线程我+1结束了
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//+1
public void decreament() throws InterruptedException {
lock.lock();
try {
while (num==0){
//等待
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
condition.signalAll();
//通知其他线程我+1结束了
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
2.5 Condition可以精准的通知和唤醒线程
实现线程ABC按顺序访问
public class C {
public static void main(String[] args) {
//创建资源对象
Data3 data = new Data3();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.printA();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.printB();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
new Thread(()->{
for(int i=0;i<10;++i){
try {
data.printC();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();
}
}
//等待,业务,通知
//资源类
class Data3{
private int num=0;
//使用Lock实现
Lock lock=new ReentrantLock();
Condition condition1=lock.newCondition();
Condition condition2=lock.newCondition();
Condition condition3=lock.newCondition();
//+1
public void printA() throws InterruptedException {
lock.lock();
try {
while (num!=0){
//等待
condition1.await();
}
num=1;
System.out.println(Thread.currentThread().getName()+"==>"+num);
condition2.signal();
//通知其他线程
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB() throws InterruptedException {
lock.lock();
try {
while (num!=1){
//等待
condition2.await();
}
num=2;
System.out.println(Thread.currentThread().getName()+"==>"+num);
condition3.signal();
//通知其他线程
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}public void printC() throws InterruptedException {
lock.lock();
try {
while (num!=2){
//等待
condition3.await();
}
num=0;
System.out.println(Thread.currentThread().getName()+"==>"+num);
condition1.signal();
//通知其他线程
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
3.集合不安全
3.1并发下ArrayList不安全
public class ListTest {
public static void main(String[] args) {
List<String> 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();
}
}
}
解决集合不安全的方案
1.List<String> list = new Vector<>();
2.List<String> list = Collections.synchronizedList(new ArrayList<>());
3.List<String> list = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList比Vector好在哪里
Vector使用了Synchronized效率比较低
public class ListTest {
public static void main(String[] args) {
//ArrayList 不安全
//解决方案:
//1.List<String> list = new Vector<>();
//2.List<String> list = Collections.synchronizedList(new ArrayList<>());
//3.List<String> list = new CopyOnWriteArrayList<>();
//CopyOnWrite 写入时复制, COW 计算机程序设计领域的一种优化策略
//多个线程调用的适合,list 读取的时候是固定的,写入的时候可能会发生覆盖
//因此为避免写入时发生覆盖,在写入的时候复制,写完之后再插入即可
//CopyOnWriteArrayList比Vector好在哪里
//1.Vector使用了Synchronized效率比较低
List<String> 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();
}
}
}
3.2并发下HashSet不安全
解决方案
1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
2.Set<String> set = new CopyOnWriteArraySet<>();
public class SetTest {
public static void main(String[] args) {
//和ArrayList一样会不安全
//解决方法和ArrayList类似
//1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
//2.Set<String> set = new CopyOnWriteArraySet<>();
Set<String> set = new CopyOnWriteArraySet<>();
// HashSet<String> set = new HashSet<>();
for(int i=1;i<=10;i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
HashMap的底层
实际就是一个仅保存key值的HashMap
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();//一个不变的值
3.3并发下HashMap不安全
public class MapTest {
public static void main(String[] args) {
//map是这样用的吗,不是,一般不使用HashMap
//默认等价于什么?Map<String,String> map=new HashMap<>(16,0.75f);
//初始容量
//static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//最大容量
//static final int MAXIMUM_CAPACITY = 1 << 30;
//默认加载因子
//static final float DEFAULT_LOAD_FACTOR = 0.75f;
//HashMap并发不安全
//解决方法:
//1. Map<String,String> map=new ConcurrentHashMap<>();
Map<String,String> map=new ConcurrentHashMap<>();
for(int i=1;i<=10;i++){
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
4.Callable
Callable接口类似于Runnable,因为它们都是方便的实例,但是可能由另一个线程执行的类而设计的。 ,Runnable不会返回结果,也不能抛出已检查的异常。
1)Callable可以有返回值
2)可以抛出异常
3)方法不同call()相当于run()
要使用FutureTask才能使用Callable
package com.lbc.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//这样才能使用Thread调用Callable接口
//因为FutureTask是Runnable的实现类同时FutureTask的构造函数可以接收Callable
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
//只会打印处一个call()
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//结果会被缓存效率高
//得到Callable的返回值
Integer o =(Integer) futureTask.get();//get方法可能产生阻塞!把他放在最后或者使用异步取处理
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 1024;
}
}
细节:
1)有缓存
2)结果可能需要等待,会阻塞!
5.常用辅助类
5.1 CountDownLatch
是一个减法计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6,必须要执行任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"go out");
//每个线程执行完减1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归0之后再向下执行
System.out.println("关门");
// countDownLatch.countDown();//-1
}
}
原理
每次有线程调用countDownLatch.countDown();
则计数器减1,当计数器为0时,countDownLatch.await();//等待计数器归0之后再向下执行
就会被唤醒,继续向下执行
5.2 CyclicBarrier
简单理解为加法计数器
注意:当设定值大于线程数时无论如何都不会执行cyclicBarrier
/**
* 集齐7颗龙珠召唤神龙
*/
public class CyclicBarrierDemo {
public static void main(String[] args) throws InterruptedException {
//召唤龙珠的线程数,第一个参数线程数,第二个参数是一个Runnable
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功");
});
for (int i = 0; i < 7; i++) {
//lambda拿不到类外面的i,可以使用final中间值来拿
final int temp=i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
//当调用的线程达到之前设定的数量7,cyclicBarrier才会执行
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
},String.valueOf(i)).start();
}
}
}
5.3 Semaphore
Semaphore即信号量
package com.lbc.add;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
//参数为信号量,即线程数量,目的是为了限流
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
//acquire()得到信号量
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);//休眠两秒后释放
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
semaphore.release();//释放信号量
}
//release()释放信号量
},String.valueOf(i)).start();
}
}
}
原理
semaphore.acquire();
得到信号量-1,如果已经满了其他线程会等到信号量被释放为止
semaphore.release();
释放信号量,会将当前的信号量+1,唤醒等待的线程
目的:
1)多个共享资源互斥的使用
2)并发限流,控制最大的线程数
5.读写锁(ReadWriteLock)
ReadWriteLock维护一对关联的locks ,一个用于只读操作,另一个用于写入。 只要没有写入器, read lock可以由多个读取器线程同时保持。 write lock只能有一个线程写。
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
*
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache2 myCache = new MyCache2();
//5个线程只写
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
//5个线程只读
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存使用读写锁
*/
class MyCache2{
private volatile Map<String,Object> map=new HashMap<>();
//创建一个读写锁,便于更细粒度的操作
private ReadWriteLock 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) {
throw new RuntimeException(e);
}finally {
lock.readLock().unlock();
}
}
}
/**
* 自定义缓存未加锁有问题
*/
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");
}
}
读和读 :可以共存
读和写:不能共存
写和写:不能共存
独占锁:即写锁(一次只能一个线程占有)
共享锁:即读锁(多个线程可以同时占有)
6.阻塞队列BlockingQueue
分为写入操作和读取操作
不得不阻塞的操作
写入: 如果队列满了则无法写入发生阻塞
读取: 如果队列为空则阻塞等待队列不为空
SynchronousQueue:同步队列
ArrayBlockingQueue:数组阻塞队列
LinkedBlockingQueue:链表阻塞队列
BlockingQueue不是新的东西
6.1BlockingQueue的使用场景
多线程并发处理和线程池
6.1BlockingQueue的使用
四组API
1.抛出异常
2.不会抛出异常
3.阻塞等待
4.超时等待
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(参数,时间,单位) |
移除 | remove | poll(返回null) | take | poll(时间,单位) |
判断队列首部 | element | peek |
/**
* 抛出异常
*/
public static void test1() {
//不用传泛型,要传入队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
//如果队列满了继续添加则抛出异常
System.out.println(arrayBlockingQueue.add("d"));
}
/**
* 有返回值不抛出异常
*/
public static void test2() {
//不用传泛型,要传入队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
//如果队列满了继续添加返回布尔值false
//移除如果队列为空则返回null
System.out.println(arrayBlockingQueue.offer("d"));
}
/**
* 一直阻塞等待
*/
public static void test3() throws InterruptedException {
//不用传泛型,要传入队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
//如果队列满了则一直等待(阻塞)
arrayBlockingQueue.put("d");
}
/**
* 超时阻塞等待
*/
public static void test4() {
//不用传泛型,要传入队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
//如果队列满了则等待2秒之后不继续等待(阻塞)
System.out.println(arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS));
}
6.2SynchronousQueue(同步队列)
同步队列没有容量,必须得等待取出之后才可往里面放一个元素!
放入:put()
取出:take()
同步队列不同于其他BlockingQueue,只要里面有一个元素则必须先取出来之后才可继续放入
public class Test {
public static void main(String[] args) {
SynchronousQueue<String> objects = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1");
objects.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
objects.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
objects.put("3");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+objects.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+objects.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+objects.take());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"T2").start();
}
7.线程池(重点)
程序的运行,本质:占用系统的资源!优化资源的使用!==>池化技术
如:线程池,连接池,内存池,对象池
池化技术: 事先准备好一定的资源,有需要使用则占用,使用完之后则返还
要设置默认值和最大值
线程池的好处:
1)降低资源的消耗
2)提高响应的速度
3)方便管理
线程复用、可以控制最大并发数、管理线程
线程池:三大方法、七大参数、四种拒绝策略
7.1 线程池三大方法
//Executors工具类,3大方法
//使用了线程池之后要使用线程池来创建线程
public class Demo01 {
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 < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//线程池用完,程序结束后要关闭线程池
threadPool.shutdown();
}
}
}
7.2 七大参数
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,//约为21亿
new SynchronousQueue<Runnable>());
}
Executors创建线程池本质是调用了ThreadPoolExecutor
//七大参数
//
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.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;
}
int corePoolSize:核心线程池大小
int maximumPoolSize:最大线程池大小
long keepAliveTime:超时没人调用就会释放
TimeUnit unit:超时时间单位
BlockingQueue workQueue:阻塞队列
ThreadFactory threadFactory:线程工厂用于创建线程,一般不用动
RejectedExecutionHandler handler:拒绝策略
7.3 手动创建线程池
四种拒绝策略
拒绝策略则是,若线程池和阻塞队列满了,如果还有线程要进来则抛出异常
public class Demo02 {
public static void main(String[] args) {
//当核心线程满了并且阻塞队列满了则线程池扩容到最大值,若此时阻塞队列还满了,则会触发拒绝策略
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 3,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());//默认拒绝策略是如果还有线程要进来则抛出异常
try{
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//线程池用完,程序结束后要关闭线程池
threadPool.shutdown();
}
}
}
7.4 四大拒绝策略
拒绝策略则是,若线程池和阻塞队列满了,如果还有线程要进来则抛出异常
AbortPolicy(): 抛出异常
CallerRunsPolicy(): 从哪个线程来的让那个线程去执行(哪来去哪)
DiscardPolicy(): 丢掉任务但是不会抛出异常
DiscardOldestPolicy(): 尝试和最早得到线程的任务竞争,不会抛出异常
7.5 总结和拓展
池的最大线程该如何设置:
1)CPU密集型,最大线程设置为CPU核数,保持CPU效率最高
//获得CPU核数
Runtime.getRuntime().availableProcessors();
2)IO密集型,判断程序中十分耗IO的线程数,最大线程数一般设置为其两倍
8.四大函数式接口(必须掌握)
lambda表达式、链式编程、函数式接口、Stream流式计算、泛型、枚举、注解、反射
函数式接口:只有一个方法的接口
函数式接口能简化编程模型如foreach()
四大原生函数式接口:
1)Consumer
2)Function<T,R>
3)Predicate
4)Supplier
8.1Function函数型接口
/**
* Function函数型接口,有一个输入参数,有一个输出
* 只要是函数时接口就可以用lambda表达式简化
*/
public class Demo01 {
public static void main(String[] args) {
//只有一个方法传入参数T,返回类型R
//工具类:输出输入的值
// Function function = new Function<String,String>() {
// @Override
// public String apply(String o) {
// return o;
// }
// };
Function<String,String> function =str->{return str;};
System.out.println(function.apply("asd"));
}
}
8.2Predicate断定型接口
/**
* Predicate断定型接口,有一个输入参数,返回boolean值
* 只要是函数时接口就可以用lambda表达式简化
*/
public class Demo02 {
public static void main(String[] args) {
//判断字符串是否为空
// Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
Predicate<String> predicate=(s)->{return s.isEmpty();};
System.out.println(predicate.test("asd"));
}
}
8.3Consumer消费型接口
/**
* Consumer消费型接口,有一个输入参数,没有返回值
* 只要是函数时接口就可以用lambda表达式简化
*/
public class Demo03 {
public static void main(String[] args) {
// Consumer<String> consumer=new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// };
Consumer<String> consumer=(str)->{System.out.println(str);};
consumer.accept("asd");
}
}
8.4 Supplier供给型接口
/**
* Supplier供给型接口,没有输入参数,有返回值
* 只要是函数时接口就可以用lambda表达式简化
*/
public class Demo04 {
public static void main(String[] args) {
// Supplier<Integer> supplier=new Supplier<Integer>() {
//
// @Override
// public Integer get() {
// System.out.println("get()");
// return 1024;
// }
// };
Supplier<Integer> supplier=()->{return 1024;};
System.out.println(supplier.get());
}
}
9.Stream流式计算(必须掌握)
什么是流式计算?
大数据即是:存储+计算
存储:集合、MySQL、Redis
计算都应该交给流来操作
/**
* 题目要求:只能用一行代码实现
* 现在有5个用户!筛选:
* 1.ID必须是偶数
* 2.年龄必须大于23岁
* 3.用户名转为大写字母
* 4.用户名字母倒着排序
* 5.只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User u1=new User(1,"a",21);
User u2=new User(2,"b",22);
User u3=new User(3,"c",23);
User u4=new User(4,"d",24);
User u5=new User(6,"e",25);
//集合就是用来存储数据的
List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
//计算交给stream
//lambda表达式、链式编程、函数式接口、Stream流式计算
users.stream()
//ID为偶数的用户
.filter(u->{return u.getId()%2==0;})
//年龄大于23
.filter(u->{return u.getAge()>23;})
//用户名转为大写字母
.map(u->{return u.getName().toUpperCase();})
//用户名字母倒着排序
.sorted((u,v)->{return v.compareTo(u);})
//只输出一个用户
.limit(1)
.forEach(System.out::println);
}
}
10.Forkjoin
Forkjoin在JDK1.7,并行执行任务!在大数量时可以提高效率
大数据:Map.Reduce(把大任务拆分成小任务)
Forkjoin特点: 工作窃取,即当一个任务还未完成,另一个任务已经完成了,完成任务的线程会从正在工作的线程中拿取任务进行执行,避免该线程空闲,从而提高效率。维护的是双端队列。
Forkjoin的操作
使用Forkjoin和并行流实现大数的累积和
Forkjoin是需要调优的,必须是大数据量时使用
Forkjoin使用
//求和计算的任务
//对于大的数据可以使用Forkjoin
//如何使用forkjoin
//1.ForkJoinpool通过它来执行
//2.新建一个计算任务,ForkJoinPool.execute(ForkJoinTask task)
//3.继承ForkJoinTask,要返回值因此创建一个递归任务 class Fibonacci extends RecursiveTask<Integer>
//实现RecursiveTask类的compute方法
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
//临界值即数超过临界值就要分为两个任务
private Long temp=10000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
//计算方法
@Override
protected Long compute() {
//小于10000时
if(end-start<temp){
Long sum=0L;
for (Long i = start; i <= end; i++) {
sum+=i;
}
return sum;
}else {//forkjoin 使用的递归思想
long middle=(start+end)/2;//中间值
//拆分为两个任务
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
//把任务1压入线程队列
task1.fork();
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
//把任务2压入线程队列
task2.fork();
//将两个分任务的结果合并并返回
long l = task1.join() + task2.join();
return l;
}
}
}
测试
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//时间5027
// test1();
//2774
//test2();
//114
test3();
}
//普通实现
public static void test1(){
Long sum=0L;
long startTime=System.currentTimeMillis();
for (Long i = 1L; i <= 10_0000_0000; i++) {
sum+=i;
}
long endTime=System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(endTime-startTime));
}
//使用Forkjoin
public static void test2() throws ExecutionException, InterruptedException {
long startTime=System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
//创建ForkJoinTask
ForkJoinTask<Long> task = new ForkJoinDemo(0L,10_0000_0000L);
//将ForkJoinTask同步提交给ForkJoinPool,无返回结果
// forkJoinPool.execute(task);
//将ForkJoinTask异步提交给ForkJoinPool,有返回结果
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
long endTime=System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(endTime-startTime));
}
//使用并行stream流解决
public static void test3(){
long startTime=System.currentTimeMillis();
//stream并行流
//range()表示左开右开
//rangeClosed()表示左开右闭
//parallel()表示并行
//reduce(T identity, BinaryOperator<T> accumulator)表示使用累积函数得到的合并结果赋给identity
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long endTime=System.currentTimeMillis();
System.out.println("sum="+sum+" 时间:"+(endTime-startTime));
}
}
11.异步回调
Future: 其设计的初衷是为了对将来某个事件的结果进行建模,即线程的异步调用。
CompletableFuture的使用
/**
* 线程的异步调用(CompletableFuture)类似ajax
* 异步执行
* 成功回调
* 失败回调
*/
public class FutureDemo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//发起无返回值一个请求
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
});
//不会阻塞在上面,而是直接往下走,直到调用get()方法
System.out.println("1111");
//阻塞获取执行结果
completableFuture.get();
}
}
/**
* 线程的异步调用(CompletableFuture)类似ajax
* 异步执行
* 成功回调
* 失败回调
*/
public class FutureDemo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"runAsync=>Integer");
//让其错误执行
int i=10/0;
return 1024;
});
//阻塞获取执行结果
//whenComplete的参数时BiConsumer
System.out.println(completableFuture.whenComplete((t,u)->{
//正常执行
System.out.println("t=>"+t);//正常的返回结果
System.out.println("u=>"+u);//如果有错误则会打印错误信息
}).exceptionally((e)->{
//失败则执行
System.out.println(e.getMessage());
return 233;
}).get());
}
}
12.JMM
对volatile的理解
volatile时jvm提供的轻量级同步机制
1.保证可见性
2.不保证原子性
3.禁止指令重排
JMM是什么?
JMM是java内存模型,是一个约定、概念,并不是实际存在的
关于JMM的一些同步的约定:
- 线程解锁前,必须把线程自己的工作内存中修改的共享变量立刻刷回主存,是主存的值变为最新值
- 线程加锁前,必须把主存的最新值读取到线程自己的工作内存中
- 加锁和解锁是同一把锁
JMM8种操作
Java内存模型定义了以下八种操作来完成
数据同步八大原子操作
(1)lock(锁定): 作用于主内存的变量,把一个变量标记为一条线程独占状态
(2)unlock(解锁): 作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
(3)read(读取): 作用于主内存的变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
(4)load(载入): 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
(5)use(使用): 作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎
(6)assign(赋值): 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量
(7)store(存储): 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
(8)write(写入): 作用于工作内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
同步规则
八个操作两两一组不允许单独使用
1)不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中
2)一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或者assign)的变量。即就是对一个变量实施use和store操作之前,必须先自行assign和load操作。
3)一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现。
4)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量之前需要重新执行load或assign操作初始化变量的值。
5)如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
6)对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)
问题:线程不知道主内存的值已经发生修改了
13.Volatile
13.1保证可见性
问题代码,不加volatile程序会死循环
public class JMMDemo {
private static int number=0;
public static void main(String[] args) {//main线程
new Thread(()->{//线程1对主内存的变化不知道
while (number==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
number=1;
System.out.println(1);
}
}
volatile保证了可见性
public class JMMDemo {
private static volatile int number=0;
public static void main(String[] args) {//main线程
new Thread(()->{//线程1对主内存的变化不知道
while (number==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
number=1;
System.out.println(1);
}
}
13.2不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰也不能被分割,要么同时成功要么同时失败。
//不保证原子性
public class VolatileDemo {
//volatile不保证原子性
private static volatile int number=0;
public static void add(){
number++;
}
public static void main(String[] args) {
//理论上number结果为20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
//main 19669
System.out.println(Thread.currentThread().getName()+" "+number);
}
}
如果不加Lock或synchronized,怎么样保证原子性
使用原子类解决原子性问题
//解决volatile不保证原子性
public class VolatileDemo {
//volatile不保证原子性
//原子类的Integer
private static volatile AtomicInteger number=new AtomicInteger(0);
public static void add(){
//AtomicInteger的+1方法,使用了CAS
number.getAndIncrement();//不是一个原子性操作
}
public static void main(String[] args) {
//理论上number结果为20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
//main 19669
System.out.println(Thread.currentThread().getName()+" "+number);
}
}
这些原子类的底层和操作系统挂钩,直接在内存中修改值,Unsafe类是一个很特殊的存在
13.3禁止指令重排
什么是指令重排?
写的程序,计算机并不是按程序所写的顺序进行执行的
源代码---->编译器优化的重排---->指令并行也可能会重排---->内存系统也会重排---->执行
处理器在指令重排时会考虑数据之间的依赖性
int x=1;//语句1
int y=2;//语句2
x=x+5;//语句3
y=x*x//语句4
期望:1234顺序执行
计算机指令重排后可能变为:2134、1324
但是不可能重排为4123 因为处理器在指令重排时会考虑数据之间的依赖性
可能造成影响的结果:abxy这四个值默认为0
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常结果:x=0;y=0;
但是由于指令重排,由于同一线程内的两个指令没有依赖关系
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排导致的诡异结果:x=2;y=1;
指令重排难以发生,如果使用volatile可以避免指令重排
volatiel避免指令重排
由于使用内存屏障(一种CPU指令),作用:
- 保证特点的操作的执行顺序
- 可以保证某些变量的内存可见性(利用这个特性保证Volatile的可见性)
使用volatie时会在指令前后添加内存屏障。
volatile常用在单例模式(DCL懒汉式)
14.单例模式
14.1饿汉式单例模式
//饿汉式单例
//可能会浪费空间
public class Hungry {
private final static Hungry instance=new Hungry();
private Hungry(){
}
public static Hungry getInstance(){
return instance;
}
}
14.2DCL懒汉式单例模式
//懒汉式单例
public class LazyMan {
private LazyMan(){
}
//加volatile防止指令重排
private volatile static LazyMan instance;
//双重检测锁模式的懒汉式单例(DCL懒汉式)
public static LazyMan getInstance(){
if (instance==null){
synchronized (LazyMan.class){
if (instance==null){
instance=new LazyMan();//不是原子性操作,
/**
* 1.分类内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
* 可能会发生指令重排
* 期望123
* 可能132 A没问题
* B线程则认为instance构造成功,实际并未完成构造
*/
}
}
}
return instance;
}
}
14.3 静态内部类实现单例
package com.lbc.single;
//静态内部类实现
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.instance;
}
public static class InnerClass{
private static final Holder instance=new Holder();
}
}
14.4 反射,可以破坏单例
//反射,可以破坏单例
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
LazyMan instance=LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//为true时屏蔽java的访问检查,使得对象的私用属性、方法和构造函数都可以被访问
declaredConstructor.setAccessible(true);
LazyMan lazyMan = declaredConstructor.newInstance();
System.out.println(lazyMan==instance);
}
解决方法1
构造方法上加锁判断,形成三重检测锁
private LazyMan(){
//避免反射破坏
synchronized (LazyMan.class){
if (instance!=null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
问题: 如果只使用反射的newInstance()获得对象则还是会有问题
//反射,可以破坏单例
public static void main(String[] args) throws Exception{
// LazyMan instance=LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//为true时屏蔽java的访问检查,使得对象的私用属性、方法和构造函数都可以被访问
declaredConstructor.setAccessible(true);
//只使用newInstance创建对象
LazyMan instance=declaredConstructor.newInstance();
LazyMan lazyMan = declaredConstructor.newInstance();
System.out.println(lazyMan==instance);
}
解决方法2
构造方法上加锁判断,形成三重检测锁,并使用隐藏的标志位进行判断
//使用隐藏的标志位进行判断
private static boolean flag=false;
private LazyMan(){
//避免反射破坏
synchronized (LazyMan.class){
if (flag==false){
flag=true;
}else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
问题: 如果直到了这个隐藏的标志位仍然能破坏单例
//反射,可以破坏单例
public static void main(String[] args) throws Exception{
// LazyMan instance=LazyMan.getInstance();
Field flag1 = LazyMan.class.getDeclaredField("flag");
flag1.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//为true时屏蔽java的访问检查,使得对象的私用属性、方法和构造函数都可以被访问
declaredConstructor.setAccessible(true);
//只使用newInstance创建对象
LazyMan instance=declaredConstructor.newInstance();
flag1.set(instance,false);
LazyMan lazyMan = declaredConstructor.newInstance();
System.out.println(lazyMan==instance);
}
14.5 防止反射破坏单例的最终解决方法
使用枚举类
package com.lbc.single;
//enum本身也是一个class
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
EnumSingle instance=EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
System.out.println(instance==enumSingle);
}
}
15.CAS
什么是CAS
CAS:比较当前工作内存的值和主内存中的值,如果这个值是期望中的,则执行操作否则就一直循环等待。
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
public class CASDemo {
//cas
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2020);
//期望,更新
//public final boolean compareAndSet(int expectedValue, int newValue) {
//如果期望的值达到了就更新,否则就不更新,CAS是CPU的并发原语
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
//会修改失败
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
}
}
原子类的底层:Unsafe类
atomicInteger.getAndIncrement()方法的底层:
1.var1、var2、var4
分别代表,当前对象、当前对象从Unsafe
中获取的当前对象的内存地址偏移量、和要加的值
2.根据当前对象和当前对象的内存地址偏移量得到该对象的内存地址中存的值并作为var5
3.再拿当前对象和当前对象的偏移量作为期望值和var5进行对比,如果相等则更新
底层使用的CAS
15.1CAS的ABA问题
ABA问题即狸猫换太子
即线程B先改变值,之后再改回原值,此时线程A就不知道这个值发生了改变。这就是ABA问题
public class CASDemo {
//cas
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2020);
//期望,更新
//public final boolean compareAndSet(int expectedValue, int newValue) {
//如果期望的值达到了就更新,否则就不更新,CAS是CPU的并发原语
//=====================捣乱的线程=============================
atomicInteger.compareAndSet(2020,2021);
System.out.println(atomicInteger.get());
atomicInteger.compareAndSet(2021,2020);
System.out.println(atomicInteger.get());
//=====================期望的线程=============================
atomicInteger.compareAndSet(2020,6666);
System.out.println(atomicInteger.get());
}
}
16.原子引用解决ABA问题
引入原子引用即可:即是乐观锁的原理
public class CASDemo {
//cas
public static void main(String[] args) {
// AtomicInteger atomicInteger=new AtomicInteger(2020);
//AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
//正常在业务,比较的都是一个个对象,不会出现Integer类似的问题
//和乐观锁的原理相同
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("a1===>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a2===>"+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3===>"+atomicStampedReference.getStamp());
},"a").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("b1===>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(atomicStampedReference.compareAndSet(1, 6,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("b2===>"+atomicStampedReference.getStamp());
},"b").start();
}
}
注意
16.各种锁的理解
16.1公平锁和非公平锁
公平锁:非常公平,不能够插队,必须先来后到
非公平锁:非常不公平,可以插队(默认非公平锁),防止一个线程执行时间很长而另一个线程执行时间很短,而导致短线程要等待长线程执行完后再执行。
//默认非公平锁 Lock和Synchronized都一样
public ReentrantLock() {
sync = new NonfairSync();
}
//加一个true设为公平锁
Lock lock=new ReentrantLock(true);
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
16.2可重入锁(递归锁)
所有锁都是可重入锁
//可重入锁测试synchronized版
public class RLockDemo {
public static void main(String[] args) {
Phone phone=new Phone();
/**
* Asms
* Acall
* Bsms
* Bcall
*/
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms");
call();//这里也有锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
}
}
//可重入锁测试Lock版
public class RLockDemo02 {
public static void main(String[] args) {
Phone02 phone=new Phone02();
/**
* Asms
* Acall
* Bsms
* Bcall
*/
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone02{
Lock lock=new ReentrantLock();
public void sms(){
lock.lock();//细节问题是拿到了两把锁,Lock锁必须配对,否则无法退出
try {
System.out.println(Thread.currentThread().getName()+"sms");
call();//这里也有锁
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}
}
public synchronized void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"call");
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}
}
}
16.3自旋锁(spinlock)
不断去尝试是获取锁,直到拿到锁为止
package com.lbc.lock;
import java.util.concurrent.atomic.AtomicReference;
//自定义自旋锁
public class SpinlockDemo {
//默认值为null
AtomicReference<Thread> atomicReference=new AtomicReference<>();
//加锁
public void myLock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"--->MyLock");
while (!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void myUnLock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"--->MyUnLock");
atomicReference.compareAndSet(thread,null);
}
}
package com.lbc.lock;
import java.util.concurrent.TimeUnit;
//自定义锁测试
public class TestSpinLock {
public static void main(String[] args) throws Exception {
//底层使用CAS
SpinlockDemo lock = new SpinlockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
lock.myUnLock();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
lock.myUnLock();
}
},"B").start();
}
}
16.4死锁
死锁测试,怎么排除死锁
public class DeadLockDemo {
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) {
throw new RuntimeException(e);
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"=>get"+lockA);
}
}
}
}
- 使用jps定位进程号
jps -l
查看进程
- 使用
jstack 进程号
找到死锁问题
如何排除问题:
1)查看日志
2)查看堆栈信息