文章目录
多线程(一)
多任务: 边吃饭边看手机、边走路边听歌、边上wc边玩手机
这些看起来Q是多个任务都在做,其实本质上我们的大脑在同一时间依旧只做了一件事;
多线程: 原来只有 1 条道路,慢慢车多了,道路堵塞,通过效率极低;为了提高效率,在道路上加了多个车道,提高效率;
打王者,原来只有一个账号,后来开通了多个账号,大家可以一起玩;
进程与线程:
1. 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念;
2. 进程 是执行程序的一次执行过程,他是一个动态的概念,是系统资源分配的单位;
3. 通常在一个进程中可以包含若干个 线程 ,(一个进程中 至少 包含 一个线程,不然没有存在的意义),线程是调度和执行的单位
注意:很多线程是模拟出来的,真正的多线程是指有多个cpu,即多核 ,如服务器 。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉;
01 线程创建
Thread、Runnable、Callable
三种创建方式
Thread class ===> 继承 Thread 类 (重点)
Runnable 接口 ===> 实现 Runnable 接口 (重点)
callable 接口 ====> 实现 Callable 接口 (了解)
继承 Thread 类
步骤:
- 自定义线程继承 Thread 类
- 重写 run() 方法,编写线程执行体
- 创建线程对象,调用 start() 方法启动线程
//创建线程方式一:继承 Thread 类,重写 run()方法,调用 start 开启线程
public class TestThread extends Thread{
@Override
public void run() {
//run 方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("多线程run方法执行!!!!");
}
}
//main线程,主线程
public static void main(String[] args) {
//创建一个线程对象
TestThread testThread =new TestThread();
//调用start()方法开启线程
testThread.start();
for (int i = 0; i < 200; i++) {
System.out.println("这是main方法线程!!!!!!!");
}
}
}
总结:线程开启不一定立即执行,是由 cpu 来调度安排执行
实现 Runnable 接口
步骤:
- 定义 MyRunnable 类实现 Runnable 接口
- 实现 run() 方法,编写线程执行体
- 创建线程对象,调用 start() 方法启动线程
//创建线程方式2 ;实现 runnablle 接口,重写 run方法,执行线程需要当如 runnable 实现类
public class TestThread2 implements Runnable {
//run方法线程体
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("这是run方法!!!!" + i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
TestThread2 testThread2 = new TestThread2();
//创建线程对象,通过线程对象来开启线程;代理模式(静态代理)
// Thread thread=new Thread(testThread2);
// thread.start();
new Thread(testThread2).start();
for (int i = 0; i < 1000; i++) {
System.out.println("这是 main 方法!!!!" + i);
}
}
}
注意:因为Java是单继承模式,推荐使用 runnable 接口实现线程
两者对比:
-
继承 Thread 类
- 子类继承 Thread 类
2. 启动:子类对象 . start()
3. 不建议;避免 oop 单继承局限性
- 子类继承 Thread 类
-
实现 Runnable 接口
- 实现Runnable 接口
2. 启动:传入目标对象(runnable接口实现类)+ Thread对象 . start()
3. 推荐:避免单继承局限性,方便同一个对象被多个线程使用,灵活;
- 实现Runnable 接口
推荐runnable 接口原因:
Java是单继承,一个子类只能有一个父类;加入有a、b两个类,且a、b两个类相互之间没有关联,现在有一个c类,想同时继承a、b类,是无法实现的;
Java中一个类可以实现多个接口,所以上面这种情况,如果是接口,那么 c 就可以同时实现 a、b 类;所以多线程推荐使用实现 Runnable 接口方式
案例(龟兔赛跑)
//多线程同时操作同一个对象;龟兔赛跑
public class TestThread3 implements Runnable {
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//让乌龟获胜
if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断是否结束比赛
boolean flag = gameOver(i);
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName() + "===>跑了" + i + "步");
}
}
//判断是否完成比赛
private boolean gameOver(int steps) {
if (winner != null) {
//已经存在胜利者
return true;
} else {
if (steps >= 100) {
winner = Thread.currentThread().getName();
System.out.println("冠军是: " + winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
TestThread3 testThread3 = new TestThread3();
new Thread(testThread3, "兔子").start();
new Thread(testThread3, "乌龟").start();
}
}
实现 Callable 接口
步骤:
- 实现 callable 接口,需要返回值类型;
- 重写 call 方法 ,需要抛出异常;
- 创建目标对象;
- 创建执行服务:
Executor Service ser = Executor . newFixedThreadPool (1);
- 提交执行:
Future<Boolean> result1 =ser.submit(t1);
- 获取结果:
boolean r1 = result1.get();
- 关闭服务:
ser.shutdownNow();
//线程创建方式三:实现 callable 接口
public class TestCallable implements Callable<Boolean> {
private String name;
public TestCallable(String name) {
this.name = name;
}
@Override
public Boolean call() {
for (int i = 0; i < 20; i++) {
System.out.println("call方法执行!!!!" + name);
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("t1");
TestCallable t2 = new TestCallable("t2");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
//获取结果
boolean res1 = r1.get();
boolean res2 = r2.get();
//关闭服务
ser.shutdownNow();
}
}
02 静态代理
静态代理可以理解就是一种 委托 的模式;比如你需要结婚了,你(真实角色)委托婚庆公司 布置一些事情;
你自己 和 婚庆公司都需要做的事情 :你 结婚 ---- 你(真实角色)与 新娘结婚;婚庆公司 结婚----接受你的委托,布置一些场地事宜;
二者都需要同时做一件事情(实现同一个接口) ==== “结婚”;
/**
* 静态代理:
* 1.真实对象和代理对象都要同时实现同一个接口
* 2.代理对象必须要代理真实角色
*
* 好处:
* 1.代理对象可以做很多其他的辅助事情
* 2.真实对象可以专注做自己的事情
*/
public class StaticProxy {
public static void main(String[] args) {
WeddingCompany weddingCompany=new WeddingCompany(new You());
weddingCompany.HappyMarry();
}
}
interface Marry {
void HappyMarry();
}
//真实角色,你去结婚
class You implements Marry {
@Override
public void HappyMarry() {
System.out.println("我很开心!要结婚了!");
}
}
//代理角色,帮助你结婚
class WeddingCompany implements Marry{
//代理===》真实目标
private Marry target;
public WeddingCompany(Marry target){
this.target=target;
}
@Override
public void HappyMarry() {
after();
this.target.HappyMarry();//真实对象
befor();
}
private void after(){
System.out.println("结婚之前,婚庆公司布置现场;");
}
private void befor(){
System.out.println("结婚之后,婚庆公司收尾款,打扫现场;");
}
}
03 Lambda 表达式
1.Lambda 简介
λ
希腊字母表中排序第十一位的字母,英文名:Lambda- 避免匿名内部类定义过多的情况
- 属于函数式编程的概念
- JDK8 的一个新特性,可以取代大部分的匿名内部类,可以极大的优化代码结构,尤其是在集合遍历等情况下
- Lambda 规定接口中只能有一个需要被实现的方法;并不是接口中只能有一个方法
语法形式: () -> {}
;
‘ () ’ :描述参数列表;
“ { } ” :描述方法体 ;
“ -> ” : lambda 运算符
语法简化形式:
2. 函数式接口(Functional Interface)
函数式接口是学习 Lambda 表达式的关键所在
函数式接口定义:
//任何接口 ,如果只包含唯一一个抽象方法,那么他就是一个函数式接口
public interface Runnable{
public abstract void run();
}
3.案例:
/**
* 推导lambda表达式
*/
public class TestLambda {
//3.静态内部类
static class Like2 implements ILike {
@Override
public void lambda() {
System.out.println("这是 lambda 2 内部类");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
//内部类
like = new Like2();
like.lambda();
//4.局部内部类
class Like3 implements ILike {
@Override
public void lambda() {
System.out.println("这是 lambda 3 局部内部类");
}
}
like = new Like3();
like.lambda();
//5.匿名内部类
like = new ILike() {
@Override
public void lambda() {
System.out.println("这是 lambda 4 匿名内部类");
}
};
like.lambda();
//6. 用 lambda 简化
like = () -> {
System.out.println("这是 lambda 5 用 lambda 简化");
};
like.lambda();
}
}
//1.定义一个函数式接口
interface ILike {
void lambda();
}
//2.实现类
class Like implements ILike {
@Override
public void lambda() {
System.out.println("这是 lambda 实现类");
}
}
这是 lambda 实现类
这是 lambda 2 内部类
这是 lambda 3 局部内部类
这是 lambda 4 匿名内部类
这是 lambda 5 用 lambda 简化
继续简化
// 简化
like = (int a) -> {
System.out.println("这是 lambda 实现类" + a);
};
like.lambda(1);
// 简化1 参数类型
like = (a) -> {
System.out.println("这是 lambda 实现类" + a);
};
like.lambda(2);
// 简化2 括号
like = a -> {
System.out.println("这是 lambda 实现类" + a);
};
like.lambda(3);
// 简化3 花括号
like = a -> System.out.println("这是 lambda 实现类" + a);
like.lambda(4);
简化总结:
/**
* 简化到最后:
* lambda 只能有一行代码的情况下才能简化成一行,如果多行,必须使用代码酷爱包裹
* 前提 接口是函数式接口
* 多个参数也可以去掉 参数类型 ,但是要去掉就要所有的都去掉,用括号包裹
*/
04 线程方法
setPriorith(int newPriority) --更改线程的优先级
static void sleep(long millis) --在指定的毫秒数内让当前正在执行的线程休眠
void join() --等待线程终止
static void yield() --暂停当前正在执行的线程对象,并执行其他线程
void interrupt() --中断线程,别用这个方式
boolean isAlive() --测试线程是否处于活动状态
05 线程停止
- 推荐线程自己停止下来,不推荐使用jdk提供的stop()、destroy()方法 【已废弃】
- 建议使用一个标志位进行终止变量;当 flag = false ,则终止线程运行
public class TestStop implements Runnable{
//1.设置一个标识位
private boolean flag=true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("run....Thread "+i++);
}
}
//2.这只一个公开的方法停止线程,转换标志位
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
TestStop testStop=new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main........"+ i);
if (i==900){
//调用stop方法切换标志位,让线程停止
testStop.stop();
System.out.println("线程停止了!");
}
}
}
}
main........897
main........898
main........899
main........900
线程停止了!
main........901
main........902
main........903
main........904
main........905
main........906
main........907
06 线程休眠
sleep(时间) 指定当前线程阻塞的毫秒数;
sleep存在异常InterruptedException;
sleep时间到达后线程进入就绪状态;
sleep可以模拟网络延时,倒计时等;
每一个对象都有一个锁,sleep不会释放锁;
//模拟网络延时:放大问题的发生性
class TstSleep2 implements Runnable {
//票数
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
//模拟网络延时
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "=====>>拿到了第" + ticketNums-- + "张飘");
}
}
public static void main(String[] args) {
TstSleep2 tstSleep2=new TstSleep2();
new Thread(tstSleep2,"小明").start();
new Thread(tstSleep2,"老师").start();
new Thread(tstSleep2,"黄牛党").start();
}
}
07 线程礼让 - yield
- 礼让线程,让当前正在执行的线程 暂停 ,但不阻塞;
- 将线程从运行状态 转为 就绪状态;
- 让 cpu 重新调度,礼让不一定成功!看cpu心情;
//线程礼让
public class TestyYeld {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield, "a").start();
new Thread(myYield, "b").start();
}
}
class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行!");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName() + "线程停止执行!");
}
}
08 线程强制执行- join
- Join 合并线程,等词线程执行完成后,再执行其他线程,其他线程阻塞;
- 类似插队;
//Join 插队
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("线程vip来了===" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//vip线程
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
//主线程
for (int i = 0; i < 10; i++) {
if (i == 5) {
thread.join();//插队
}
System.out.println("main" + i);
}
}
}
09 线程状态观测 - State
-
线程状态。线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。RUNNABLE
在Java虚拟机中执行的线程处于此状态。BLOCKED
被阻塞等待监视器锁定的线程处于此状态。WAITING
正在等待另一个线程执行特定动作的线程处于此状态。TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。TERMINATED
已退出的线程处于此状态。
一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
public class TestState { public static void main(String[] args) { Thread thread = new Thread(() -> { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("//"); }); //观测状态 Thread.State state = thread.getState(); System.out.println(state);//new //观测启动后 thread.start(); state = thread.getState(); System.out.println(state);//run while (state != Thread.State.TERMINATED) {//只要线程不终止,就一直输出状态 state = thread.getState();//跟新线程状态 System.out.println(state); } } }
10 线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行;
- 线程的优先级用数字表示,范围从1-10 :
Thread.MIN_PRIOROTY =1 ;
Thread.MAX_PRIOROTY =10 ;
Thread.NORM_PRIOROTY=5 ;
- 使用一下方式改变或者获取优先级
getPriority().setPriority(int xx)
//测试线程的优先级
public class TestPrioty {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"---->>" +Thread.currentThread().getPriority());
MyPriority myPriority =new MyPriority();
Thread t1=new Thread(myPriority);
Thread t2=new Thread(myPriority);
Thread t3=new Thread(myPriority);
Thread t4=new Thread(myPriority);
Thread t5=new Thread(myPriority);
Thread t6=new Thread(myPriority);
//设置线程优先级,
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10
t4.start();
t5.setPriority(8);
t5.start();
t6.setPriority(7);
t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->>"+Thread.currentThread().getPriority());
}
}
注意:
- 优先级的设定建议在 start()调度前;
- 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看 cpu 的调度;
11 守护线程(daemon)
- 线程分为 用户现场 和 守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 比如: 后台记录操作日志,监控内存,垃圾回收等待…
package com.lambda;
//测试守护线程
//上帝守护你
public class TestDaemon {
public static void main(String[] args) {
God god=new God();
You you=new You();
Thread thread=new Thread(god);
thread.setDaemon(true);//默认是false表示用户线程,正常的线程都是用户数据...
thread.start();//上帝守护线程
new Thread(you).start();//你(用户线程)启动了...
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保佑着你!");
}
}
}
//你
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你一直开心的活着!");
}
System.out.println("++++++++++++++goodble!world!+++++++++++++++++++++");
}
}