博主主页: 码农派大星.
数据结构专栏:Java数据结构
数据库专栏:MySQL数据库
JavaEE专栏:JavaEE
关注博主带你了解更多数据结构知识
1.CAS
1.1CAS全称:Compare and swap
比较内存和cpu寄存器中的内容,如果发现相同,就进行交换(交换的是内存和另一个寄存器的内容)
一个内存的数据 和 两个寄存器中的数据进行操作(寄存器1和寄存器2)
此处的"交换"实际上就是赋值
比较内存和寄存器1中的值,是否相等,如果不等,就无事发生,如果相等,就交换内存和寄存器2的值(此处只关心内存交换后的内容,不关心寄存器2交换后的内容)
因此CAS就能编写多线程代码,"无锁化编程"
CAS具体使用场景
1)基于CAS实现原子类 都是线程安全
import java.util.concurrent.atomic.AtomicInteger;
public class Main5 {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i <50000 ; i++) {
count.getAndIncrement();//count++
// count.incrementAndGet();//++count
// count.getAndDecrement();//count--
// count.decrementAndGet();//--count
// count.getAndAdd(10);//count+= 10
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i <50000 ; i++) {
count.getAndIncrement();//count++
/* count.incrementAndGet();//++count
count.getAndDecrement();//count--
count.decrementAndGet();//--count*/
}
});
t1.start();
t2.start();
t1.join();
t1.join();
System.out.println(count.get());
}
}
2.Callable接口
Callable 接口也是创建线程的一种方式。相当于把线性封装了一个返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
return sum;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
System.out.println(futureTask.get());
}
}
3.创建线程的方式:
1.直接继承Thead类
2.使用Runnable
3.使用Callable
4.使用lambda
5.使用线程池
4.ReentrantLock
4.1synchroniazed与ReentrantLock区别
1)大多是情况下使用synchronized,属于关键字(底层是通过JVM的c++代码实现的)
ReentrantLock则是标准库提供的类,通过Java代码实现的
2)synchronized通过代码块控制加锁解锁,ReentrantLock通过调用lock unlock方式来完成 unlock可能会遗漏
3)ReentrantLock 提供了tryLock这样的加锁风格
前面介绍的加锁,都是发现锁被别人占用了,就阻塞等待
tryLock在加锁失败的时候,不会阻塞,而是返回,通过返回值来反馈是加锁成功还是失败
4)1Reentrantlock提供了公平锁的实现
默认是非公平的,可以在构造方法中,传入参数,设定成公平的
5)Reentrantlock还提供了功能更强的功能"等待通知机制"
基于Condition类,能力要比wait notify更强一些.
import java.util.concurrent.locks.ReentrantLock;
public class Main2 {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
ReentrantLock locker = new ReentrantLock();
Thread t1 = new Thread(()->{
for (int i = 0; i < 50000; i++) {
locker.lock();
count++;
locker.unlock();
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i <50000 ; i++) {
locker.lock();
count++;
locker.unlock();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
5.信号量Semaphore
import java.util.concurrent.Semaphore;
public class Main3 {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore =new Semaphore(3);//可硬资源数,计数器初始值
semaphore.acquire();
System.out.println("申请一个资源");
semaphore.acquire();
System.out.println("申请一个资源");
semaphore.acquire();
System.out.println("申请一个资源");
semaphore.release();
System.out.println("释放一个资源");
semaphore.acquire();
System.out.println("申请一个资源");
}
}
6.CountDownLatch
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main4 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
//构造方法的数字,就是拆分出来的任务个数
CountDownLatch countDownLatch =new CountDownLatch(20);
for(int i = 0 ; i < 20;i++){
int id = i;
executorService.submit(()->{
System.out.println("下载任务"+id+"开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("下载任务"+id+"结束任务");
//完毕!!
countDownLatch.countDown();
});
}
//当countDownLatch收到20个"完成",所有的任务就完成了
//await= allwait
countDownLatch.await();
}
}
借助CountDownLatch就能衡量出当前任务是否整体执行结束
7.线程安全的集合类
解决方案:
1)自己加锁
2)如果要使用ArraryList/LinkedList这样的结构
标准库中,提供了一个带锁的List
还可以使用CopyOnWrite集合类
写时拷贝的缺点:
1)无法应对,多个线程同时修改的情况
2)如果涉及到数据量很大,拷贝起来非常慢
3)想多线程环境下使用队列
BlockingQueue
4)多线程环境下使用哈希表
Hashtable虽然是可选项
ConcurrentHashMap[推荐]
此处这个数据结构,相比于HashMa和Hashtable 来说,改进力度非常大的
1)优化了锁的粒度
Hashtable的加锁,就是直接给put get 的方法加上synchronized,就是给this加锁
整个哈希表对象就是一把锁,任何一个针对这个哈希表的操作,都会触发锁竞争
ConcurrentHashMap则是给每个哈希表中的"链表" 进行加锁.(不是一把锁,而是多把锁)
上述设定方式,是可以保证线程安全的!!
其次可大大降低锁冲突的概率,只有同时进行两次修改,恰好在修改在同一链表元素上的时候,才会
触发锁竞争
2)ConcurrentHashMap引入了CAS原子操作.针对像修改了size这样的操作,直接借助CAS完成,才不会加锁
3)针对读操作,做了特殊处理 .上述的加锁,只是针对写操作,加锁
对于读操作,通过volatile以及一些精巧的代码实现,确保读操作,不会读到"修改一半的数据"
4)针对hash表的扩容,进行了特殊的优化
普通hash表的扩容,需要创建新的hash表,把元素搬过去
这一列操作,很有可能就在一次put中就完成了就会使这次put开销非常大,耗时非常长
ConcurrentHashMap进行了"化整为零" 不会在一次操作中,进行搬运所有数据而是一次搬运一部分,伺候每次操作,都会触发,一部分key的搬运,最终把所有的key都搬运完成
当新旧同时存在的时候
1)插入操作,直接插入到新空间中
2)查询/修改/删除/都需要同时查询旧的空间和新空间
标签:加锁,进阶,Thread,util,import,new,多线程,public From: https://blog.csdn.net/jj666mhhh/article/details/140770019