什么是程序
程序是一个落到磁盘上的一个可执行文件,鼠标双击这个文件可以使这个文件运行。比如QQ,每次双击的时候都可以打开一个新的登陆页面,这些登陆页面就相当于一个一个进程。
什么是线程
专业角度来讲,进程叫做资源分配的基本单位,线程在进程的内部,叫做调度执行的基本单位,多个线程共享同一个进程里面的资源。
单核cup设定多线程有意义吗?
答:有意义。因为有一些线程在执行过程中 可能在等在资源 或者 sleep,此时等待的过程中是不消耗cpu算力的,切换到其他线程执行的时候使用cpu算力,才能更好的压榨cpu。
工作线程数是不是设置的越大越好
并不是,因为线程之间切换也是需要消耗资源的,
线程池中的线程数量设置多少合适?
线程数= 核心线程数 * 期望使用率 * (1+等待时间/计算时间)
//获取cpu核心数
int cpuCoreCount = Runtime.getRuntime().availableProcessors();
//计算一个等待时间和计算时间比率各占一半, 期望cpu利用率达到90%的线程数量
int threadCount=cpuCoreCount*90/100*(1+50/50);
如何知道等待时间 和 计算时间? 有工具
线程状态转换
NEW、RUNNABLE、TERMINATED 状态演示
Thread t1=new Thread(() -> {
System.out.println("2.t1执行start方法后,此时的状态为:"+Thread.currentThread().getState());
for (int i = 0; i < 3; i++) {
SleepHelper.sleepS(1);
System.out.println(i+"");
}
System.out.println();
});
System.out.println("1.t1线程此时还没有调用start方法,状态为:"+t1.getState());
t1.start();
//等待t1线程执行完毕
t1.join();
System.out.println("3. t1线程执行完毕,线程状态为:"+t1.getState());
WAITING、TIMED_WAITING 状态演示
Thread t2=new Thread(() -> {
//线程阻塞,此时线程的状态应该为 waiting
LockSupport.park();
System.out.println("t2线程正在执行...");
SleepHelper.sleepS(5);
});
t2.start();
//主线程休眠一秒,保证t2线程已经启动
TimeUnit.SECONDS.sleep(1);
System.out.println("调用LockSupport.park()方法线程被挂起后,t2线程状态为:"+ t2.getState());
//放开线程,会走sleep方法休眠5秒,此时的线程的状态应该为 time waiting
LockSupport.unpark(t2);
//休眠一秒,确保t2线程被放开
TimeUnit.SECONDS.sleep(1);
System.out.println("调用SleepHelper.sleepS(5)方法线程休眠,t2线程状态为:"+ t2.getState());
BLOCKED状态的演示
Object o=new Object();
Thread t3=new Thread(() -> {
synchronized (o){
System.out.println("t3获取到了o锁");
}
});
new Thread(() -> {
//创建线程休眠5秒钟并启动拿到o锁,再启动t3线程 并查看状态,此时由于o锁被当前线程获取所以无法进入sync代码块,此时线程的状态为blocking
synchronized (o){
SleepHelper.sleepS(5);
}
}).start();
TimeUnit.SECONDS.sleep(1);
t3.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("t3无法获取到o锁被阻塞,此时t3线程的状态为:"+t3.getState());
总结: 线程阻塞状态,只有
synchronized
关键字才会使线程状态进入BLOCKED状态,其他的阻塞方式 例如LockSupport.park()
、ReentrantLock的lock方法
都是WAITING状态。
线程的打断
线程的“打断”,并非是字面意义上的 线程执行过程中被中断,而是给当前线程打标记(标志位),至于获取到这个标志位如何使用?可以自定义代码逻辑。
jdk提供了三个关于打断线程的方法:
interrupt()
打断某个线程(设置标志位)isInterrupt()
查询某个线程是否被打断(查询标志位)static interrupted()
查询当前线程是否被打断过,并重置打断标志位(false)
代码示例
Thread t1=new Thread(() -> {
while (true){
if (Thread.currentThread().isInterrupted()){
System.out.println("当前线程被标识打断标记");
System.out.println(Thread.currentThread().isInterrupted());
//如果被标记了打断标识,则跳出循环,结束线程
break;
}else {
System.out.println("当前线程没有标识打断标记");
}
}
//判断是否有打断标识,并重置打断标识,此方法为一个静态方法
if (Thread.interrupted()){
System.out.println("重置打断标识:"+Thread.currentThread().isInterrupted());
}
});
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt();
interrupt配合sleep()、wait()、join()使用
在使用线程方法sleep()
、wait()
、join()
的时候,会捕获InterruptedException
异常,当线程被标记打断标识后,则会捕获该异常,处理逻辑由程序员自定义编写。示例代码如下:
Thread t1=new Thread(() -> {
while (true){
try {
Thread.sleep(1000);
System.out.println("1");
} catch (InterruptedException e) { //catch的是 InterruptedException一场
// e.printStackTrace();
System.out.println("当前线程设置 打断标识");
//在捕获异常后,打断标识会被重置,已防止其他人再次打断无法捕获
System.out.println(Thread.currentThread().isInterrupted());
}
}
});
t1.start();
//只打断一次,就会被恢复
t1.interrupt();
SleepHelper.sleepS(20);
多个线程 在使用
synchronized
或者ReentrantLock
的locak()
同步代码块争抢锁的时候,并不会受interrupt
的影响。如果希望受interrupt
的影响,可以使用ReentrantLock
的lockInterruptibly()
来实现。代码示例如下:
Lock myLock=new ReentrantLock();
Thread t1=new Thread(() -> {
try {
myLock.lock();
System.out.println("t1开始执行");
SleepHelper.sleepS(10);
} catch (Exception e) {
e.printStackTrace();
} finally {
myLock.unlock();
}
System.out.println("t1线程执行完毕");
});
t1.start();
SleepHelper.sleepS(1);
Thread t2=new Thread(() -> {
try {
//t1获得锁之后会休眠10秒,因此t2无法获取到锁,被标记打断标识后会抛出一场
myLock.lockInterruptibly();
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("t2线程被标记打断标识");
}finally {
myLock.unlock();
}
System.out.println("t2线程执行完毕");
});
t2.start();
SleepHelper.sleepS(1);
t2.interrupt();
thread.sleep()抛出中断异常之后,会清空中断位。
优雅的结束线程
- 自然结束。
- 线程的
stop方法
。不建议使用,原因:太过粗暴,在锁同步代码块的时候,可能执行一半,线程被终止,导致数据不一致。 - 线程的
suspend方法
和 线程的resume方法
(暂停和恢复)。不建议使用,原因:同样存在数据不一致的问题。 - 使用
volatile关键字
修饰公共静态变量 ,控制while循环。有局限性,如果循环中存在wait
之类的阻塞方法,就不能及时的结束线程。 - 使用
interrupt方法
设置中断标志位,在线程内部中断是否有标识,此方法可以使sleep
、wait
、join
方法抛出InterruptedException
异常。