前言
记录学习过程
Java线程并发
开发工具:IDEA
环境:jdk1.8
目录
- 概念
- 创建线程
2.1.继承Thread类
2.2.实现Runnable接口
2.3.Thread类与Runnable接口
2.4.匿名内部类 - 线程优先级
- 线程的其他操作方法
- 总结
线程
概念
曾经在操作系统就学过相关概念
操作系统是计算机的管理者,它负责任务的调度,资源的分配管理,硬件的使用,而我们评测编程的程序就是在操作系统之上运行的
进程:就是一个任务或者程序的动态执行过程,是操作系统进行资源调度分配的一个独立的单位,是程序运行的载体
线程:进程越来越复杂,就有了线程的概念,一个程序或者任务需要实现多个子任务才能完成,这些子任务就是线程,总的说线程是进程的执行单元,是程序执行流的最小单元,是处理器调度和分派的基本单位
早期计算机简单,进程是程序执行的最小单位,随着计算机的发展,进程的切换开销越来越大,就需要一个更小的单元–线程,一个进程可以有一个或多个线程
最直观的:这就叫进程
创建线程
常见的创建线程的方法:继承Thread类、实现Runnable接口,匿名内部类
更高级的方法:ExecutorService、Callable< Class>、Future 有返回值线程,基于线程池创建线程,这些是下一个学习的目标
继承Thread类
创建线程:
package com.company.Thread;
public class OpenThread {
static class CreatThread1 extends Thread{
public void run(){
for(int i=0;i<30;i++)
System.out.println(i+":运行线程1");
}
}
static class CreatThread2 extends Thread{
public void run(){
for(int i=0;i<300;i++)
System.out.println(i+":运行线程2");
}
}
public static void main(String[] args){
CreatThread1 thread1=new CreatThread1();
CreatThread2 thread2=new CreatThread2();
//使用线程的启动方法start
thread1.start();//启动线程1
thread2.start();//启动线程2
for(int i=0;i<3000;i++)
System.out.println(i+":运行主程序");
}
}
注意:启动线程的唯一方法就是通过 Thread 类的 start()实例方法,start()方法是一个 native 方法,它将启动一个新线
程,并执行 run()方法
运行顺序不一样,这就涉及到多线程的并发问题
通过继承Thread类实现线程类缺点在与不能继承其他父类了
其实Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例
查看源码:
实现Runnable接口
package com.company.Thread;
public class OpenThread {
static class CreatThread1 implements Runnable{
public void run(){
for(int i=0;i<30;i++)
System.out.println(i+":运行线程1");
}
}
static class CreatThread2 implements Runnable{
public void run(){
for(int i=0;i<300;i++)
System.out.println(i+":运行线程2");
}
}
public static void main(String[] args){
CreatThread1 thread1=new CreatThread1();
CreatThread2 thread2=new CreatThread2();
//使用线程的启动方法start
//启动线程需要实例化Thread,并传入我们的实例
new Thread(thread1).start();//启动线程1
new Thread(thread2).start();//启动线程2
for(int i=0;i<3000;i++)
System.out.println(i+":运行主程序");
}
}
线程运行抢占cpu
Thread类与Runnable接口
继承Thread类可以实现线程
可以看看Thread类源码是如何实现的?
Thread实现了Runnable接口(所以Thread类与Runable接口实现线程本质也没什么区别)
启动机制:
通过new Thread(thread1)方法实例化Thread
传入target线程,然后会运行Thread类的run方法,这个run方法是启动我们自身线程的run
所以说,我们继承Thread类和实现Runnable接口是一样的,Runnable接口仅仅是规范子类必须重写run方法
Runnable接口仅仅有一个抽象方法run
而启动机制:其实new Thread(thread1).start启动后,实际上运行的是Thread的run方法,只不过是Thread的run方法调用了thread1的run方法(如果重写一些new Thread的run方法,你会发现导入的thread1线程不会运行)
不管什么线程,最基本的是Thread类
对于继承Thread还是实现Runnable接口的选择:
推荐还是通过实现Runnable接口创建线程
通过接口将“执行者”与“被执行者”分离,降低了耦合度
匿名内部类
匿名内部类的方法只是改变了写法而已
关于匿名内部类的知识:JavaSE笔记(2.4)Java基础 - 内部类、Lambda表达式
package com.company.Thread;
public class OpenThread {
static class CreatThread1 implements Runnable{
public void run(){
for(int i=0;i<30;i++)
System.out.println(i+":运行线程1");
}
}
static class CreatThread2 implements Runnable{
public void run(){
}
}
public static void main(String[] args){
CreatThread1 thread1=new CreatThread1() {
public void run() {
for (int i = 0; i < 300; i++)
System.out.println(i + ":运行线程2");
}
};
CreatThread2 thread2=new CreatThread2();
//使用线程的启动方法start
//启动线程需要实例化Thread,并传入我们的实例
new Thread(thread1).start();//启动线程1
new Thread(thread2).start();//启动线程2
for(int i=0;i<3000;i++)
System.out.println(i+":运行主程序");
}
}
我们知道Lambda表达式是匿名类的简写版,我们可以继续简写
package com.company.Thread;
public class PersonThread {
static class CreatThread1 {
public void run() {
for (int i = 0; i < 30; i++)
System.out.println(i + ":运行线程1");
}
}
public static void main(String[] args) {
CreatThread1 creatThread1=new CreatThread1();
new Thread(()->creatThread1.run()).start();
}
}
甚至可以不用继承Thread类或者Runnable接口
其实还可以更简单
package com.company.Thread;
public class PersonThread {
static class CreatThread1 {
public void run() {
for (int i = 0; i < 30; i++)
System.out.println(i + ":运行线程1");
}
}
public static void main(String[] args) {
CreatThread1 creatThread1=new CreatThread1();
new Thread(creatThread1::run).start();
}
}
但是匿名类、Lambda表达式效率太差,虽然很炫
线程优先级
前面的写法没有设置优先级,所有线程都是默认普通优先级,就可以自由的去抢占cpu
当设置了优先级,就可以让线程根据优先级去占据cpu
优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级
通过Thread.setPriority()方法设置优先级
package com.company.Thread;
public class OpenThread {
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
static class CreatThread1 implements Runnable{
public void run(){
for(int i=0;i<30;i++)
System.out.println(i+":运行线程1");
}
}
static class CreatThread2 implements Runnable{
public void run(){
for(int i=0;i<300;i++)
System.out.println(i+":运行线程2");
}
}
public static void main(String[] args){
CreatThread1 thread1=new CreatThread1();
CreatThread2 thread2=new CreatThread2();
//使用线程的启动方法start
//启动线程需要实例化Thread,并传入我们的实例
//启动线程1
Thread myThread1=new Thread(thread1);
//设置优先级
myThread1.setPriority(MAX_PRIORITY);
myThread1.start();
//启动线程2
Thread myThread2=new Thread(thread2);
myThread2.setPriority(MIN_PRIORITY);
myThread2.start();
/* for(int i=0;i<3000;i++)
System.out.println(i+":运行主程序");*/
}
}
设置了线程1为最高优先级,确实是先运行完线程1再运行线程2
那反过来会怎样?
线程2运行到两百多时,线程1也开始运行了
这是为什么?
1、java线程是通过映射到系统的原生线程上来实现的,所以线程的调度最终还是取决于操作系统,操作系统的优先级与java的优先级并不一一对应,如果操作系统的优先级级数大于java的优先级级数(10级)还好,但是如果小于得的话就不行了,这样会导致不同优先级的线程的优先级是一样的。
2、优先级可能会被系统自动改变,比如windows系统中就存在一个优先级推进器,大致功能就是如果一个线程执行的次数过多的话,可能会越过优先级为他分配执行时间
线程的调度最终还是要取决于操作系统的线程规划器
我们的程序最终还是要在操作系统中执行,而Thread2运行次数太多了,Thread1等待时间太长,操作系统越过优先级运行Thread1
线程的其他操作方法
线程休眠方法为:sleep();
改一下前面运行线程2的操作:
//启动线程2
Thread myThread2=new Thread(thread2);
myThread2.setPriority(MAX_PRIORITY);
//睡眠1秒
try {
myThread2.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread2.start();
线程2优秀级最高,但是因为睡眠了1秒,线程1先运行了
暂停线程使用Thread中的suspend()方法;恢复暂停的线程使用resume()方法(过时)-- 容易造成死锁
可以去jdk文档查看这些方法
总结
复习了一下进程、线程的概念,线程的三种创建启动方式,线程优先级和一些线程的操作方法
接下来要学习多线程并发