Java
并发:
线程状态?
新建 new: 创建后尚未启动;
可运行 Runnable:可能正在运行,也可能正在等待CPU时间片;
(包含了操作系统线程状态中Running和Ready)
阻塞 Blocking:等待获取一个排它锁,如果其线程释放了锁,就会结束此状态。
无限期等待 Waiting:等待其他线程显式唤醒,否则不会被分配CPU时间片。
限期等待 Timed Waiting:无需等待其他线程显式地唤醒,在一定时间之后会被系统自动唤醒。
死亡 Terminated:可以是线程结束任务之后自己结束,或者产生了异常而结束。
实现线程的方式?
1. 继承Thread类
2. 实现Runnable接口
3. 实现Callable接口:Callable 可以有返回值,返回值通过 FutureTask 进行封装
4. 创建线程池
public class MyThread extends Thread {
public void run() {
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
public class MyRunnable implements Runnable {
public void run() {
}
}
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}
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());
}
线程池:
线程池 ThreadPoolExecutor:
三种定义好的线程池,但不推荐使用:
1、newCachedThreadPool:有多少任务,创建多少线程。CPU干100%。
2、newFixedThreadPool(10):超过第10个任务,任务要排队等待(10个10个一组)。
内存溢出。
3、newSingleThreadPool:和Fixed同理,也会内存溢出。
推荐使用自定线程池:
常用7个参数:
1. 核心线程数:int corePoolSize
线程池创建好后,就准备就绪的线程数量,等待来接受异步任务执行。
2. 池中允许的最大线程数:int maxinumPoolSize
3. 存活时间:long keepAliveTime
当线程数大于核心线程数时,这是多余的空间线程在终止前,等待新任务的最长时间。
4. 时间单位 TimeUnit unit
5. 阻塞队列 BlockingQueue<Runnable> workQueue
如果任务有很多,就会将目前多的任务,放在队列里;
只要有线程空闲了,就会去队列里面取出新的任务继续执行。(默认)
6. 线程的创建工厂 ThreadFactory threadFactory
7. 拒绝策略 RejectedExecutionHandler handler
如果队列满了,按照指定的拒绝策略执行任务。(默认使用丢弃策略AbortPolicy)
举例:一个线程池:core 7,max 20,queue:50;
100个并发进来怎么执行?
7个会立刻执行,50个会进入队列,再开13个进入执行,剩下30个就被拒绝执行。
阻塞队列:BlockingQueue
1. ArrayBlockingQueue
2. LinkedBlockingQueue
拒绝策略:RejectedExecutionHandler
1. AbortPolicy:丢弃任务并抛出异常:
RejectedExcutionException异常;
2. DiscardPolicy:丢弃任务,但是不抛出异常;
3. DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。
4. CallerRunsPolicy:重试添加当前的任务,自动重复调用execute()方法,直到成功。
配置线程池考虑因素:
任务的优先级,
任务的执行时间长短,
任务的性质(CPU密集/ IO密集),
任务的依赖关系。
CPU密集型: 尽可能少的线程,Ncpu+1;
IO密集型: 尽可能多的线程, Ncpu*2,比如数据库连接池;
混合型: CPU密集型的任务与IO密集型任务的执行时间差别较小,拆分为两个线程池;否则没有必要拆分。
public class ThreadPoolTest {
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 20L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 0; i < 40; i++) {
poolExecutor.execute(new MyTask(i));
}
poolExecutor.shutdown();
}
}
class MyTask implements Runnable {
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "___" + i);
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
异步执行:
CompletableFuture 异步执行任务。
Future:用于Runnable和Callable任务执行结果,进行取消、查询是否完成,获取结果;
必要时使用get方法获取执行结果,该方法会阻塞到任务返回结果。
CompletableFuture的 join和get 的区别?
1. 都是获取CompletableFuture异步之后的返回值的;
2. join抛出异常是uncheck异常,未经检查的异常,不会强制开发者捕捉
(CompletionException、CancellationException);
get抛出的是经过检查的异常,需要手动try-catch抛出
(ExecutionException、InterruptedException)。
1.
runAsync: 启动一个异步任务,无返回值;
supplyAsync: 启动一个异步任务,有返回值。
public static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20,
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(50), new ThreadPoolExecutor.CallerRunsPolicy());
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture.runAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getName());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}, executor);
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getName());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}, executor);
Integer resultNum = future.get();
System.out.println("异步任务有返回值的,返回值结果为:" + resultNum);
}
2.
whenComplete:能够接受返回值和异常,但没法修改返回值;和上一个调用执行是同一个线程,
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getName());
int i = 10 / 0;
System.out.println("运行结果:" + i);
return i;
}, executor).whenComplete((res, throwable) -> {
System.out.println(res);
System.out.println(throwable.getMessage());
}).exceptionally(throwable -> {
System.out.println(throwable.getMessage());
return 10;
});
3.
handle: 无论成功或失败,都可以在回调中,接受异常,接受返回值并修改数据。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getName());
int i = 10 / 0;
System.out.println("运行结果:" + i);
return i;
}, executor).handle((res, throwable) -> {
if (ObjectUtil.isNotNull(res)) {
if (res % 2 == 0) {
return 2;
}
return 1;
}
System.out.println(throwable.getMessage());
return -1;
});
Integer resNum = future.get();
System.out.println("最终返回值:" + resNum);
方法start run join wait sleep
:
start():
底层调用start0方法,会创建新的线程;
start0方法是本地方法jvm调用,是c语音实现的。
run():
只是简单的方法调用,不会创建新的线程。
main线程启动一个子线程Thread时,主线程不会阻塞,而是主线程和子线程交替执行;
(不是主线程结束了,子线程也结束)
join():
线程插队,插队的线程一旦成功,肯定先执行,插入的线程所有任务。
yield():
线程让步,让出CPU使用权,让其他线程执行。(让了也不一定成功)
wait():
wait是object方法,任何对象实例都能调用;
wait会释放锁,但调用它的前提是:当前线程占用锁。
sleep():
sleep是Thread的静态方法;
sleep不会释放锁,也不会占用锁。
锁?
线程要不要锁住同步资源?
锁住,悲观锁;
不锁住,乐观锁;
锁住同步资源失败,线程要不要阻塞?
阻塞;
不阻塞;自旋锁,适应性自旋锁;
一个线程中的多个流程能不能获取同一把锁?
能,可重入锁;
不能,非可重入锁;
多个线程竞争锁时,要不要排队?
排队,公平锁;
先尝试插队,插队失败在排队; 非公平锁;
多个线程能不能共享一把锁?
能,共享锁;
不能,排它锁;
多个线程竞争同步资源的流程细节有没有区别?
无锁;不锁住资源,多个线程中只有一个能修改资源成功,其他线程会重试;
偏向锁;同一个线程执行同步资源时自动获取资源;
轻量级锁;多个线程竞争同步资源时,没有获取资源的线程自选等待锁释放;
重量级锁;多个线程竞争同步资源时,没有获取资源的线程阻塞等待唤醒;
悲观锁:(查询,新增)
认为线程安全问题,一定会存在;
因此在操作数据之前,先获取锁,确保线程串行执行。
如:Synchronized,Lock (添加同步锁,让线程串行执行)
乐观锁:(只能用于修改)
认为线程安全问题不一定会发生,因此不加锁;
只是在更新数据时,去判断有没有其他线程修了数据。
如果没有修改,则认为是安全的,自己才更新数据;
如果已经被其他线程修改,说明发生了安全问题,此时可以重试或抛异常。
1.版本号:version查询要更新版本号,更新时也要更新版本号;
2.简化:可使用 库存>0 代替版本号 (CAS法 比较并交换);
(不加锁,在更新时判断是否有其他线程在修改)。
public synchronized void testMethod() {
}
private ReentrantLock lock = new ReentrantLock();
public void modifyPublicResources() {
lock.lock();
lock.unlock();
}
private AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.incrementAndGet();
阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成的,这种状态转换需要耗费处理器时间;
如果同步代码块的内容过于简单,状态转换消耗的时间可能比代码执行的时间还要长。
为了让当前线程 等待一下,我们要让当前线程进行自旋,如果在自旋完成后,前面锁定的同步资源的线程就释放了;
那么当前线程就不必阻塞,而是直接获取同步资源,从而避免切换线程的开销,者就是自旋锁。
1. 自旋锁:
某线程尝试获取同步资源的锁失败,资源被占用;
不放弃CPU时间片,通过自旋等待释放锁;
再尝试获取锁,如果失败,就自旋继续等待;
(通过自旋操作减少CPU切换,以及恢复现场导致的消耗)
获取锁成功,获取同步资源的锁。
自旋锁本身是由缺点的:
它不能代替阻塞,自旋等待虽然避免了线程切换的开销;
但它要占用处理器时间,如果锁被占用的时间很短,自旋等待的效果就会非常好;
反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。
自旋的等待时间必须要有一定的限度,如果自旋超过了限定次数;
(默认10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。
自旋锁的实现原理同样也是CAS,
AtomicInteger中调用unsafe进行自增操作的源码中的do-while循环就是一个自旋操作,如果修改数值失败则通过循环来执行自旋,直至修改成功。
ABA问题:
线程1:拿到的了A的值,把修改为B;
线程2:抢到A,把修改成了C,但最后又修改成了A)。
解决方法:加个version字段,修改一次,版本号加1(AtomicStampedReference实现类)。
如果是基本数据类型无所谓,但是修改的是对象,引用类型就有问题了。
可重入锁:
synchronized和Lock都是可重入锁,也叫递归锁。
一把锁可以重复调用,直到栈溢出,所以是可重入
public synchronized void add() {
add();
}
public static void main(String[] args) {
new SynThread().add();
}
synchronized
:
是互斥锁、悲观锁、重量级锁(满足原子性)
使用synchronized修饰,表示在一个时刻只能有一个线程访问;
局限性:会导致程序执行效率低。
底层实现:
1. jdk早期是重量级锁,有操作系统OS调用;
2. 后期:锁升级;
sync(obj):第一个去访问锁的线程,先在object头部记录这个线程id(偏向锁);
如果线程争用,发现头部的某两位,id不相等;升级位自旋锁(调用者一直循环在哪里,看该锁释放已经释放了);
自旋10次以后,还没有得到这把锁,才升级位重量级锁。
3. 执行时间短(加锁代码):
线程数少,用自旋;线程数多,同系统锁。
public static void main(String[] args) {
int i = 0;
Object o = new Object();
synchronized (o) {
i++;
}
}
1.
非静态同步锁:
sychronzied(object) 不能用String,常量,Integer,Long,基本数据类型;
可以是this,也可以是其他对象,
new Ojbect();
(要求多个线程共享的同一个对象)。
private int num = 10;
public synchronized void m() {
num--;
System.out.println(Thread.currentThread().getName() + " num=" + num);
}
public void m1() {
synchronized (this) {
num--;
System.out.println(Thread.currentThread().getName() + " num=" + num);
}
}
2.
静态同步锁:
public class StaticSynchronized {
private static int count = 10;
public synchronized static void m() {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void m1() {
synchronized (StaticSynchronized.class) {
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
Lock
接口ReentrantLock
:
public class T {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock();
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000L);
System.out.println(Thread.currentThread().getName() + " Thread: " + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void m2() {
boolean locked = false;
try {
locked = lock.tryLock(5L, TimeUnit.SECONDS);
System.out.println(Thread.currentThread().getName() +
"Thread-TryLock " + locked);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (locked) {
lock.unlock();
}
}
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m1, "t1").start();
new Thread(t::m2, "t2").start();
}
}
public class SaleTicket {
public static void main(String[] args) {
LockTicket lockTicket = new LockTicket();
new Thread(lockTicket, "窗口1").start();
new Thread(lockTicket, "窗口2").start();
new Thread(lockTicket, "窗口3").start();
}
}
class LockTicket implements Runnable {
private static int ticketSum = 100;
private Boolean flag = true;
private final Lock lock = new ReentrantLock(true);
public void setFlag(Boolean flag) {
this.flag = flag;
}
public Boolean getFlag() {
return flag;
}
private void sale() {
lock.lock();
try {
if (ticketSum <= 0) {
System.out.println("票已卖完,售票结束");
setFlag(false);
return;
}
System.out.println(Thread.currentThread().getName() +
"当前窗口卖一张票,剩余票数:" + (ticketSum--));
} finally {
lock.unlock();
}
}
@Override
public void run() {
while (flag) {
sale();
}
}
}
Lock synchronized区别?
1.
Lock接口:
发生异常时,若没有主动通过unLock()去释放锁,会造成死锁,需要在final块中释放锁;
有公平锁、非公平锁;
可以让等待锁的线程响应中断:lock interupptibly;
尝试获取锁。
2.
synchronized是关键字,内置语言实现;
发生异常时,会自动释放占有线程锁,不会导致死锁;
无公平锁;
不能中断响应,会让等待的线程一直等待下去。
卖票超卖案例:
多个线程同时对一个数据进行操作时,会出现并发的问题,我们应该对同步操作加锁。
public class SynSaleTicket {
public static void main(String[] args) {
SynSale synSale = new SynSale();
new Thread(synSale, "窗口1").start();
new Thread(synSale, "窗口2").start();
new Thread(synSale, "窗口3").start();
}
}
class SynSale implements Runnable {
private static int ticketSum = 100;
private Boolean flag = true;
public void setFlag(Boolean flag) {
this.flag = flag;
}
public Boolean getFlag() {
return flag;
}
private synchronized void sale() {
if (ticketSum <= 0) {
System.out.println("票已卖完,售票结束");
setFlag(false);
return;
}
System.out.println(Thread.currentThread().getName() +
"当前窗口卖一张票,剩余票数:" + (ticketSum--));
}
private void sale1() {
synchronized (this){
if (ticketSum <= 0) {
System.out.println("票已卖完,售票结束");
setFlag(false);
return;
}
System.out.println(Thread.currentThread().getName() +
"当前窗口卖一张票,剩余票数:" + (ticketSum--));
}
}
@Override
public void run() {
while (flag) {
sale1();
}
}
}
线程之间交替执行:
需求:
两个线程一个打印ABCD,一个打印1234,结果交替执行1A2B3C4D;
public class SynAlternate {
public static void main(String[] args) {
new SynAlternate().doRun();
}
Object o = new Object();
char[] a1 = "1234567".toCharArray();
char[] aA = "ABCDEFG".toCharArray();
public void doRun() {
new Thread(() -> {
synchronized (o) {
for (char c : a1) {
System.out.print(c);
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "a1").start();
new Thread(() -> {
synchronized (o) {
for (char c : aA) {
System.out.print(c);
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "aA").start();
}
}
public class LockAlternate {
public static void main(String[] args) {
new LockAlternate().doRun();
}
char[] a1 = "1234567".toCharArray();
char[] aA = "ABCDEFG".toCharArray();
Lock lock = new ReentrantLock();
Condition conditionT1 = lock.newCondition();
Condition conditionT2 = lock.newCondition();
public void doRun() {
new Thread(() -> {
lock.lock();
try {
for (char c : a1) {
System.out.print(c);
conditionT2.signal();
conditionT1.await();
}
conditionT2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "a1").start();
new Thread(() -> {
lock.lock();
try {
for (char c : aA) {
System.out.print(c);
conditionT1.signal();
conditionT2.await();
}
conditionT1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "aA").start();
}
}
public class LockSupportTest {
static Thread t1 = null;
static Thread t2 = null;
char[] a1 = "1234567".toCharArray();
char[] aA = "ABCDEFG".toCharArray();
public void doRun() {
t1 = new Thread(() -> {
for (char c : a1) {
System.out.print(c);
LockSupport.unpark(t2);
LockSupport.park();
}
});
t2=new Thread(()->{
for (char c : aA) {
LockSupport.park();
System.out.print(c);
LockSupport.unpark(t1);
}
});
t1.start();
t2.start();
}
public static void main(String[] args) {
LockSupportTest lockSupportTest = new LockSupportTest();
lockSupportTest.doRun();
}
}
虚假唤醒问题wait,await
:
1.
使用wait()方法,必须使用while,不能用if;
在哪等待,就在哪唤醒,之前的if是不会进行判断的,所以不能用if,由于多次同一个线程执行跳过了if。
while (number != 0) {
this.wait();
}
while (number != 0) {
condition.await();
}
线程通信:
三步骤:
判断
干活
通知
public class SynTalk {
private int number = 0;
public synchronized void incr() throws InterruptedException {
while (number != 0) {
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()
+ ",当前线程加一,为:" + number);
this.notify();
}
public synchronized void decr() throws InterruptedException {
while (number != 1) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()
+ ",当前线程减一,为:" + number);
this.notify();
}
public static void main(String[] args) {
SynTalk synTalk = new SynTalk();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
synTalk.incr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
synTalk.decr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
public class LockTalk {
private int number = 0;
private final ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void incr() {
try {
lock.lock();
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()
+ ",当前线程加一,为:" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decr() {
try {
lock.lock();
while (number != 1) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()
+ ",当前线程减一,为:" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
LockTalk lockTalk = new LockTalk();
new Thread(()->{
for (int i = 0; i < 10; i++) {
lockTalk.incr();
}
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
lockTalk.decr();
}
}).start();
}
}