线程
1.1 - 进程
进程就是正在运行中的程序(进程是驻留在内存中的)
是系统执行资源分配和调度的独立单位
每一进程都有属于自己的存储空间和系统资源
注意:进程A和进程B的内存独立不共享。
1.2 - 线程
线程就是进程中的单个顺序控制流,也可以理解成是一条执行路径
单线程:一个进程中包含一个顺序控制流(一条执行路径)
多线程:一个进程中包含多个顺序控制流(多条执行路径)
在java语言中:
线程A和线程B,堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。
对于单核的CPU来说,不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是多个事情同时在做。
1.3 -java中多线程的生命周期
就绪状态:就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权力(CPU时间片就是执行权)。当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法的开始执行标志着线程进入运行状态。
运行状态:run方法的开始执行标志着这个线程进入运行状态,当之前占有的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU时间片,当再次抢到CPU时间之后,会重新进入run方法接着上一次的代码继续往下执行。
阻塞状态:当一个线程遇到阻塞事件,例如接收用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片。之前的时间片没了需要再次回到就绪状态抢夺CPU时间片。
锁池:在这里找共享对象的对象锁线程进入锁池找共享对象的对象锁的时候,会释放之前占有CPU时间片,有可能找到了,有可能没找到,没找到则在锁池中等待,如果找到了会进入就绪状态继续抢夺CPU时间片。(这个进入锁池,可以理解为一种阻塞状态)
1.4 - 多线程的实现方式(一)
-
继承Thread类
1、自定义一个类MyThread类,用来继承与Thread类
2、在MyThread类中重写run()方法
3、在测试类中创建MyThread类的对象
4、启动线程
/** * @author Mr.乐 * @Description */ public class Demo01 { public static void main(String[] args) { //创建线程 MyThread t01 = new MyThread(); MyThread t02 = new MyThread(); MyThread t03 = new MyThread("线程03"); //开启线程 // t01.run(); // t02.run(); // t03.run(); // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。) // start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。 // 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。 // 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。 // run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。 t01.start(); t02.start(); t03.start(); //设置线程名(补救的设置线程名的方式) t01.setName("线程01"); t02.setName("线程02"); //设置主线程名称 Thread.currentThread().setName("主线程"); for (int i = 0; i < 50; i++) { //Thread.currentThread() 获取当前正在执行线程的对象 System.out.println(Thread.currentThread().getName() + ":" + i); } } } class MyThread extends Thread{ public MyThread() { } public MyThread(String name) { super(name); } //run方法是每个线程运行过程中都必须执行的方法 @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(this.getName() + ":" + i); } } }
此处最重要的为start()方法。单纯调用run()方法不会启动线程,不会分配新的分支栈。
start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。线程就启动成功了。
启动成功的线程会自动调用run方法(由JVM线程调度机制来运作的),并且run方法在分支栈的栈底部(压栈)。
run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
单纯使用run()方法是不能多线程并发的。
-
1.5 - 设置和获取线程名
设置线程名setName(String name):设置线程名
通过带参构造方法设置线程名
获取线程名
getName():返回字符串形式的线程名
Thread.CurrentThread():返回当前正在执行的线程对象
1.6 - 多线程的实现方式(二)
实现Runnable接口1、自定义一个MyRunnable类来实现Runnable接口
2、在MyRunnable类中重写run()方法
3、创建Thread对象,并把MyRunnable对象作为Tread类构造方法的参数传递进去
4、启动线程
/** * @author Mr.乐 * @Description */ public class Demo02 { public static void main(String[] args) { MyRunnable myRun = new MyRunnable();//将一个任务提取出来,让多个线程共同去执行 //封装线程对象 Thread t01 = new Thread(myRun, "线程01"); Thread t02 = new Thread(myRun, "线程02"); Thread t03 = new Thread(myRun, "线程03"); //开启线程 t01.start(); t02.start(); t03.start(); //通过匿名内部类的方式创建线程 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); } } },"线程04").start(); } } //自定义线程类,实现Runnable接口 //这并不是一个线程类,是一个可运行的类,它还不是一个线程。 class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); } } }
1.7 - 多线程的实现方式(三)
实现Callable接口( java.util.concurrent.FutureTask; /JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。)1、自定义一个MyCallable类来实现Callable接口
2、在MyCallable类中重写call()方法
3、创建FutureTask,Thread对象,并把MyCallable对象作为FutureTask类构造方法的参数传递进去,把FutureTask对象传递给Thread对象。
4、启动线程
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; /** * @author Mr.乐 * @Description 线程实现的第三种方式 */ public class Demo04 { public static void main(String[] args) throws Exception { // 第一步:创建一个“未来任务类”对象。 // 参数非常重要,需要给一个Callable接口实现类对象。 FutureTask task = new FutureTask(new Callable() { @Override public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值 // 线程执行一个任务,执行之后可能会有一个执行结果 // 模拟执行 System.out.println("call method begin"); Thread.sleep(1000 * 10); System.out.println("call method end!"); int a = 100; int b = 200; return a + b; //自动装箱(300结果变成Integer) } }); // 创建线程对象 Thread t = new Thread(task);
/** * @author Mr.乐 * @Description 线程睡眠 */ public class DemoSleep { public static void main(String[] args) { // 创建线程 MyThread1 t01 = new MyThread1("黄固"); MyThread1 t02 = new MyThread1("欧阳锋"); MyThread1 t03 = new MyThread1("段智兴"); MyThread1 t04 = new MyThread1("洪七公"); //开启线程 t01.start(); t02.start(); t03.start(); t04.start(); } } class MyThread1 extends Thread{ public MyThread1() { } public MyThread1(String name) { super(name); } @Override // 重点:run()当中的异常不能throws,只能try catch // 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。 public void run() { for (int i = 1; i < 50; i++) { System.out.println(this.getName() + "正在打出第 - " + i + "招"); try { Thread.sleep(500);//让当前正在执行的线程睡眠指定毫秒数 } catch (InterruptedException e) { e.printStackTrace(); } } } }
// 启动线程 t.start(); // 这里是main方法,这是在主线程中。 // 在主线程中,怎么获取t线程的返回结果? // get()方法的执行会导致“当前线程阻塞” Object obj = task.get(); System.out.println("线程执行结果:" + obj); // main方法这里的程序要想执行必须等待get()方法的结束 // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果 // 另一个线程执行是需要时间的。 System.out.println("hello world!"); } }
1.8 -线程控制
方法名 说明
void yield() 使当前线程让步,重新回到争夺CPU执行权的队列中
static void sleep(long ms) 使当前正在执行的线程停留指定的毫秒数
void join() 等死(等待当前线程销毁后,再继续执行其它的线程)
void interrupt() 终止线程睡眠 -
1.8.1 -sleep()方法 (谁执行谁就是当前线程)/** * @author Mr.乐 * @Description 线程睡眠 */ public class DemoSleep { public static void main(String[] args) { // 创建线程 MyThread1 t01 = new MyThread1("黄固"); MyThread1 t02 = new MyThread1("欧阳锋"); MyThread1 t03 = new MyThread1("段智兴"); MyThread1 t04 = new MyThread1("洪七公"); //开启线程 t01.start(); t02.start(); t03.start(); t04.start(); } } class MyThread1 extends Thread{ public MyThread1() { } public MyThread1(String name) { super(name); } @Override // 重点:run()当中的异常不能throws,只能try catch // 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。 public void run() { for (int i = 1; i < 50; i++) { System.out.println(this.getName() + "正在打出第 - " + i + "招"); try { Thread.sleep(500);//让当前正在执行的线程睡眠指定毫秒数 } catch (InterruptedException e) { e.printStackTrace(); } } } }
注意:run()方法中的异常只能try catch,因为父类没有抛出异常,子类不能抛出比父类更多的异常。
-
1.8.2 -interrupt()方法和stop()方法
-
/** * @author Mr.乐 * @Description 终止线程 */ public class DemoInterrupt { public static void main(String[] args) { Thread t = new Thread(new MyRunnable2()); t.setName("t"); t.start(); try { Thread.sleep(1000 * 5); } catch (InterruptedException e) { e.printStackTrace(); } // 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。) t.interrupt(); // t.stop(); //强行终止线程 //缺点:容易损坏数据 线程没有保存的数据容易丢失 } } class MyRunnable2 implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "---> begin"); try { // 睡眠1年 Thread.sleep(1000 * 60 * 60 * 24 * 365); } catch (InterruptedException e) { // e.printStackTrace(); } //1年之后才会执行这里 System.out.println(Thread.currentThread().getName() + "---> end"); } }
1.8.3 -合理的终止线程
做一个boolean类型的标记
/** * @author Mr.乐 * @Description */ public class DemoSleep02 { public static void main(String[] args) { MyRunable4 r = new MyRunable4(); Thread t = new Thread(r); t.setName("t"); t.start(); // 模拟5秒 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // 终止线程 // 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。 r.run = false; } } class MyRunable4 implements Runnable { // 打一个布尔标记 boolean run = true; @Override public void run() { for (int i = 0; i < 10; i++){ if(run){ System.out.println(Thread.currentThread().getName() + "--->" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }else{ // return就结束了,你在结束之前还有什么没保存的。 // 在这里可以保存呀。 //save.... //终止当前线程 return; } } } }
1.8.4 -
yield()
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。 -
/** * @author Mr.乐 * @Description 线程让位 */ public class DemoYield { public static void main(String[] args) { //创建线程 MyThread5 t01 = new MyThread5("线程01"); MyThread5 t02 = new MyThread5("线程02"); MyThread5 t03 = new MyThread5("线程03"); //开启线程 t01.start(); t02.start(); t03.start(); } } class MyThread5 extends Thread{ public MyThread5() { } public MyThread5(String name) { super(name); } @Override public void run() { for (int i = 0; i < 50; i++) { if(30 == i){ Thread.yield();//当循i环到30时,让线程让步 //1、回到抢占队列中,又争夺到了执行权 //2、回到抢占队列中,没有争夺到执行权 } System.out.println(this.getName() + ":" + i); } } }
1.8.5 -join()
1.9 - 线程的调度
线程调度模型均分式调度模型:所有的线程轮流使用CPU的使用权,平均分配给每一个线程占用CPU的时间。
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么就会随机选择一个线程来执行,优先级高的占用CPU时间相对来说会高一点点。
Java中JVM使用的就是抢占式调度模型
getPriority():获取线程优先级
setPriority:设置线程优先级
、/** * @author Mr.乐 * @Description 线程的调度 */ public class Demo07 { public static void main(String[] args) { //创建线程 MyThread t01 = new MyThread("线程01"); MyThread t02 = new MyThread("线程02"); MyThread t03 = new MyThread("线程03"); //获取线程优先级,默认是5 // System.out.println(t01.getPriority()); // System.out.println(t02.getPriority()); // System.out.println(t03.getPriority()); //设置线程优先级 t01.setPriority(Thread.MIN_PRIORITY); //低 - 理论上来讲,最后完成 t02.setPriority(Thread.NORM_PRIORITY); //中 t03.setPriority(Thread.MAX_PRIORITY); //高 - 理论上来讲,最先完成 //开启线程 t01.start(); t02.start(); t03.start(); } }