JUC是java.util.concurrent工具包的简称,这是一个处理线程的工具包,jdk1.5开始出现的。
Synchronized是java中的关键字,是一种同步锁。可以加到代码块、方法和类上。加锁和解锁都是自动完成的,不需要额外的代码。
Lock是JUC包下的一个接口,使用的时候需要手动加锁和解锁,不能够自动解锁。不手动释放锁的话可能会引起死锁,所以释放锁一般会写在finally中。
Synchronized与Lock的主要区别有以下几点:
synchronized通过wait()方法让线程等待,通过notify()和notifyAll()唤醒线程。
lock通过await()方法让线程等待,通过signal()和signalAll()唤醒线程。
实现的流程一般都是:判断(不能用if,必须使用while)->操作->通知。
//-1的方法
public synchronized void decr() throws InterruptedException {
//判断
while(number != 1){
this.wait();
}
//干活
number--;
//通知其他线程
System.out.println(Thread.currentThread( ).getName()+" :: "+number );
this.notifyAll();
}
class Test{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//+1
public void incr() throws InterruptedException {
//上锁
lock.lock();
try {
//判断
while(number != 0) {
condition.await();
}
//干活
number++;
system.out.println(Thread.currentThread().getName()+"::"+number);
//通知其他线程
condition.signalAll();
}finally {
//解锁
lock.unlock();
}
}
}
让线程按照约定的顺序进行输出,就是某个线程运行完后唤醒指定的其他线程。
比如:A线程指定唤醒B,B指定唤醒C,C指定唤醒A。
实现代码如下:
//第一步 创建资源类
class ShareResource {
//定义标志位
private int flag = 1; // 1 AA 2 BB 3 CC
//创建Lock锁
private Lock lock = new ReentrantLock();
//创建三个condition
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
//打印5次,参数第几轮
public void print5(int loop) throws InterruptedException {
//上锁
lock.lock();
try {
//判断
while(flag != 1) {
//等待
c1.await();
}
//干活
for (int i = 1; i <=5; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//通知
flag = 2; //修改标志位 2
c2.signal(); //通知BB线程
}finally {
//释放锁
lock.unlock();
}
}
//打印10次,参数第几轮
public void print10(int loop) throws InterruptedException {
lock.lock();
try {
while(flag != 2) {
c2.await();
}
for (int i = 1; i <=10; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//修改标志位
flag = 3;
//通知CC线程
c3.signal();
}finally {
lock.unlock();
}
}
//打印15次,参数第几轮
public void print15(int loop) throws InterruptedException {
lock.lock();
try {
while(flag != 3) {
c3.await();
}
for (int i = 1; i <=15; i++) {
System.out.println(Thread.currentThread().getName()+" :: "+i+" :轮数:"+loop);
}
//修改标志位
flag = 1;
//通知AA线程
c1.signal();
}finally {
lock.unlock();
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print5(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print10(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()->{
for (int i = 1; i <=10; i++) {
try {
shareResource.print15(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
}
}
ArrayList是线程不安全的,在多线程的情景下可能会报并发异常的错误,因为它的add方法没有加锁。
要想保证集合的安全,有以下几种方案:
要想保证HashSet的线程安全,有以下几种方案:
常见面试题:
要想保证HashMap的线程安全,有以下几种方案:
公平锁:
先到的线程先获取锁,保证了线程获取锁的顺序。
非公平锁:
多个线程的情况下,先申请锁的线程未必就会先获得锁。无法保证线程获取锁的顺序。
JUC包下的ReentrantLock默认是非公平锁,可以设置为公平锁。Synchronized也是非公平锁。
可重入锁也就递归锁,指的是同一线程的外层函数获得锁之后,内层递归函数仍然能获取该锁。
A方法调用B方法,A方法有锁的话,B方法会自动获取到A方法的锁。
ReentrantLock和Synchronized都是非公平可重入锁。
自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式尝试获取锁。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
线程池运行原理:
一个都没有使用,用的是自定义的线程池。
首页要明白,业务时CPU密集型还是IO密集型。
//查看CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
CPU密集型
CPU密集型是指,一般需要大量的计算工作,没有阻塞,CPU全速允许。
这种情况下尽量减少线程的切换,因为线程上下文的切换也会占用CPU。
参考公式:CPU核数+1个线程的线程池
IO密集型
IO密集型任务并不是一直执行任务,应配置尽可能多的线程数,如CPU核数*2。
还有一种公式:CPU核数 / (1-阻塞系数)。 阻塞系数在0.8-0.9之间。