Java并发总结
Posted yuwenS.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发总结相关的知识,希望对你有一定的参考价值。
文章目录
1、Java并发
1.1、使用线程
使用线程有三种方法:
继承Therad类
实现Runnable接口
实现Callable接口
继承Thread类:
需要实现Run()方法,因为Thread类实现了Runnable接口
当调⽤ start() ⽅法启动⼀个线程时,虚拟机会将该线程放⼊就绪队列中等待被调度,当⼀个线程被调度 时会执⾏该线程的 run() ⽅法
public class MyThread extends Thread {
public void run() {
// ...
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
t.start()方法会创建一个新栈,然后立马结束,从而实现并发
实现接口:
实现 Runnable 和 Callable 接⼝的类只能当做⼀个可以在线程中运⾏的任务,不是真正意义上的线程, 因此最后还需要通过 Thread 来调⽤。可以理解为任务是通过线程驱动从⽽执⾏的
实现Runnable接口:
需要实现接⼝中的 run() ⽅法
public class MyRunnable implements Runnable {
@Override
public void run() {
// ...
}
}
使⽤ Runnable 实例再创建⼀个 Thread 实例,然后调⽤ Thread 实例的 start() ⽅法来启动线程
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}
实现Callable接口:
与 Runnable 相⽐,Callable 可以有返回值,返回值通过 FutureTask 进⾏封装
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
public static void main(String[] args) throws ExecutionException,
InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
接口实现比较常用,因为Java是单继承的,如果使用继承那么就不能继承别的类。而接口实现可以去继承别的类
线程生命周期:线程生命周期有5大状态
新建状态:刚刚new出来的线程
就绪状态:该状态的线程已经准备就绪,可以抢夺cpu时间片
运行状态:该状态是就绪状态的线程抢到了cpu时间片,当之前的时间片用完之后又会回去就绪状态抢夺时间片
阻塞状态:阻塞状态的线程会放弃之前占有的cpu时间片(遇到了阻塞时间会进入该状态,如键盘输入、sleep()方法)当阻塞状态解除后就会回到就绪状态去抢夺时间片
死亡状态:run结束,线程消失
1.2、方法
sleep()方法出现在哪个线程中,哪个线程就休眠跟 对象调用无关,会转化为Thread.sleep(),而不是对象休眠
实例方法:
设置线程的优先级:setPriority(int newPriority)
获取线程优先级:getPriority()
最低优先级1
默认优先级5
最高优先级10
合并线程:线程.join()
当前线程进入阻塞状态,调用join()的线程执行,直到该线程执行完成,当前线程才可以继续执行
静态方法:
线程让位:yield()
让另一个线程执行,但是让位线程不是进入阻塞状态,而是重新进入就绪状态
1.3、互斥同步
1.3.1、线程同步机制(synchronized)
线程同步机制的语法:
synchronized(共享对象){
//线程同步代码块
}
synchronized()表明线程允许时必须拿到共享变量的锁才能继续执行,在遇到synchronized时相当于进入了阻塞状态,需要拿到锁才能进入就绪状态去抢夺时间片
synchronized(共享对象) 中的共享对象,是你自己定义想要哪几个线程共享的对象,这样那几个线程就会进行线程排队
synchronized同步一个类时,两个线程调用同一个类的不同对象,也会进行线程等待排队
synchronized(类名.class){
//线程同步代码块
}
Java中的三大变量
实例变量:在堆中
静态变量:在方法区中
局部变量:在栈中
在三大变量中:局部变量永远不会存在线程安全问题,因为局部变量不共享,(一个线程一个栈)
如果锁在了静态变量中相当于拿到到的是类锁
在开发中如何解决线程安全问题
尽量使用局部变量代替实例变量
如果必须是实例变量,就尽量创建多个对象
如果必须是实例变量并且只能创建一个对象,那么就用synchronized()锁住同步代码块
1.3.2、ReentrantLock
跟synchronized类似,都是通过锁住线程同步语句块来保证线程安全,但是synchronized是自动释放锁,而ReentrantLock需要手动释放锁
public class ReentrantLockTest {
private Lock lock = new ReentrantLock();
public void test(){
lock.lock();
try {
System.out.println("来锁住我吧");
}finally {
lock.unlock(); //手动释放锁,避免死锁的产生
}
}
}
1.3.3、二者比较
锁的实现:
synchronized 是 JVM 实现的,⽽ ReentrantLock 是 JDK 实现的
性能:
新版本 Java 对 synchronized 进⾏了很多优化,例如⾃旋锁等,synchronized 与 ReentrantLock⼤致相同
等待可中断:
当持有锁的对象长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其它的事情,ReentrantLock可中断,而synchronized 不行
公平锁:
公平锁是指在多个线程等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁
synchronized 中的锁的不公平的,ReentrantLock默认情况下也是非公平的,但是也可以是公平的
使用选择:
除⾮需要使⽤ ReentrantLock 的⾼级功能,否则优先使⽤ synchronized。这是因为 synchronized 是 JVM 实现的⼀种锁机制,JVM 原⽣地⽀持它,⽽ ReentrantLock 不是所有的 JDK 版本都⽀持。并且使 ⽤ synchronized 不⽤担⼼没有释放锁⽽导致死锁问题,因为 JVM 会确保锁的释放
1.4、守护线程
具有代表性的守护线程:垃圾回收线程
将线程设置为守护线程:线程.setDaemon(true);
所有线程结束时,守护线程也会结束,无论守护线程是否还在运行
1.5、定时器
间隔特定的时间,执行特定的程序
1.6、关于Object中的wait()和notify()方法
wait()和notify()方法不是线程对象的方法,是Java中任何一个Java对象都有的方法,应为这两个方法是Object自带的
wait()方法:让正调用的对象上活动的线程进入等待状态,无限期等待,直到被唤醒为止。并且还会释放锁
notify()方法:唤醒调用对象上活动的线程
notifyAll()方法:唤醒所有进入等待状态的线程
与wait()和notify()类似的方法:
await() signal() signalAll()个方法
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调⽤ await() ⽅法使线程等待,其它线程调⽤ signal() 或 signalAll() ⽅法唤醒等待的线程。 相⽐于 wait() 这种等待⽅式,await() 可以指定等待的条件,因此更加灵活。 使⽤ Lock 来获取⼀个 Condition 对象
1.7、线程池
线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务
ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理
- execute(Runnable任务对象) 把任务丢到线程池
Executors:辅助创建线程池的工具类
- newFixedThreadPool(int nThreads) 最多n个线程的线程池
- newCachedThreadPool() 足够多的线程,使任务不必等待
- newSingleThreadExecutor() 只有一个线程的线程池
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"------->"+"hello");
}
});
}
pool.shutdown();
}
1.8、Java内存模型
Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达 到⼀致的内存访问效果
1.8.1、内存间交互操作(8大操作)
read:把一个变量从主内存中传递到工作内存中
load:在read之后执行,把read得到的值放入工作内存变量副本之中
use:把工作内存中一个变量的值传递给执行引擎
assign:把⼀个从执⾏引擎接收到的值赋给⼯作内存的变量
store:把⼯作内存的⼀个变量的值传送到主内存中
write:在 store 之后执⾏,把 store 得到的值放⼊主内存的变量中
lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态
unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来 , 释放后的变量才可以被其他线程锁定unlock(解锁):作用于主内存的变量,把一个处于锁定状态的变量释放出来 , 释放后的变量才可以被其他线程锁定
1.8.2、内存模型的三大特性
原子性: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。最常遇到的 就是回滚操作解决这个问题
Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
AtomicXXX 类也能保证原子性
可见性: 可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
Java提供了volatile关键字来保证可见性,当一个共享变量被volatile修饰时,它保证本修改的值会被立刻更新到主存。当有其它线程去读取时,会去主存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性
通过synchronized和Lock也能保证可见性,因为synchronized和Lock能保证同一时刻只有一个线程获取锁任何执行同步代码,并且会在释放锁之前将变量的修改更新到主存中,所有能保证可见性
有序性: 指在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,即程序执行的顺序按照代码的先后顺序执行,一般情况下,处理器由于要提高执行效率,对代码进行重排序,运行的顺序可能和代码先后顺序不同,但是结果一样。单线程下不会出现问题,多线程就会出现问题了
volatile 关键字通过添加内存屏障的⽅式来禁⽌指令重排,即重排序时不能把后⾯的指令放到内存屏障 之前。 也可以通过
synchronized 来保证有序性,它保证每个时刻只有⼀个线程执⾏同步代码,相当于是让线 程顺序执⾏同步代码
1.9、锁优化
这里所说的是JVM对synchronized锁优化
1.9.1、自旋锁
互斥同步进⼊阻塞状态的开销都很⼤,应该尽量避免。在许多应⽤中,共享数据的锁定状态只会持续很 短的⼀段时间,为了避免在这个短时间内进入阻塞,就引入了自旋锁。自旋锁的思想就是让一个线程请求一个共享数据时等待一段时间(自旋)(无意义的循环),看持有锁的线程是否会很快释放锁
自旋锁虽然能避免进入阻塞状态而减少开销,但是它需要进行忙循环操作占用cpu时间,它只适用共享数据锁定状态很短的场景
1.9.2、锁消除
锁消除是检测出来不存在竞争的共享数据的锁进行消除。锁消除的依据是逃逸分析的数据支持
1.9.3、锁粗化
如果一系列的连续操作都对同一个对象进行反复的加锁和解锁,频繁的加锁就会导致性能损耗
1.9.4、轻量级锁
引入轻量级锁的主要目的是在多没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。其对锁的获取和释放依靠更加轻量的 cas 实现,从而减少性能损耗
1.9.5、偏向锁
偏向锁的思想是偏向于让第⼀个获取锁对象的线程,这个线程在之后获取该锁就不再需要进⾏同步操 作,甚⾄连 CAS 操作也不再需要
以上是关于Java并发总结的主要内容,如果未能解决你的问题,请参考以下文章
经验总结:Java高级工程师面试题-字节跳动,成功跳槽阿里!
全栈编程系列SpringBoot整合Shiro(含KickoutSessionControlFilter并发在线人数控制以及不生效问题配置启动异常No SecurityManager...)(代码片段