JAVA多线程
1.并行与并发
并发:同一个CPU处理多个线程,哪个线程获取到CPU的时间片,则执行该线程,即CPU的轮询调度。
并行:多个CPU同时处理多个线程。现在的多核心CPU可以做到真正的并发
2.同步和异步
同步:调用方必须等待响应方执行完毕才返回(A接口调用B接口,A等待B接口的相应结果
使用场景:在编排的流程中,必须等待拿到响应结果才能去做下一步操作,且在实时链路中相互之间有串联或关联数据的
异步:非阻塞式调用,立即返回,调用方无需等待响应方返回实际结果,响应方会通过状态、通知或回调来告知调用方
使用场景:主线程中提交耗时任务到线程池,然后通过Feture来异步获取任务执行结果,这里也可以由异步任务发消息等途径来通 知主线程。注意:主线程会一直等待(阻塞)future.get()的结果,直到结果返回
3.线程的5种状态
NEW:线程创建但是还没有调用start()方法
RUNNABLE :可运行线程的状态,线程已经在JVM虚拟机中执行,但是可能需要等待操作系统资源,如处理器。RUNNABLE包括RUNNING和READY
BLOCKED :阻塞的线程意味着正在等待监视器锁,来进入或重入synchronized代码块或者方法
WAITING :等待状态,需要其他线程中断或通知来唤醒
TIMED_WAITING :定时等待状态,在指定等待时间后返回,或提前被其他线程中断或通知返回
TERMINATED :终止线程的线程状态,线程已执行完成
Thread.yield():线程让步,使用该方法的线程会让出cpu时间片,然后和其它线程一起去竞争cpu时间片
Thread.sleep():线程休眠,线程让出cpu时间片,时间到了之后cpu会继续执行该线程,注意:cpu不会释放当前线程所占有的锁
Thread.join():等待该线程死亡/终止,当前线程会等待调用该方法的线程执行完毕后才能继续执行,threadA.join(),threadB.join()
Object.wait():线程等待,调用该方法的线程必须先持有锁,例如在synchronized代码块内,wait() 方法会释放锁,必须调用notify或者nofityAll来唤醒,涉及到线程之间的通信
4.死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,产生原因:两个及以上的线程,抢占2把及以上的锁,抢占锁的顺序不一致
死锁避免:
1.不使用锁,不使用2把及以上的锁
2.必须使用2把及以上锁的时候,确保在整个应用程序中对获取锁的顺序是一致的
3.尝试获取具有超时释放的锁,例如Lock中的tryLock来获取锁
4.当发生了Java-level的锁时,重启程序来干掉进程/线程
5.Demo
Thread.sleep使用:
package com.s2020;
/**
*
* @author chenw
* 2020/02/08
*
*/
public class SleepDemo {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
System.out.println(Thread.currentThread().getName()+"开始休眠");
try {
//线程休眠3s
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"结束休眠");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}) ;
Thread thread2 = new Thread(()->{
System.out.println(Thread.currentThread().getName()+"开始休眠");
try {
//线程休眠3s
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"结束休眠");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}) ;
thread1.start();
thread2.start();
}
}
运行结果:
Thread-1开始休眠
Thread-0开始休眠
Thread-0结束休眠
Thread-1结束休眠
Thread.join
package com.s2020;
/**
*
* @author chenw
* 2020/02/08
*/
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = createThread(100, "线程1");
Thread thread2 = createThread(200, "线程2");
thread1.start();
thread2.start();
//thread1运行结束主线程才继续执行下面代码
thread1.join();
//thread2运行结束主线程才继续执行下面代码
thread2.join();
System.out.println("主线程运行结束...");
}
public static Thread createThread(int number,String threadName) {
return new Thread(()->{
int sum = 0;
for(int i=0;i<=number;i++) {
sum = sum+i;
}
try {
//睡眠5s方便测试
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"的计算结果:"+sum);
},threadName);
}
}
运行结果
线程2的计算结果:20100
线程1的计算结果:5050
主线程运行结束...
Thread.yield()
package com.s2020;
/**
*
* @author chenw
* 2020/02/08
*
*/
public class YieldDemo {
public static void main(String[] args) {
new Thread(()->{
for(int i=0;i<10;i++) {
if(i==5) {
//线程让步,让出当前cpu的时间片,和其它线程一起去竞争cpu的时间片
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
},"线程1" ).start();
}
}
结果:
线程1:0
线程1:1
线程1:2
线程1:3
线程1:4
线程1:5
线程1:6
线程1:7
线程1:8
线程1:9
synchronized 同步代码块
演示2个窗口的售票,同步代码块,线程获取对象的锁(锁对象)和获取类的锁
package com.s2020;
/**
*
* @author chenw
* 2020/02/08
*
*/
public class SynchronizedBlock {
private static Object obj = new Object();
private int window1 = 0; //窗口1的票数
private int window2 = 0; //窗口2的票数
private int count = 10000; //总票数
public static void main(String[] args) throws InterruptedException {
SynchronizedBlock synchronizedBlock = new SynchronizedBlock();
new Thread(()->{
while(synchronizedBlock.count>0) {
synchronizedBlock.sellTicket2();
//二选一
//synchronizedBlock.sellTicket();
}
},"窗口1").start();
new Thread(()->{
while(synchronizedBlock.count>0) {
synchronizedBlock.sellTicket2();
//二选1
//synchronizedBlock.sellTicket();
}
},"窗口2").start();
//睡眠3s,方便观察
Thread.sleep(3000);
System.out.println("窗口1卖了:"+synchronizedBlock.window1+"张票");
System.out.println("窗口2卖了:"+synchronizedBlock.window2+"张票");
}
public void sellTicket() {
//多个线程去竞争obj对象的锁,获得对象的锁的线程执行同步代码块的代码,
//同一时刻只能有一个线程获得对象的锁,同步锁/非公平锁/可重入锁
synchronized (obj) {
if(count>0) {
count--;
if(Thread.currentThread().getName().equals("窗口1")) {
window1++;
}else {
window2++;
}
}
}
}
public void sellTicket2() {
//多个线程获取类的锁
//同一时刻只能有一个线程获得对象的锁,同步锁/非公平锁/可重入锁
synchronized (SynchronizedBlock.class) {
if(count>0) {
count--;
if(Thread.currentThread().getName().equals("窗口1")) {
window1++;
}else {
window2++;
}
}
}
}
}
窗口1卖了:6068张票
窗口2卖了:3932张票
synchronized同步方法(静态方法属于类,非静态通过对象调用,但是要同一个对象才有作用):
package com.s2020;
/**
*
* @author chenw
* 2020/02/08
*
*/
public class SellTicket2 {
private static int ticketCount = 10000;
private static int threadFirstSellCount;
private static int threadSecondSellCount;
public static void main(String[] args) throws InterruptedException {
sell();
}
public static void sell() throws InterruptedException {
Thread thread1 = new Thread(()-> {
while(ticketCount>0) {
sellCount();
}
},"窗口1");
Thread thread2 = new Thread(()-> {
while(ticketCount>0) {
sellCount();
}
},"窗口2");
thread2.start();
thread1.start();
Thread.sleep(3000);
System.out.println("窗口1卖了:"+threadFirstSellCount);
System.out.println("窗口2卖了:"+threadSecondSellCount);
}
//同步方法,静态的属于类,也可以是非静态通过对象调用
private static synchronized void sellCount() {
if(ticketCount>0) {
ticketCount--;
if(Thread.currentThread().getName().equals("窗口1")) {
threadFirstSellCount++;
}else {
threadSecondSellCount++;
}
}
}
}
窗口1卖了:5119
窗口2卖了:4881
线程池+Callable接口演示wait(),nofityAll和notify
Object.wait()和notifyAll(),notify():线程的等待和唤醒,在调用wait方法前必须先获取对象的锁。wait必须通过nofity或者notifyAll来唤醒,线程被唤醒后去获取cpu的时间片,获取直到继续执行接下的代码。
两个线程打印A1B2C3…Z26的例子
package com.s2020;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
*
* @author chenw
* 2020/02/08
*
*/
public class PrintDemo {
private static Object obj = new Object();
public static void main(String[] args) throws InterruptedException, ExecutionException {
//线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
/**
* Future维护线程的执行结果,异步调用,可以通过future.get()方法获取线程执行的结果
*
*/
Future<String> future = executorService.submit(new Callable<String>() {
//重写Callable接口的call方法
public String call() throws Exception {
int i = 1;
//线程获取对象的锁,得到锁则执行,否则等待
synchronized (obj) {
while(i<=26) {
//唤醒所有在队列中等待的线程
obj.notifyAll();
System.out.print(i);
i++;
//线程进入等待状态
obj.wait();
}
obj.notifyAll();
}
System.out.println();
return Thread.currentThread().getName()+"执行完毕";
}
});
Future<String> future2 = executorService.submit(new Callable<String>() {
public String call() throws Exception {
char letter = 'A';
synchronized (obj) {
while(letter<='Z') {
obj.notifyAll();
System.out.print(letter);
letter++;
obj.wait();
}
obj.notifyAll();
}
System.out.println();
return Thread.currentThread().getName()+"执行完毕";
}
});
//为了方便观察结果
Thread.sleep(3000);
//future.get()方法获取Callable的call方法返回的结果,
//在结果没有返回之前主线程会一直等待(阻塞),直到结果返回在执行下一行
System.out.println(future.get());
System.out.println(future2.get());
}
}
运行结果:
1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z
pool-1-thread-1执行完毕
pool-1-thread-2执行完毕
ReentrantLock
ReentrantLock 和synchronized 都是可重入锁,独占锁
synchronized是非公平锁
ReentranLock(true 公平锁/false 非公平锁);默认为非公平锁
区别:
synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活
ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂
ReentrantLock和synchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;
而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁
package com.s2020;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class PrintReentrantLockDemo {
//可重入锁,默认为非公平锁
private static ReentrantLock lock = new ReentrantLock();
//线程通信
private static Condition condition = lock.newCondition();
public static void main(String[] args) {
print();
}
public static void print() {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(()->{
lock.lock();
try {
for(int i=1;i<27;i++) {
//唤醒在等待的线程,类似于Object.notifyAll()
condition.signal();
System.out.print(i);
//进入等待状态,类似于Object.wait()
condition.await();
}
} catch (Exception e) {
// TODO: handle exception
} finally {
condition.signal();
//释放锁,获取锁的次数和释放锁的次数必须一致
lock.unlock();
}
});
executorService.submit(()->{
lock.lock();
try {
for(char a='A';a<='Z';a++) {
condition.signal();
System.out.print(a);
condition.await();
}
} catch (Exception e) {
// TODO: handle exception
} finally {
condition.signal();
lock.unlock();
}
});
}
}
运行结果:
1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z