多线程基础
程序:为了完成任务用某种语言编写的一组指令的集合
进程:
- 进程是指运行中的程序,比如使用QQ就是启动一个进程,操作系统就会为该进程分配内存空间。
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它本身的产生、存在和消亡的过程
线程:
- 线程由进程创建,是进程的一个实体
- 一个进程可以拥有多个线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdXejj5S-1632128244791)(C:\Users\91966\AppData\Roaming\Typora\typora-user-images\1629934401218.png)]
main线程运行60次,Thread-0子线程运行80次,当main线程结束后进程并没有结束,而是等所有线程结束后进程才会结束
start()方法调用**start0()**方法后,该线程不会立即执行,只是将线程变为可运行状态,具体什么时候执行,取决于CPU,由CPU统一调度
创建线程:
-
通过继承Thread类创建线程
-
通过实现接口Runnable来开发线程 //这里无法再直接调用start()来启动线程 //需要创建一个Thread对象,把dog对象(已实现Runnable接口)放入Thread Thread thread = new Thread(dog); thread.start();
继承Thread和实现Runnable接口的区别
- 从java的设计上来看,通过继承Thread和实现Runnable接口本质上没有区别,从jdk文档上看Thread类本身就是实现Runnable接口
- 实现Runnable接口更加适合多个线程共享一个资源的情况,并且避免了单继承。建议都是用Runnable
线程终止:
- 线程完成后,自动退出
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
线程常用方法1
-
setName 设置线程名称,使之与参数name相同
-
getName 返回该线程的名称
-
start 使该线程开始执行;实际上是java虚拟机底层调用该线程的start0方法
- start底层会创建新的线程,调用run方法,run方法本身是一个简单地方法调用,不会启动新的线程
-
run 调用线程对象run方法
-
setPriority 更改线程的优先级
- 优先级范围:常用最小(MIN_PRIORITY = 1)是1;默认(NORM_PRIORITY = 5)是5;最大(MAX_PRIORITY = 10)是10
-
getPriority 获取线程的优先级
-
sleep 在指定的毫秒内让当前正在执行的线程休眠(暂停执行)
- 线程的静态方法,是当前线程休眠
-
interrupt 中断线程
-
中断线程,但没有真正的结束线程。所以一般用来中断正在休眠的线程
package com.lic.method; public class ThreadMethod01 { public static void main(String[] args) throws InterruptedException { T t = new T(); t.setName("李琛");//给线程设置名称 t.setPriority(Thread.MIN_PRIORITY);//设置优先级为最低 t.start(); for (int i = 0; i <5 ; i++) { Thread.sleep(1000); System.out.println("hi"+i); } System.out.println(t.getName()+"线程的优先级为"+t.getPriority()); t.interrupt();//执行到这里就会中断t线程的休眠 } } class T extends Thread{ @Override public void run() { while (true){ for (int i = 0; i <100 ; i++) { System.out.println(Thread.currentThread().getName()+"吃包子"+i);//获取线程名称 } try { System.out.println(Thread.currentThread().getName()+"休眠中"); Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); //当线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码 //InterruptedException是捕获到的一个中断异常 System.out.println(Thread.currentThread().getName()+"被interrupt了"); } } } }
-
线程常用方法2
-
yield 线程礼让。让出cpu,让其他线程先执行,但礼让的时间不确定,所以也不一定礼让成功
-
join 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入线程所有的任务
package com.lic.method; public class ThreadMethod02 { public static void main(String[] args) { M1 m1 = new M1(); m1.setName("子线程"); m1.start(); for (int i = 1; i <=20 ; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hi "+Thread.currentThread().getName()+i); if (i==5){ //try { System.out.println("先运行完毕子线程"); Thread.yield();//礼让子线程,但是不一定成功 // m1.join();//线程插队 //} catch (InterruptedException e) { // e.printStackTrace(); //} System.out.println("子线程运行完毕,主线程继续运行"); } } } } class M1 extends Thread{ @Override public void run() { for (int i = 1; i <=20 ; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hello "+Thread.currentThread().getName()+i); } } }
现场常用方法3
-
用户线程 也叫工作线程,当线程的任务执行完成或通知的方式结束
-
守护线程 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
-
常见的守护线程:垃圾回收机制
package com.lic.method; public class ThreadMethod03 { public static void main(String[] args) throws InterruptedException { MyDaemonThread myDaemonThread = new MyDaemonThread(); //如果需要在主线程结束后,子线程自动结束。只需要将子线程设置成守护线程 myDaemonThread.setDaemon(true);//需要先设置成守护线程再启动 myDaemonThread.start();//DaemonThread:守护线程 for (int i = 1; i <=10 ; i++) { System.out.println("宝强在辛苦的工作"); Thread.sleep(1000); } System.out.println("宝强不工作回家了,马蓉和宋喆也不能聊天了"); } } class MyDaemonThread extends Thread{ @Override public void run() { while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("马蓉和宋喆快乐聊天"); } } }
线程的生命周期 **
线程同步机制
-
在多线程中一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时候最多只有一个线程访问,以保证数据的完整性
-
也可以这样理解:线程同步,即当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
-
具体方法
-
-
同步代码块
synchronized(对象){ //得到对象的锁才能操作同步代码 //需要被同步的代码 }
-
-
- synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m (String name){ //需要被同步的代码 }
-
互斥锁
-
局限性:导致程序的执行效率降低
-
非静态的同步方法的锁可以是this,也可以是其他对象(要求是同一个对象)
-
静态的同步方法的锁为当前类本身
//静态的同步方法的锁为当前类本身 public synchronized static void m1(){} //锁是加在SellTickets3.class上 //如果要在静态方法中,实现一个静态代码块 public static void m2(){ synchronized (SellTickets3.class){//SellTickets3.clas:类本身 System.out.println("m2"); } }
互斥锁注意事项:
- 同步方法如果没有使用static修饰,默认锁的对象是this
- 如果方法使用static修饰,默认锁的对象是 类.class
- 实现步骤:
- 需要分析上锁的代码
- 选择同步代码或同步方法
- 要求多个线程的锁对象为同一个即可
线程的死锁
多个线程都占用了对方的资源,但不肯相让,导致了死锁,在编程一定要避免死锁的发生
下面操作会释放锁:
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步方法、同步代码块中遇到break和return
- 当前线程在同步方法、同步代码块中遇到Error和Exception,导致异常结束
- 当前线程在同步方法、同步代码块中执行了线程对象的wait()方法,当前线程暂停,并且释放锁
下面的操作不会释放锁:
- 线程执行同步方法、同步代码块时,程序调用Thread.sleep()和Thread.yield()方法时暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了suspend()方法将此线程挂起,该线程不会释放锁
- 避免使用suspend()来控制线程
-
、同步代码块中遇到Error和Exception,导致异常结束
4. 当前线程在同步方法、同步代码块中执行了线程对象的wait()方法,当前线程暂停,并且释放锁
下面的操作不会释放锁:
- 线程执行同步方法、同步代码块时,程序调用Thread.sleep()和Thread.yield()方法时暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了suspend()方法将此线程挂起,该线程不会释放锁
- 避免使用suspend()来控制线程