Java学习(十) --------- 多线程


一、多线程的基本概念

1. 概述

线程指进程中的一个执行场景,也就是执行流程,那么进程和线程有什么区别呢?每个进程是一个应用程序,都有独立的内存空间。同一个进程中的线程共享其进程中的内存和资源。(共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己的。)

2. 进程与线程的区别

一个进程就是一个应用程序。在操作系统中每启动一个应用程序就会相应的启动一个进程。

线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。

3. 系统引入多线程的作用

提高 CPU 的使用率。
注:进程和进程之间的内存独立。

4. 进程引入多线程的作用

提高进程的使用率。
注:线程和线程之间栈内存独立,堆内存和方法区内存共享。一个线程一个栈。

5. 描述Java程序的执行原理

java 命令执行会启动 JVM,JVM 的启动表示启动一个应用程序,表示启动了一个进程。该进程会自动启动一个“主线程”,然后主线程负责调用某个类的 main 方法。所以 main 方法的执行是在主线程中执行的。然后通过 main 方法代码的执行可以启动其他的“分支线程”。所以,main 方法结束程序不一定结束,因为其他的分支线程有可能还在执行。

二、线程的创建与启动

1. 线程的创建

Java 虚拟机的主线程入口是 main 方法,用户可以自己创建线程,创建方式有s三种:
1.继承 Thread 类实现
2.Runnable 接口(推荐使用 Runnable 接口)

方法一 :写一个类,直接继承java.lang.Thread,重写run方法。

// 定义线程类
public class MyThread extends Thread{
	public void run(){
			
	}
}
	// 创建线程对象
	MyThread t = new MyThread();
	// 启动线程。
	t.start();

方法二 :编写一个类,实现java.lang.Runnable接口,实现run方法。

public class MyRunnable implements Runnable {
	   public void run(){
			
	   }
}
   // 创建线程对象
   Thread t = new Thread(new MyRunnable());
   // 启动线程
   t.start();

**方法三:实现Callable接口 **

这种方式实现的线程可以获取线程的返回值。上述那两种方式是无法获取线程返回值的,因为run方法返回void。

2. 线程的启动

//线程对象迪奥哟领start方法即可
t.start();

三、线程的生命周期

线程的生命周期存在五个状态:新建、就绪、运行、阻塞、死亡。

在这里插入图片描述

新建:采用 new语句创建完成
就绪:执行 start 后
运行:占用 CPU 时间
阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合
终止:退出 run()方法

四、线程的调度与控制

1. 概述

通常我们的计算机只有一个 CPU,CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。在单 CPU 的机器上线程不是并行运行的,只有在多个 CPU 上线程才可以并行运行。Java 虚拟机要负责线程的调度,取得 CPU的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,Java 使用抢占式调度模型。

2. 分类

分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型:优先级高的线程获取 CPU 的时间片相对多一些,如果线程的优先级相同,那么会随机选择一个

3. 线程优先级

线程优先级主要分三 种 : MAX_PRIORITY(最高级) ; MIN_PRIORITY(最低级) ; NOM_PRIORITY(标准) 默认

关于线程优先级的方法

void setPriority(int newPriority);设置线程的优先级
int getPriority();获取线程优先级
/*
最低优先级1
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
 */
static void yield();// 让位方法,暂停当前正在执行的线程对象,并执行其他线程yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。注意:在回到就绪之后,有可能还会再次抢到。

void join();//合并线程,当前线程可以调用另一个线程的 join 方法,调用后当前线程会被阻塞不再执行,直到被调用的线程执行完毕,当前线程才会执行

void sleep(); //设置休眠的时间,单位毫秒,当一个线程遇到 sleep 的时候,就会睡眠,进入到阻塞状态,放弃 CPU,腾出 cpu 时间片,给其他线程用,所以在开发中通常我们会这样做,使其他的线程能够取得 CPU 时间片,当睡眠时间到达了,线程会进入可运行状态,得到 CPU 时间片继续执行,如果线程在睡眠状态被中断了,将会抛出 IterruptedException

void interuptt();//如果我们的线程正在睡眠,可以采用 interrupt 进行中断

五、线程同步与数据安全

共享同一个对象启动两个线程,两个线程会对同一个对象进行操作,数据会出现异常,即数据不安全。

1. 什么时候数据在多线程并发的环境下会存在安全问题呢?

条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。

2. 如何解决线程安全问题?

用排队执行解决线程安全问题, 即线程同步。

3. 何为线程同步机制?

线程同步,指某一个时刻,指允许一个线程来访问共享资源,线程同步其实是对对象加锁,如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上的问题,我们只要保证线程一操作 s 时,线程 2 不允许操作即可,只有线程一使用完成 s 后,再让线程二来使用 s 变量。

4. 异步编程模型与同步编程模型

异步编程模型 :
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)

同步编程模型 :
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。给共享对象加线程锁。

5. Java中三大变量有哪些会线程安全问题?

三大变量

实例变量:在堆中。
静态变量:在方法区。
局部变量:在栈中

线程安全

局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)局部变量在栈中。所以局部变量永远都不会共享。

实例变量在堆中,堆只有1个。静态变量在方法区中,方法区只有1个。堆和方法区都是多线程共享的,所以可能存在线程安全问题。

局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。

因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。
ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable是线程安全的。

6. 如何给线程共享对象加锁?

第一种:同步代码块
	   灵活
	   synchronized(线程共享对象){
		     同步代码块;
	   }

第二种:在实例方法上使用synchronized
	   表示共享对象一定是this
	   并且同步代码块是整个方法体。
		
第三种:在静态方法上使用synchronized
	   表示找类锁。
	   类锁永远只有1把。
	   就算创建了100个对象,那类锁也只有一把
//synchronized 是对对象加锁
//采用 synchronized 同步最好只同步有线程安全的代码
//可以优先考虑使用 synchronized 同步块
//因为同步的代码越多,执行的时间就会越长,其他线程等待的时间就会越长
//影响效率

7. 在开发中一定要用线程同步机制吗?

不是,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。

代替方案

第一种方案:尽量使用局部变量代替实例变量和静态变量。

第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)

第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

六、守护线程

1. 概述

从线程分类上可以分为:用户线程(以上讲的都是用户线程),另一个是守护线程。守护线程是这样的,所有的用户线程结束生命周期,守护线程才会结束生命周期,只要有一个用户线程存在,那么守护线程就不会结束,例如 java 中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,它才会结束。

2. 如何设置守护线程

t.setDaemon(true);
//设置为守护线程后,当主线程结束后,守护线程并没有把所有的数据输出完就结束了,也即是说守护线程是为用户线程服务的,当用户线程全部结束,守护线程会自动结束

七、定时器

1. 概述

间隔特定的时间,执行特定的程序。可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

2. 定时器用法

//第一种方法:设定指定任务task在指定时间time执行 schedule(TimerTask task, Date time)
public static void timer1() {
   Timer timer = new Timer();
   timer.schedule(new TimerTask() {
       public void run() {
            System.out.println("-------设定要指定任务--------");
       }
    }, 2000);// 设定指定的时间time,此处为2000毫秒
}

八、生产者与消费者模式

1. 概述

生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间里生产数据,而消费者取走数据。阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

2. wait() 与 notify() 方法实现生产者与消费者模式

注 : wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。wait方法和notify方法不是通过线程对象调用。

Object o = new Object();
o.wait();

/*表示:
让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。*/

Object o = new Object();
o.notify();

/*表示:
唤醒正在o对象上等待的线程。
还有一个notifyAll()方法:
这个方法是唤醒o对象上处于等待的所有线程*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在森林中麋了鹿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值