二、线程阶段一
1.线程(Thread)概念
一个线程就是一个执行流,每个线程之间都可以按照自己的顺序执行自己的代码,多个线程之间也就执行这多份代码。简单来讲线程它就类似于厂里面的生产线。
2.多线程编程和多进程编程都能满足“并发编程”需求场景,那我们为什么要使用多线程编程呢?
进程是比较“重量的”速度慢,消耗资源多,在创建、销毁、调度进程的时候成本都是比较高的,虽然进程它可以解决并发编程的问题,但它不是一个高效的选择(就好比上面我说过的两个特别优秀的同学,都是满分,但是一个用时10分钟,一个用时30分钟,很明显我们选择的是用时最少的)。
那进程为什么会是重量的呢?这里主要是体现在资源分配操作上,比如说我们要给进程分配一块内存,系统就需要遍历自己的空闲内存的表,找到一个大小差不多的空间进行资源分配。同时如果有很多进程都在申请系统资源,那么在进行资源分配时候就得一个一个来,很明显是浪费了很多时间的。
而线程它则是更轻量的进程,约定,一个进程中可以包含多个线程,每个线程都是一个独立可以调度执行的执行流,这些执行流之间本身就是并发的,同时这些线程共用同一份进程的系统资源,也就是说对于线程来讲,系统资源是已经分配好了,所以在创建、销毁、调度线程都比进程的更快。
所以说操作系统真正调度的是在调度线程,而不是进程,线程是操作系统调度运行的基本单位,进程是操作系统分配资源的基本单位
3.进程和线程之间的区别
(1)进程包含线程,每个进程至少有一个线程存在,即主线程(工厂和流水线)
(2)进程有自己独立的空间和文件描述符表,同一个进程中的各个线程之间共享同一个地址空间和文件描述符表。进程和进程之间不共享内存空间。
(3)进程是操作系统分配资源的基本单位,线程是操作系统调度执行的基本单位。
(4)进程之间具有独立性,一个进程挂了,不会影响别的进程,同一个进程里的多个线程之间,一个线程挂了,可能会把整个进程带走,影响到其它线程的。
4.Java进行线程编程
Java标准库提供了一个类Thread表示一个线程,下面是感受多线程程序和普通程序的区别:
package notebook;
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("多线程线程");
}
}
}
public class ThreadTest01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//开启线程
myThread.start();
while (true) {
System.out.println("主线程");
}
}
}
可以看出每个线程都是一个独立的执行流,并且多个线程之间是并发执行的。同时我们可以看到打印的结果也是无序的,随机的,这里的话表示的是两个线程都是同时执行的,但是打印结果肯定是有先有后的,因为是两个线往同一个控制台上打印,所以同一个控制台必须得顺序输出,这也就出现了我们的看到了无序的这种打印结果。
5.使用jconsole命令观察线程
前提我们得把程序跑起来!!!
我们可以看到当前我们的线程Thread-0,还有我们的主线程main情况。
package notebook;
class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("多线程线程");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadTest01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//开启线程,start创建线程
myThread.start();
//start会创建新的线程
//run不会创建新的线程,run是在main线程中执行的,入口的方法
//run是特殊方法,可以被自动调用,对于普通方法,需要手动调用
// myThread.run();
while (true) {
System.out.println("主线程");
}
}
}
上面的run方法不是一个随便的方法,它是重写了父类的方法。在我们使用thread.start()时候它会调用操作系统的api,创建新线程,新的线程里调用thread.run();。
此处的sleep是Thread的静态方法,等多少时间后在执行。
6.线程的创建
(1)继承Thread类,重写run方法
package notebook;
class Mythread1 extends Thread {
@Override
public void run() {
System.out.println("hello");
}
}
public class ThreadTest02 {
public static void main(String[] args) {
Mythread1 mythread1 = new Mythread1();
mythread1.start();
System.out.println("world");
}
}
(2)实现Runnable接口,重写run方法
package notebook;
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("hello");
}
}
public class ThreadTest03 {
public static void main(String[] args) {
//创建Thread实例,调用Thread的构造方法时将Runnable对象作为target参数
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
System.out.println("Runnable接口实现线程");
}
}
第一种写法是使用Thread的run描述线程入口
第二种是使用Runnable interface来描述线程入口
所以说两个写法没有本质区别
(3)继承Thread类,使用匿名内部类
package notebook;
public class ThreadTest04 {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
while (true) {
try {
System.out.println("使用匿名内部类创建Thread子类对象");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();
while (true) {
try {
System.out.println("主线程");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(4)实现Runnable接口,使用匿名内部类
package notebook;
public class ThreadTest05 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
System.out.println("实现Runnable接口,使用匿名内部类");
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
thread.start();
while (true) {
try {
System.out.println("主线程");
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
(5)lambda表达式(推荐写法)
package notebook;
public class ThreadTest06 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("使用lambda表达式创建线程");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
while (true) {
System.out.println("主线程");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
lambda表达式,本质上是一个匿名函数,基本写法:() -> {}
()里面放参数,如果只有一个可以省略
{}里面放函数体,只有一行代码,也可以省略
7.多线程的优势——>增加运行速度
这里通过并发和串行方式计算变量的值,看计算的时间长短进行验证。
System.nanoTime()记录当前系统的纳秒级时间戳
serial串行的完成一系列运算,concurrency使用两个线程并行的完成同样的运算
package notebook;
/**
* 多线程的优势——》增加运算速度
*/
public class ThreadTest07 {
public static final long count = 10_0000_0000;
// 并发方式
public static void concurrency() throws InterruptedException{
// 开始时间
long begin = System.nanoTime();
// 通过线程计算a的值
Thread thread = new Thread(() -> {
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
});
thread.start();
//主线程种计算b的值
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
// 等待thread结束后在开始进行
thread.join();//这里的话要抛异常,在main线程种,调用thread.join表示是让main线程等待thread结束,再往下执行,
// 统计时间
long end = System.nanoTime();//最后计算完成后的时间
//1.0转换为浮点类型,/1000/1000转换为毫秒
double resulttime = (end - begin) * 1.0 / 1000 / 1000;
System.out.println("并发计算时间:" + resulttime + "毫秒");
}
//串行方式,在主线程中进行计算
public static void serial() {
long begin = System.nanoTime();
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
long end = System.nanoTime();
double resulttime = (end - begin) * 1.0 / 1000 / 1000;
System.out.println("串行计算时间:" + resulttime + "毫秒");
}
public static void main(String[] args) throws InterruptedException{
concurrency();
serial();
}
}
8.Thread类及常见方法
Thread类是JVM用来管理线程的一个类,也就是说每个线程都有一个唯一的Thread对象与之关联。
(1)常见的构造方法
public class ThreadTest08 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true) {
System.out.println("Thread()");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("常见构造方法:Thread()");
}
}
class MyRunnables implements Runnable {
@Override
public void run() {
while (true) {
try {
System.out.println("Thread(Runnable target)");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadTest09 {
public static void main(String[] args) {
MyRunnables myRunnables = new MyRunnables();
Thread thread = new Thread(myRunnables);
thread.start();
System.out.println("使用Runnable对象创建线程对象");
}
}
package notebook;
public class ThreadTest10 {
public static void main(String[] args) {
Thread thread = new Thread("线程1") {
@Override
public void run() {
while (true) {
System.out.println("我是线程1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();
while (true) {
System.out.println("创建线程对象,并命名");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同样的对于创建线程对象,并且命名,Thread(String name),我们可以通过上述说的jconsole去查看我们的线程名字:
package notebook;
public class ThreadTest11 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("使用Runnable对象创建线程对象,并命名");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"线程2");
thread.start();
while (true) {
System.out.println("主线程");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package notebook;
public class ThreadTest12 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while(true) {
System.out.println("lambda表达式创建线程,并命名");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"线程3");
thread.start();
while(true) {
System.out.println("主线程");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(2)Thread的几个常见属性
代码示例:
package notebook;
public class ThreadTest13 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + "啦啦啦");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "我的名字叫线程1");
},"线程1");
System.out.println(Thread.currentThread().getName() + ":ID:" + thread.getId());
System.out.println(Thread.currentThread().getName() + ":名称:" + thread.getName());
System.out.println(Thread.currentThread().getName() + ":状态:" + thread.getState());
System.out.println(Thread.currentThread().getName() + ":优先级:" + thread.getPriority());
System.out.println(Thread.currentThread().getName() + ":是否是后台线程:" + thread.isDaemon());
System.out.println(Thread.currentThread().getName() + ":是否存活:" + thread.isAlive());
System.out.println(Thread.currentThread().getName() + ":是否被中断:" + thread.isInterrupted());
thread.start();
while (thread.isAlive()) {
try {
System.out.println(Thread.currentThread().getName() + ":状态:" + thread.getState());
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
以上就是对线程的初步学习啦,主要就是为什么要使用多线程编程,如何创建线程,如何通过jconsole去观察线程,线程的常见构造方法使用,即常见属性。