多线程与高并发基础
一、程序、进程、线程、纤程(协程)的定义
程序:可执行文件
进程:程序中资源分配的最小单元
纤程:程序中资源调度的最小单元
携程:用户管理的纤程
你必须知道的几个概念
1、同步&异步
2、并行&并发
并行:真正意义上的同时执行
并发:偏重于多个任务交替执行(cpu是交替执行,所以有高并发一说)
比如登山时,地形险峻,路面湿滑,导游要求走路不看景,看景不足路,这种看会风景走一会的方式就是并发。
但是坐缆车登山观景,登山和看景可以同时进行,这就是并行。
3、临界区
临界区用来表示一种公共资源或者说共享数据,可以被多线程使用。但是每一次只有一个线程使用它,一旦临界区资源被占用,其他线程想要使用这个资源,就必须等待。比如办公室的打印机,
在并行程序中,临界区的资源是保护的对象,如果意外出现打印机同时打印两个打印任务,那么最有可能的结果就是打印出来的文件是损坏的文件。
4、阻塞&非阻塞
一个线程占用了临界资源,那么其他所有需要这个资源的线程都必须在这个临界区中进行等待。等待会导致线程挂起,这种操作就是阻塞。
5、死锁&饥饿&活锁
死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁发生在当一些进程请求其他进程占有的资源而被阻塞时。
死锁发生的四个条件
1、互斥条件:线程对资源的访问是排他性的,如果一个线程对占用了某资源,那么其他线程必须处于等待状态,直到资源被释放。
2、请求和保持条件:线程T1至少已经保持了一个资源R1占用,但又提出对另一个资源R2请求,而此时,资源R2被其他线程T2占用,于是该线程T1也必须等待,但又对自己保持的资源R1不释放。
3、不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程剥夺,只能在使用完以后由自己释放。
4、环路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”,即:{p0,p1,p2,…pn},进程p0(或线程)等待p1占用的资源,p1等待p2占用的资源,pn等待p0占用的资源。(最直观的理解是,p0等待p1占用的资源,而p1而在等待p0占用的资源,于是两个进程就相互等待)
活锁:是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。
活锁不会被阻塞,而是不停检测一个永远不可能为真的条件。除去进程本身持有的资源外,活锁状态的进程会持续耗费宝贵的CPU时间。
关于“死锁与活锁”的比喻:
死锁:迎面开来的汽车A和汽车B过马路,汽车A得到了半条路的资源(满足死锁发生条件1:资源访问是排他性的,我占了路你就不能上来,除非你爬我头上去),汽车B占了汽车A的另外半条路的资源,A想过去必须请求另一半被B占用的道路(死锁发生条件2:必须整条车身的空间才能开过去,我已经占了一半,尼玛另一半的路被B占用了),B若想过去也必须等待A让路,A是辆兰博基尼,B是开奇瑞QQ的屌丝,A素质比较低开窗对B狂骂:快给老子让开,B很生气,你妈逼的,老子就不让(死锁发生条件3:在未使用完资源前,不能被其他线程剥夺),于是两者相互僵持一个都走不了(死锁发生条件4:环路等待条件),而且导致整条道上的后续车辆也走不了。
例如:马路中间有条小桥,只能容纳一辆车经过,桥两头开来两辆车A和B,A比较礼貌,示意B先过,B也比较礼貌,示意A先过,结果两人一直谦让谁也过不去。
饥饿:线程因某种原因(优先级)一直无法获取到所需的资源。例如雏鸟喂食,弱小的一直抢不到食物。
二、单核系统做多线程设计是否有意义
有
纤程分为io密集型和CPU密集型,对于IO密集型纤程,cpu多数时间处于空闲状态,适当的多线程设计可提供cpu利用率,充分压榨cpu性能,提高处理能力。
三、是不是线程数设计越多越好?
不是
cpu只负责操作指令的执行,操作系统负责任务的调度。OS在多个线程间切换会消耗一定的性能,线程数过度会消耗过多性能在线程切换上,反而会降低系统性能。
import java.text.DecimalFormat;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class Test_ContextSwitch {
private static double[] nums=new double[1_0000_0000];
private static Random r=new Random( );
private static DecimalFormat df=new DecimalFormat("0.00");
static {
for (int i = 0; i < nums.length; i++) {
nums[i]=r.nextDouble();
}
}
private static void m1(){
long startTime=System.currentTimeMillis();
double result=0.0;
for (int i = 0; i < nums.length; i++) {
result+=nums[i];
}
long endTime=System.currentTimeMillis();
System.out.println("m1: "+(endTime-startTime)+" result = "+df.format(result));
}
//2个线程实现
static double result1=0.0,result2=0.0,result=0.0;
private static void m2() throws Exception{
Thread t1=new Thread(()->{
for (int i = 0; i < nums.length/2; i++) {
result1+=nums[i];
}
});
Thread t2=new Thread(()->{
for (int i = nums.length/2; i < nums.length; i++) {
result2+=nums[i];
}
});
long startTime=System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
result=result1+result2;
long endTime=System.currentTimeMillis();
System.out.println("m2:"+(endTime-startTime)+" result = "+df.format(result));
}
private static void m3(int number) throws Exception{
final int threadCount;
threadCount=number>0? number:1000;
Thread[] threads=new Thread[threadCount];
double[] results=new double[threadCount];
final int segmentCount=nums.length/threadCount;
CountDownLatch latch=new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
int m=i;
threads[i]=new Thread(()->{
for (int j = m*segmentCount; j < (m+1)*segmentCount&&j<nums.length; j++) {
results[m]+=nums[j];
}
});
latch.countDown();
}
double resultM3=0.0;
long startTime=System.currentTimeMillis();
for (Thread t : threads) {
t.start();
}
latch.await();
for (int i = 0; i < results.length; i++) {
resultM3+=results[i];
}
long endTime=System.currentTimeMillis();
System.out.println("m3:"+(endTime-startTime)+" resultM3 = "+df.format(result));
}
public static void main(String[] args) throws Exception{
m1();//单线程执行
m2();//启动2个线程执行
m3(10000);//自定n个线程执行(int)
}
}
"D:\Program Files\Java\jdk1.8.0_201\bin\java" "...
m1: 1135 result = 50001208.95
m2:2176 result = 50001208.95
m3:2785 resultM3 = 50001208.95
Process finished with exit code 0
四、线程数如何设计才是最合理的?
线程数=cpu核数cpu利用率*[任务执行时间/(任务执行时间-IO等待时间)]
举例:某线程等待时间为50%,期望cpu利用率为50%,cpu核数为32核。则:线程数=32* 0.5*[1/(1-0.5)]=32
五、是不是cpu利用率越高越好?
不是
因为不管在什么环境下运行一个java程序,都会有其他线程存在(比如gc),不可能该线程完全独享cpu资源。而且在实际开发中,我们需要空闲出部分资源防止突发情况。
六、如何确定线程的IO等待时间?
使用profiler工具
七、创建线程的几种方式
1、 直接继承自Thread
2、实现Runnable接口
3、使用Lambda表达式创建(本质和m1是一样的)
4、使用线程池方式实现Callable newCachedThreadPool
5、使用FutureTask实现Callable
6、使用线程池方式,通过Lambda表达式创建
7、使用线程池方式实现Callable newFixedThreadPool
8、使用线程池方式实现Callable newSingleThreadExecutor
9、使用线程池方式实现Callable newScheduledThreadPool
package com.example.mythreadtest;
import java.util.concurrent.*;
/**
* @Auther wangdongdong
* @Date 2021/7/19 上午 10:30
* @Description
* @Verson 1.0
*/
public class CreateThread {
public static void main(String[] args) {
m0(); //直接继承自Thread
m1(); //实现Runnable接口
m2(); //使用Lambda表达式创建(本质和m1是一样的)
m3(); //使用线程池方式实现Callable newCachedThreadPool
m4(); //使用FutureTask实现Callable
m5(); //使用线程池方式,通过Lambda表达式创建
m6(); //使用线程池方式实现Callable newFixedThreadPool
m7(); //使用线程池方式实现Callable newSingleThreadExecutor
m8(); //使用线程池方式实现Callable newScheduledThreadPool
}
直接继承自Thread
public static void m0(){
new myThread().start();
}
//实现Runnable接口
public static void m1(){
new Thread(new myRunable()).start();
}
//使用Lambda表达式创建(本质和m1是一样的)
public static void m2(){
new Thread(()->{
System.out.println("m2: This is my Thread user Lambda!");
}).start();
}
//使用线程池方式实现Callable
public static String m3(){
String ret="";
ExecutorService service=Executors.newCachedThreadPool();
Future<String> f = service.submit(new myCallable());
try {
ret=f.get();
System.out.println("m3: "+ret);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
service.shutdown();
}
return ret;
}
//使用FutureTask实现Callable
/*
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
*/
public static String m4(){
String ret="";
FutureTask<String> ft=new FutureTask<>(new myCallable());
Thread td=new Thread(ft);
td.start();
try {
ret=ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("m4: "+ret);
return ret;
}
//使用线程池方式,通过Lambda表达式创建
public static void m5(){
ExecutorService service=Executors.newCachedThreadPool();
service.execute(()->{
System.out.println("m5: This is my Thread user cachedThreadPool!");
});
service.shutdown();
}
//使用线程池方式,newFixedThreadPool
/*
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,
它不会释放工作线程,还会占用一定的系统资源。
*/
public static String m6(){
String ret="";
ExecutorService service=Executors.newFixedThreadPool(3);
Future<String> f = service.submit(new myCallable());
try {
ret=f.get();
System.out.println("m6: "+ret);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
service.shutdown();
}
return ret;
}
//使用线程池方式,newSingleThreadExecutor
/*
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,
保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。
单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
*/
public static String m7(){
String ret="";
ExecutorService service=Executors.newSingleThreadExecutor();
Future<String> f = service.submit(new myCallable());
try {
ret=f.get();
System.out.println("m7: "+ret);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
service.shutdown();
}
return ret;
}
//使用线程池方式,newScheduleThreadPool
/*
创建一个定长的线程池,而且支持定时的以及周期性的任务执行
*/
public static void m8(){
String ret="";
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println("m8: This is my thread extend Thread,delay 3 seconds !");
}
}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
//延时一秒,没3秒执行一次
System.out.println("m8: delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
}
}
class myThread extends Thread{
@Override
public void run() {
System.out.println("m0: This is my thread extend Thread !");
}
}
class myRunable implements Runnable{
@Override
public void run() {
System.out.println("m1: This is my runable implements Runnable !");
}
}
class myCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "This is my callable implements Callable !";
}
}
"D:\Program Files\Java\jdk1.8.0_201\bin\java" "...
m1: This is my runable implements Runnable !
m0: This is my thread extend Thread !
m2: This is my Thread user Lambda!
m3: This is my callable implements Callable !
m4: This is my callable implements Callable !
m5: This is my Thread user cachedThreadPool!
m6: This is my callable implements Callable !
m7: This is my callable implements Callable !
m8: delay 1 seconds, and excute every 3 seconds
m8: This is my thread extend Thread,delay 3 seconds !
m8: delay 1 seconds, and excute every 3 seconds
Process finished with exit code -1
八、线程的状态
1、new
2、runnable(ready&running)
3、waiting
4、timed waiting
5、blocked
6、terminated
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* 代码打印6种状态
* 1: NEW
* 2: RUNNABLE
* 3: TERMINATED
* 4: WAITING
* 5: TIMED_WAITING
* 6: BLOCKED
*/
public class Test_ThreadStatus {
public static void main(String[] args) throws Exception {
Thread t1=new Thread(()->{
//线程被执行
System.out.println("2: "+Thread.currentThread().getState());
for (int i = 0; i < 3; i++) {
SleepHelper.sleepSeconds(1);
}
});
//线程刚创建,未被执行
System.out.println("1: "+t1.getState());
t1.start();
t1.join();
//线程执行结束
System.out.println("3: "+t1.getState());
//========================================================================
Thread t2=new Thread(()->{
try {
LockSupport.park();//线程等待被唤醒
TimeUnit.SECONDS.sleep(5);
}catch (Exception e){
e.printStackTrace();
}
});
t2.start();
TimeUnit.SECONDS.sleep(1); //保证线程已经被park()
System.out.println("4: "+t2.getState());
LockSupport.unpark(t2);
TimeUnit.SECONDS.sleep(1);//确保1线程已经被1叫醒
System.out.println("5: "+t2.getState());//在执行sleep(5)方法
//========================================================================
final Object o=new Object();
Thread t3=new Thread(()->{
synchronized (o){
SleepHelper.sleepSeconds(1);
}
});
new Thread(()->{
synchronized (o){
SleepHelper.sleepSeconds(5);
}
}).start();//使当前线程先抢占到锁
SleepHelper.sleepSeconds(1);
t3.start();
SleepHelper.sleepSeconds(1);
System.out.println("6: "+t3.getState());//处于阻塞状态
}
}
class SleepHelper{
//避免代码里多次try、catch
public static void sleepSeconds(int seconds){
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
注:使用synchronized会进入阻塞状态(block),使用锁会进入等待(waiting)状态
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
public class Test_ThreadStatus {
public static void main(String[] args) throws Exception {
//注:使用synchronized会进入阻塞状态(block),使用锁会进入等待(waiting)状态
final Lock lock=new ReentrantLock();
Thread t4=new Thread(()->{
lock.lock();
SleepHelper.sleepSeconds(1);
lock.unlock();
});
new Thread(()->{
lock.lock();
SleepHelper.sleepSeconds(5);
lock.unlock();
}).start();
SleepHelper.sleepSeconds(1);
t4.start();
SleepHelper.sleepSeconds(1);
System.out.println("7:"+t4.getState());//结果输出 7:WAITING
}
}
class SleepHelper{
//避免代码里多次try、catch
public static void sleepSeconds(int seconds){
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}