多线程初阶------------
1,认识线程
1.1 概念
1)线程是什么
线程就是一个"执行流",可以理解为程序执行的最小单位;
可以看成轻量级的进程;
2)为啥要有线程
"并发编程" 的需要,但是我们不是已经有进程了吗,我们要知道,我们进行的是服务器开发,我们在访问网站的时候,一个用户进行访问就是一个进程,用户的数据量是非常庞大的,进程的创建和销毁需要的开销就会变得非常非常大,这样我们就引出了线程,让一个进程中包含一个或多个进程,提升效率,
3)线程和进程的区别
1,进程是操作系统进行资源分配的基本单位,线程是操作系统进行运算调度的基本单位;
2,线程的创建,销毁,调度需要的开销更小;
3,进程之间互不影响,同一进程下的线程会互相影响,创建进程后会自动创建一个线程,第一个线程会涉及到申请资源的操作,其余线程不会涉及,进程销毁才会释放资源,线程的销毁不会释放资源;
4,因为线程是调度相关,所以每一份线程都有调度相关的数据
5,一个进程死掉了不会影响其他进程,但是一个进程中一个线程死掉了就掀桌了,全部都运行不了了;
4)java中线程和操作系统的关系
java中包装好了操作系统中对线程操作的API,但是java是不推荐多进程编程的,我们只去学习多线程编程;
1.2 第一个多线程程序
java
class MyThread extends Thread{
public void run(){
System.out.println("myThread");
}
}
public class Demo1 {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
System.out.println("main");
}
}
main就是进程刚创建我们自动生成的第一个线程,运行
只是个示范,看接下来的讲解就好;
1.3 创建线程
1)创建对象继承Thread类
java
class MyThread2 extends Thread{
public void run(){
while(true){
System.out.println("MyThread2 线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
Thread thread = new MyThread2();
System.out.println("主线程");
}
}
我们创建一个类,让它继承Thread类,Thread类中给我们提供了一个run方法,让我们自己去写里面的内容,我们就在自己实现的类中重写run方法,我们循环打印,并且打印一次睡眠1秒,在主线程也就是main方法中,打印主线程,我们来看运行结果;
只有一个主线程,因为我们没有去调度线程,我们可以直接用.run或者是.start来开启线程
java
thread.run();
程序一直在运行,这里不明显,我们来借助一个工具,
找到jdk中的bin ,
以管理员身份运行它
找到我们刚才创建的Demo2
点击线程
我们看到main线程一直在等待,因为我们使用的run方法,所以是在主线程上运行的,如果我们想看到我们自己创建的线程,就要用start
我们在试试;
这个Thread------0就是我们自己创建的线程,但是main呢,还有为啥先打印的主线程呢,因为调度随机的,我们不知道操作系统让拿个线程先执行,所以就会发生这样的状况,这也是我们后期要重点掌握的,要怎么保证线程之间协调配合,避免乌鸦哥掀桌,哈哈哈,main线程在这里已经结束了,没啥好说的了,下一个;
2)实现Runnable接口
java
class MyRunnable implements Runnable{
public void run(){
while(true){
System.out.println("MyRunnable 线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo3 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
System.out.println("主线程");
}
}
这是第二种,我们Runnable接口,我们还是要使用Thread类来创建,这不有病吗.........这么麻烦,还不如用第一种,其实这种想法是不对的,大家听,没听过,高内聚低耦合,这里就谈到了低耦合,我们使用接口,在想要修改的时候去修改接口的代码即可,是不影响Thread的,但是我们使用Thread的时候,想要修改的时候,就要修改Thread中的代码, 可能扯到线程相关的代码,而且用类继承一次局限性大,接口更灵活;
运行
我们这次让主线程也活着
java
class MyRunnable implements Runnable{
public void run(){
while(true){
System.out.println("MyRunnable 线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyRunnable());
thread.start();
while(true){
System.out.println("主线程");
Thread.sleep(1000);
}
}
}
来运行
这次更能看到随机调度的现象
3)匿名内部类(Thread)
这几个其实用的都少,最多用到的还是lambda表达式
java
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
public void run(){
while(true){
System.out.println("Thread 线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
thread.start();
while(true){
System.out.println("主线程");
Thread.sleep(1000);
}
}
}
跟之前都一样,就是使用·匿名内部类了;
直接看运行
4)匿名内部类(Runnable)
java
public class Demo5 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(true){
System.out.println("Thread 线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
thread.start();
while(true){
System.out.println("主线程");
Thread.sleep(1000);
}
}
}
一样嗷,匿名内部类创建Ruunable对象,
5)lambda表达式
这个才是我们使用最多的方法,主要是很方便;
lambda表达式:
(参数)->{实现了啥}
java
public class Demo6 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
while (true){
System.out.println("Thread 线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
while(true){
System.out.println("主线程");
Thread.sleep(1000);
}
}
}
方便吧,在new Thread的时候直接在括号中使用lambda表达式就行;
大家可能有疑问,不说重写run方法吗,这个{}里面的就是我们已经重写了,这个跟那个第三个匿名内部类的方法其实很像的;
我们来看运行结果;
完美嗷
2,Thread类及常见方法
Thread类是JVM用来管理线程的一个类,我们每创建一个Thread对象就有一个线程与他对应;
2.1 Thread的常见构造方法
|-------------------------------------|--------------------|
| 方法 | 说明 |
| Thread() | 创建线程对象 |
| Thread(Runnable target) | 使用Runnable对象创建线程对象 |
| Thread(String name) | 创建线程对象并命名 |
| Thread(Runnable target,String name) | 使用Runnable |
java
Thread thread1 = new Thread();
Thread thread2 = new Thread(new Runnable() {public void run() {}});
Thread thread3 = new Thread(()->{
while (true){
System.out.println("线程3");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"线程3");
Thread thread4 = new Thread(new Runnable() {public void run() {}},"线程4");
我们用4种构造方法创建了线程,我们来观察一下线程3,我们是否把线程的名字修改了呢;
成功看到线程3了;
2.2 Thread的常见属性
|--------|-----------------|
| 属性 | 获取方法 |
| ID | getId() |
| 名称 | getName() |
| 状态 | getState() |
| 优先级 | getPriority() |
| 是否后台线程 | isDaemon() |
| 是否存活 | isAlive() |
| 是否被中断 | isInterrputed() |
-
ID 类似进程的pid,线程的唯一标识,不同线程不会重复;
-
名称 各种调试工具用到;
-
线程当前所处的情况;
-
通常来说优先级高的线程会容易调用;
-
可以想象为饭局中的小程序员,对这次饭局不起决定性作用,JVM会在一个进程的所有非后台线程结束后结束;
我们可以使用SetDaemon()来把当前线程设置为后台线程;
-
run方法是否结束;
-
终止线程运行;
我们来写一个代码获取所以线程信息;
java
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "还活着");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "即将死亡");
},"线程1");
System.out.println("ID :" + Thread.currentThread().getId() + " " + "ID :" + thread.getId());
System.out.println("name :" + Thread.currentThread().getName() + " " + "name :" + thread.getName());
System.out.println("state :" + Thread.currentThread().getState() + " " + "state :" + thread.getState());
System.out.println("优先级 :" + Thread.currentThread().getPriority() + " " + "优先级 :" + thread.getPriority());
System.out.println("是否存活 :" + Thread.currentThread().isAlive() + " " + "是否存活 :" + thread.isAlive());
System.out.println("中断? :" + Thread.currentThread().isInterrupted() + " " + "中断? :" + thread.isInterrupted());
thread.start();
while (thread.isAlive()){
System.out.println("ID :" + Thread.currentThread().getId() + " " + "ID :" + thread.getId());
System.out.println("name :" + Thread.currentThread().getName() + " " + "name :" + thread.getName());
System.out.println("state :" + Thread.currentThread().getState() + " " + "state :" + thread.getState());
System.out.println("优先级 :" + Thread.currentThread().getPriority() + " " + "优先级 :" + thread.getPriority());
System.out.println("是否存活 :" + Thread.currentThread().isAlive() + " " + "是否存活 :" + thread.isAlive());
System.out.println("中断? :" + Thread.currentThread().isInterrupted() + " " + "中断? :" + thread.isInterrupted());
System.out.println(Thread.currentThread().getState() + " " + thread.getState());
Thread.sleep(1000);
}
System.out.println("ID :" + Thread.currentThread().getId() + " " + "ID :" + thread.getId());
System.out.println("name :" + Thread.currentThread().getName() + " " + "name :" + thread.getName());
System.out.println("state :" + Thread.currentThread().getState() + " " + "state :" + thread.getState());
System.out.println("优先级 :" + Thread.currentThread().getPriority() + " " + "优先级 :" + thread.getPriority());
System.out.println("是否存活 :" + Thread.currentThread().isAlive() + " " + "是否存活 :" + thread.isAlive());
System.out.println("中断? :" + Thread.currentThread().isInterrupted() + " " + "中断? :" + thread.isInterrupted());
}
}
运行之后就能看到整个过程了;
2.3 启动一个线程
我们之前用过run方法来启动线程,实际上着并不是真正创建了线程,我们使用start真正在操作系统底层创建了一个线程,只有创建了线程对象再start才是让线程真正独立执行了;
2.4 中断一个线程
线程一旦工作就会等到任务结束才会停下来,但是有时候我们有让线程立即停下的需求,我们有两种办法来中断一个线程,其实叫终止更好,因为不是间断,而是线程就结束了;
我们来模拟一个场景,有两个员工张三,李四,老板让他们去给别人转账,张三正在转给骗子,李四及时阻止;
1,共享标记来中断线程
java
public class Demo3 {
public static boolean a = true;
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
while (a){
System.out.println(Thread.currentThread().getName() + "正忙着转账呢");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "我嘞个豆,差点转走了");
},"张三");
Thread thread2 = new Thread(()->{
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "老板来电话了!" + "张三在给骗子转账!");
a = false;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"李四");
thread1.start();
thread2.start();
}
}
来看运行结果
哈哈哈哈哈,好玩吧;
2,调用interrupt()方法来通知
|--------------------------------------|------------------------------------|
| 方法 | 说明 |
| Thread对象.interrupt() | 中断对象关联的线程,如果线程正在阻塞,以异常方式通知,否则设置标志位 |
| public static boolean interrputed(); | 判读当前线程的标志位是否设置,调用后清除标志位; |
| public boolean | 判读当前线程的标志位是否设置,调用后不清除标志位; |
在Java线程的上下文中,中断标志位是Thread对象维护内部的布尔值用于表示该线程是否被请求中断。
java
public class Demo3 {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
//或者用Thread.interrupted();
while (!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName() + "正忙着转账呢");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "我嘞个豆,差点转走了");
},"张三");
Thread thread2 = new Thread(()->{
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "老板来电话了!" + "张三在给骗子转账!");
thread1.interrupt();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"李四");
thread1.start();
thread2.start();
}
}
这里的原理就一样了,但是代码运行会报一个异常,
这个是因为
java
thread1.interrupt();
唤醒了sleep让他直接抛出InterruptedException,被捕获到,抛出RuntimeException异常,所以我们在这里直接break就行;
这样结果就对了;
2.5 等待一个线程
|------------------------------------------|---------------------|
| 方法 | 说明 |
| public void join() | 等待线程结束 |
| public void join(long millis) | 等待线程结束,最多等待millis毫秒 |
| public void join(long millis, int nanos) | 等待线程结束,精度更高;后面是纳秒; |
java
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
while (true){
System.out.println(Thread.currentThread().getName() + "线程正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"张三");
Thread thread2 = new Thread(()->{
while (true){
System.out.println(Thread.currentThread().getName() + "线程正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"李四");
thread1.start();
thread1.join();
thread2.start();
thread2.join();
System.out.println("全部线程打印结束");
}
}
我们创建了两个线程和主线程,thread1.join意思为让主线程等待thread1线程执行完再执行 ,此时thread2还没开启,我们来看运行结果
张三始终在工作我天,因为我们使用的join没有放参数,是无休止的等待;如果我们放参数就不会这样傻傻的等待了,
java
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
while (!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName() + "线程正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
},"张三");
Thread thread2 = new Thread(()->{
while (!Thread.interrupted()){
System.out.println(Thread.currentThread().getName() + "线程正在工作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
},"李四");
thread1.start();
thread1.join(2000);
thread1.interrupt();
thread2.start();
thread2.join(2000);
thread2.interrupt();
System.out.println("全部线程打印结束");
}
}
");
修改一下代码。。。。。
我们这回看到第一个join先让thread1插队,thread1运行2毫秒后,主线程启动,设置中断标志位,thread1停止,thread2插队,等待两毫秒后嗝屁,主线程也结束了;
2.6 获取当前线程的引用
这个之前我们就使用过了;
|--------------------------------------|------------------|
| 方法 | 说明 |
| public static Thread currentThread() | 返回当前对象的引用,类似this |
没啥好说的嗷,来段代码就好了;
java
public class Demo2 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
System.out.println(Thread.currentThread().getName());
},"线程1");
thread.start();
System.out.println(Thread.currentThread().getName());
}
}
运行结果
2.7 休眠当前线程
这个也没啥好说的,我们一直在使用
|-----------------------------------------------------------------------------|----------------|
| 方法 | 说明 |
| public static void sleep (long millis) throws InterputedException | 休眠当前线程millis毫秒 |
| public static void sleep (long millis,int nanos) throws InterputedException | 更高精度 |
不演示了嗷,马上下一期