【Java】读完理解多线程
多线程
- 进程:电脑上运行的一个个程序就称为进程。一个进程中至少包含一个线程。
- 线程:在一个进程中可以有多个执行单元同时运行,这些执行单元就是线程。
- 多线程:进程中的多个线索同时运行
1.多线程的实现方式:
- 继承Thread类,覆写Thread类的run()方法
- 实现Runnable接口,同样也是在run()方法中实现运行在线程上的代码
1.1继承Thread实现多线程的步骤
实现:
1.让目标类继承Thread类
2.重写run()方法
/** * 继承Thread类并重写run()方法 * @author asus * */ public class Mythread extends Thread{ public void run(){ int j=0; while(j<10){ System.out.println("继承Thread方法正在运行"+j); j++; } } }
执行:
3.创建实现子类的对象实例
4.通过子类的对象实例去调用start()方法开启多线程执行
import java.util.Iterator; import java.util.Set; import java.util.TreeMap; /* * 定义一个测试类 * */ public class Test { public static void main(String[] args) { //创建实现了Thread类的对象 Mythread thread = new Mythread(); //开启线程 thread.start(); //普通方法打印输出 int i = 0; while(i<10){ System.out.println("自定义普通方法在运行"+i); i++; } } }
1.2Runnable方法的诞生
- 为什么有了继承Thread类还不够,还又产生Runnable实现多线程:因为通过继承Thread方法会因为一个子类只能有一个父类的单根继承而被约束,存在很大的局限性。Runnable接口是为了解决继承存在的局限而产生的。
- Runnable接口的原型是Thread类的一个构造方法:Thread(Runnable target),其中Runnable是一个接口,它只有一个run()方法。
- 当我们通过该构造方法实现多线程的时候,只需要传入Runnable接口的实现类对应的实例即可。
1.3通过Runnable接口实现多线程的步骤:
实现:
1.子类实现Runnable接口
2.实现run()方法
/** * 实现Runnable接口并重写run()方法 * @author asus * */ public class Mythread implements Runnable{ //线程的代码代码段,当调用start()方法时,线程从此处开始执行 public void run(){ int j=0; while(j<10){ System.out.println("实现Runnable接口正在运行"+j); j++; } } }
执行:
1.实例化子类
2.将子类实例作为参数传入带有Runnable接口的Thread(Runnable target)方法中,从而实例化有参构造Thread类
3.通过实例化有参的Runnable接口构造得到的实例对象,通过实例对象调用start()方法开启多线程
import java.util.Iterator; import java.util.Set; import java.util.TreeMap; /* * 定义一个测试类 * */ public class Test { public static void main(String[] args) { //创建实现了Runnable类的Mythread类实例对象 Mythread Mythread = new Mythread(); //创建Thread线程对象 Thread thread = new Thread(Mythread); //开启线程 thread.start(); //普通方法打印输出 int i = 0; while(i<10){ System.out.println("自定义普通方法在运行"+i); i++; } } }
后台线程才会被结束掉
通过setDaemon()方法可以设置成后台程序
但是该方法要放在start()方法之前调用设置
2.线程的生命周期及状态流转
线程的生命周期开始于Thread对象的创建,结束于run()方法中代码执行完毕或出现Exception/Error异常情况阻断时。
线程的整个生命周期可分为5个阶段:新建--就绪--运行--阻塞--死亡
2.1线程的调度有两种模式
- 分时调度模式:所有的线程轮流获取CPU的使用权,并且平均分配所占用的CPU时间片
- 抢占式调度模式:让可运行池中优先级高的线程优先占用CPU使用权,优先级相同的随机选择一个执行,优先级高的执行完再执行优先级低的
2.2线程优先级
优先级实例:
public class MaxPriority implements Runnable { public void run(){ int j=0; while(j<10){ System.out.println(Thread.currentThread().getName()+"最大优先级执行"+j); j++; } } } /** * 实现Runnable接口并重写run()方法 * @author asus * */ public class MinPriority implements Runnable{ //线程的代码代码段,当调用start()方法时,线程从此处开始执行 public void run(){ int j=0; while(j<10){ System.out.println(Thread.currentThread().getName()+"MinPriority在运行"+j); j++; } } } /* * 定义一个测试类 * */ public class Test { public static void main(String[] args) { //创建minPriority线程实例对象 Thread minPriority = new Thread(new MinPriority()); //创建maxPriority线程实例对象 Thread maxPriority = new Thread(new MaxPriority()); //设置优先级为1 minPriority.setPriority(Thread.MIN_PRIORITY); //设置线程优先级为10 maxPriority.setPriority(10); //开启线程 maxPriority.start(); minPriority.start(); } }
2.3线程中阻塞方法总结
2.3.1线程休眠---sleep(long millis)
调用该线程方法可以让线程休眠,使用该静态方法需要抛出异常
2.3.2线程让步---yield()
yield()方法不会阻塞线程,它只是将线程转换成就绪状态让系统的调度器重新再调度一次
注意:当某个线程调用yield()方法之后,只有与当前线程优先级相同或者优先级更高的线程才能获得执行机会。
2.3.3线程插队---join()
当某个线程中调用其他线程的join()方法时,当前调用的线程将会被阻塞,开始执行插队的线程,直到被插队进来的join()方法对应的线程执行完成后,该线程才会继续执行。
2.3.4同步锁
同步代码块:实现线程资源共享访问需要加上同步锁
synchronized(lock){// lock:1.执行 0.等待 操作共享资源代码块 }
被synchronized修饰的方法在某一时刻只允许一个线程访问,有一个线程在访问的时候,其他的线程都会被阻塞,直到当前线程访问完毕后,其他线程才会有机会执行方法
synchronized 返回值类型 方法名([参数1],......){ }
注意:同步方法也有锁,它的锁就是当前调用该方法的对象,也就是this指向的对象,这样做的好处是同步方法被所有线程共享,方法所在的对象相当于所有线程来说是唯一的,从而保证了锁的唯一性。
Java中静态方法的锁是该方法所在类的class对象,该对象可以直接用“类名.class”的方式来获取
同步代码块的缺点:
线程每次执行同步代码块时,每次都会判断锁的状态,非常消耗资源,效率较低。
同步锁使用不恰当会出现死锁现象,导致代码可用性不高