目录
线程相关概念
进程:
-
进程是指运行中的程序,比如QQ 就是启动了一个进程,操作系统就会为该进程分配内存空间,当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间
-
进程是程序的一次执行过程,或是 正在运行的一个程序 是动态进程:有它自身的产生,存在和消亡的过程
什么是线程
-
线程是由进程创建的 是进程的一个实体(执行路径)
-
一个进程可以拥有多个线程
相关概念:
-
单线程 :同一个时刻 只允许执行一个线程
-
多线程:同一个时刻可以执行多个线程 比如 一个QQ程序 可以同时开启多个聊天程序 一个迅雷 可以同时下载多个文件
-
并发: 同一个时刻 多个任务交替执行 造成一种"貌似同行"的错觉 简单来说 单核CPU实现的多任务就是并发;
-
并行:同一个时刻 多个任务同时执行, 多核CPU可以实现并行
线程的基本使用
创建线程的三种方式
继承Thread类 重写run方法
实现Runnable接口 重写run方法
实现Callable接口 重写call方法
在idea下面的Terminal中输入jconsole 进入java监视和管理控制台
start() 启动线程
案例1 继承Thread类
//演示通过继承Thread 类创建线程 public static void main(String[] args) { //创建Cat对象 可以当做线程使用 Cat cat = new Cat(); cat.start();//启动线程---> 会执行Cat 的run方法 //说明当main线程启动一个子线程Thread-0 主线程不会阻塞 会继续执行 //这时说明 主线程和子线程是交替执行 for (int i = 0; i < 10; i++) { System.out.println("主线程"+i); //让主线程休眠1秒 Thread.sleep(1000); } } } //当一个类继承了Thread 类 该类就可以当做线程使用 // 我们会重写run方法 写上自己的业务逻辑 //run Thead 类实现了 Runnable 接口的 run方法 /* @Override public void run() { if (target != null) { target.run(); } } */ /*源码解读 1)public synchronized void start(){ start0(); } 2) Start0 是本地方法 是jvm调用 底层是c/c++实现 真正实现多线程的效果 是start0() 而不是run方法 private native void start0(); */ class Cat extends Thread{ @Override public void run() {// 重写run方法 写上自己的业务逻辑 int a = 1; while (true) { //该线程 每隔一秒 在控制台输出 我是小猫 System.out.println("我是小猫咪" + a++); // 让该线程休眠1秒 try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } if(a == 10){//当 a = 10 退出循环 线程也就退出 break; } } } }
案例2 实现Runnable 接口
-
java 是单继承的 在某些情况下一个类可能已将继承了某个父类 这时在用继承Thread类方法来创建线程显然是不可能了
-
java设计者提供了另外一种方式创建线程,就是通过实现Runnable接口来创建线程
//通过实现接口Runnable 来创建线程 public static void main(String[] args) { Dog dog = new Dog(); // dog.start();这里不能调用start //我们来创建 Thread对象 把dog(实现Runnable) 对象放进去 Thread thread = new Thread(dog); thread.start(); } } class Dog implements Runnable{ int a = 1; @Override public void run() { while (true){ System.out.println("小狗在叫"+(a++)+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(a > 10){ break; } } } }
案例3 多线程案例
//在main线程中启动2个子线程 public static void main(String[] args) { t1 t1 = new t1(); t2 t2 = new t2(); Thread thread1 = new Thread(t1); Thread thread2 = new Thread(t2); thread1.start();//启动第一个线程 thread2.start();//启动第二个线程 } } class t1 implements Runnable{ int num = 0; @Override public void run() { //每隔一秒钟 输出hello while (true) { System.out.println("hello world" + (++num)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (num ==10){ break; } } } } class t2 implements Runnable{ int num = 0; @Override public void run() { //每隔一秒钟 输出hello while (true) { System.out.println("hi" + (++num)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (num==5){ break; } } } }
案例4 实现Callable接口 重写call方法
public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<String> task = new FutureTask<>(new T()); Thread thread = new Thread(task); thread.start(); //get() 获取任务的结果 System.out.println(task.get()); } } class T implements Callable <String>{ @Override public String call(){ for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()); System.out.println("T类方法调用中"); } return "执行完毕"; }
继承 Thread 和实现Runnable 的区别
-
从java的设计来看 两者本质上没有区别 从jdk帮助文档我们可以看到 Thread类本身就实现了Runnable接口
-
实现runnable接口方式更加适合多个线程共享一个资源的情况 并且避免了单继承限制
线程常用方法
start() //启动线程 Thread.sleep(100)//线程休眠 (100)表示休眠100毫秒 线程的静态方法 Thread.currentThread().getName() //获取线程名 在idea下面的Terminal中输入jconsole 进入java监视和管理控制台 SetName():// 设置线程名称 使其参数 name 相同 getName()// 返回该线程的名称 run // 调用线程对象 run方法 setPriority(number) // 更改线程的优先级 getPriority() // 回去线程的优先级 interrupt() // 中段线程 但并没有真正意义上的结束线程 一般用于终止休眠的线程 yield() :// 线程礼让 暂停自身的线程 让给优先级相同或更高的线程[存在失败几率] join()://线程插队 插队的线程一旦插队成功 先执行完插队的线程所有任务 isDaemon() // 是否是守护线程 是 就返回true 不是返回false setDaemon(true)//设置线程为守护线程(主线程不可设置)【应在启动线程前设置】【守护线程是依赖非守护线程的存在/死亡 而存在/慢慢消亡】 getState(): // 获取线程的状态
线程的优先级概念
线程的优先级等级为 :1------10
创建的线程默认优先级 都是 5
线程的三个常量 :
MAX_Priority 默认优先级 10
MIN_Priority 默认优先级 1
NOME_Priority 默认优先级 5
线程终止基本说明:
当线程完成任务后 会自动退出
还可以通过使用变量来控制run方法退出的方式停止线程 ,【通知方式】
线程的礼让 和 插队:
yield: 线程礼让 让出cpu 让其他线程执行,但礼让的时间不确定 所有不一定成功
join:线程插队 插队的线程一旦插队成功 先执行完插队的线程所有任务
Thread.yield() :// 线程礼让 暂停自身的线程 让给优先级相同或更高的线程[存在失败几率]
Thread.join()://线程插队 插队的线程一旦插队成功 先执行完插队的线程所有任务
用户线程和守护线程
1.用户线程 也叫工作线程 当线程的任务Zhi性完或通知方式结束
2.守护线程 一般是为了工作线程服务的,当所有的用户线程结束后 守护线程自动结束
常见的守护线程:垃圾回收机制
isDaemon() // 是否是守护线程 是 就返回true 不是返回false
setDaemon(true)//设置线程为守护线程(主线程不可设置)【应在启动线程前设置】【守护线程是依赖非守护线程的存在/死亡 而存在/慢慢消亡】
Thread t = new THread();
t.setDaemon(true)//设置 线程t 为守护线程
线程的生命周期
在JDK 中用Thread.State 枚举表示了线程的7种状态
-
NEW
尚未启动的线程处于此状态
-
RUNNABLE
在java虚拟机中执行的线程处于此状态
-
BLOCKED
被阻塞等待监视器锁定的线程处于此状态
-
WAITING
正在等待另一个线程执行特定动作的线程处于此状态
-
TIMED_WAITING
正在等待另一个线程执行动作达到指定时间的线程处于此状态
-
TERMINATED
已经退出的线程处于此状态
-
RUNNABLE 细分的话 又分为2个状态 【执行状态】---【就绪状态】
getState(): // 获取线程的状态 Thread thread = new Thread(new A()); System.out.println(thread.getState());//获取子线程当前状态 thread.start(); while (Thread.State.TERMINATED != thread.getState()) {//只要主线程 不等于子线程就一直循环 // 输出子线程当前的状态 System.out.println(thread.getName() + " " + thread.getState()); Thread.sleep(500);//主线程休息0.5秒 } //循环退出后 子线程的状态 System.out.println(thread.getName()+" 状态 "+thread.getState()); } } class A implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("真好"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
线程转换图
线程的同步
Synchronized 线程同步
线程同步机制
-
在多线程编程 一些敏感数据不允许被多个线程同时访问,此时就是用同步访问技术 保证数据在任何时刻,最多有一 个线程访问,以保证数据的完整性
-
也可以这样理解 线程同步 即当有一个线程在对内存进行操作时,其他线程都不能对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
同步具体方法
//1) synchronized(对象){//得到对象的锁后 才能操作同步代码 //需要被同步的代码 } //2) synchronized //还何以放在方法的声明中,表示整个方法--为同步方法 public synchronized void m(String name){ //需要被同步的代码 }
案例
// 有三个窗口在卖票 代表三个线程 Synchronized01 syn = new Synchronized01(); new Thread(syn).start();//第一个 new Thread(syn).start();//第二个 new Thread(syn).start();//第三个 } } // 实现接口方式 使用Synchronized 实现线程同步 class Synchronized01 implements Runnable { private static int num = 10;//让多个线程共享 private boolean toop = true; //控制run的方法变量 public synchronized void Sell(){//使用同步方法 在同一时刻 只能有一个线程来执行run方法 if (num <=0){ System.out.println("票卖完了"); Thread.interrupted(); toop = false; return; } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口"+Thread.currentThread().getName()+"卖出一张 还剩下"+(--num)+"张"); } @Override public void run() { while (toop) { Sell();//同步方法 } } }
java互斥锁
基本介绍
-
java语言中 引用了对象互斥锁的概念 来保证共享数据的完整性
-
每个对象都对应了一个可称为“互斥锁”的标记 这个标记用来保证在任一时刻,只能有一个线程访问该对象
-
关键字synchronized 来与对象的互斥锁联系 当某个对象用synchronized 修饰时 表明该对象在任一时刻只能有一个线程访问
-
同步的局限性: 导致程序的可执行效率要降低
-
同步方法(非静态的) 的锁可以是this 也可以是其他对象(要求是同一对象)
-
同步方(静态的)的锁为当前对象本身
class A{ // 同步方法(非静态) 锁是加在 this对象上 public synchronized void m1(){} //也可以加在方法内 同步代码块 public void m1(){ synchronized(this){ System.out.println("hello"); } } } class A{ //同步方法 (静态的) 锁是加在类上 A.class 上 public synchronized static void m1(){} //也可以加在方法内 public void m1(){ synchronized(A.class){ System.out.println("hello"); } } }
注意事项和细节
-
同步方法如果没有使用static修饰 默认锁对象为this
-
如果方法使用static 修饰 默认锁对象为类.class
-
实现的落地步骤
需要先分析上锁的代码
选择同步代码块 或 同步方法
要求多个线程的锁对象为同一个
Lock锁
JDK 5.0 出现的新特性
Lock lock = new ReentrantLock();// 创建锁对象
//显示加锁
lock.lock();
//显示释放锁
lock.unlock();
// 释放锁是必须要做的 所以要加在finally块中
线程的死锁
多个线程都占用了对方的锁资源 但不肯想让 导致了死锁 在编程是一定要避免的死锁的发生
诊断死锁
-
在黑窗口 cmd 中输入 tasklist 知道线程对应的pid号 (java/javaw)
输入jstack 的pid值 查找哪里发生了死锁
-
在idea 下面 terminal 中输入 jstack 诊断
释放锁
下面操作会释放锁
-
当前线程的同步方法 同步代码块执行结束
案例 :上厕所大号 完事后出来
-
当前线程在同步代码块 同步方法中遇到break return
案例 没有正常的完事 经理 叫他出来修改bug 不得不出来
-
当前线程在同步代码块 同步方法中出现了未处理的Error 或 Exception 导致异常结束
案例 没有正常的完事 发现忘带纸 不得不出来
-
当前线程在同步代码块 同步方法中执行了线程对象的wait方法 当前线程暂停 并释放锁
案例 没有正常完事 觉得需要酝酿下 所以出来等会再进去
下面操作不会释放锁
-
线程执行同步代码块 或同步方法时 程序调用Thread.sleep(),Thread.yield()方法暂停当前线程的执行 不会释放锁
案例 上厕所 太困了 咋坑位上眯了会
-
线程执行同步代码块时 其他线程调用了该线程的suspend()方法 将线程挂起 该线程不会释放锁
提示 应尽量避免使用suspend 和 resume 来控制线程 方法 不推荐使用
线程池
线程池是一个容器
Executors 创建线程池
//创建一个默认线程池
static Executors.newCachedTheadPool();
//创建指定数量线程的线程池
static Executors.newFixedThreadPool(int nThreads)
public static void main(String[] args) {
// 创建默认线程池
ExecutorService pool = Executors.newCachedThreadPool();
// 向池中提交任务
pool.submit(new my());
pool.submit(new my());
//关闭线程
//已提交的任务执行完后 不接受新任务
pool.shutdown();
//试图停止正在执行的任务 不执行已提交并
// 处于等待状态的任务 将未执行的任务返回到list集合中
List<Runnable> runnables = pool.shutdownNow();
}
}
class my implements Runnable{
@Override
public void run() {
for (int i = 0; i <11; i++) {
System.out.println(Thread.currentThread().getName()+"--------"+i);
}
}
}
关于Java多线程就分享到这了 如果有遗漏的 还请大家评论指出
如果本篇文章帮到了你 也希望大家可以动动小手点赞加关注 支持下