0
点赞
收藏
分享

微信扫一扫

Java-多线程

一,多线程的概念

进程:正在运行的程序

线程:是进程中的一个执行单元(一条执行路径),一个进程中至少包含一个线程。如果一个进程中有多个线程,这样的程序就称为多线程程序。

线程的调度:

  1. 分时调度:所有线程轮流使用CPU,每个线程平均占用CPU的时间
  2. 抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么随机选择一个线程为其分配CPU的资源(随机性),Java 多线程的执行方式就是抢占式的。

注:

  1. Java 程序在没有开辟新线程的情况下也会有两个线程:主函数所在的主线程、垃圾回收线程


二,创建线程

方式一:继承 Thread 类

  1. 自定义类继承 Thread 类
  2. 重写 run() 方法,在 run() 中明确线程执行的功能
  3. 创建 Thread 的子类对象
  4. 使用这个子类对象调用 start() 方法开启线程,JVM 会自动调用重写后的 run() 方法

public class Test1 {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
MyThread mt2 = new MyThread();
mt2.start();

for(int i = 1;i <= 100;i++){
System.out.println("main方法:"+i);
}
}
}
class MyThread extends Thread{
// 重写run()方法是为了明确线程中所执行的任务
@Override
public void run() {
for(int i = 1;i <= 100;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}

方式二:实现 Runnable 接口

  1. 自定义类实现 Runnable 接口
  2. 重写 run() 方法,在 run() 中明确线程执行的功能
  3. 创建 Runnable 实现类的对象

public class Test2 {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
t.start();
Thread t2 = new Thread(mr);
t2.start();
for(int i = 1;i <= 300;i++){
System.out.println("main方法:"+i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 1;i<=300;i++){
System.out.println(Thread.currentThread()+"--"+i);
}
}
}

继承 Thread 类 和 实现 Runnable 接口的选用:

使用实现的方式,因为避免了单继承的局限性

注:同一个线程对象不能重复开启,重复开启会发生 IllegalThreadStateException

使用匿名内部类的方式创建线程并开启:

new Thread(){
public void run(){
// 线程执行的任务
}
}.start();
new Thread(new Runnable(){
public void run(){
// 线程执行的任务
}
}).start();



三,线程中的方法

  1. String getName():返回线程的名称
  2. static Thread currentThread():返回线程对象,
    Thread [ Thread - 0 , 5 , main]
  1. Thread - 0:线程的名称
  2. 5:优先级
  3. main:在主函数中开启的
  1. static void sleep(long time)


四,线程安全问题

什么是线程安全:如果有多个线程同时执行,这些线程同时操作同一个内容,程序运行后的结果和单线程运行的结果是一样的时候,就称为线程安全。

完成 3 个窗口同时卖票案例时,发现了重复票和负数票的情况,原因是多个线程在操作同一个 ticket 变量,

在某个线程通过了 if 判断后,被其他线程抢夺了 CPU 的执行权,所以 if 后的 ticket-- 操作,在一次判断后重复执行了多次。

解决方式:

  1. 同步代码块
    格式:

synchronized(锁对象){
// 可能发生线程安全问题的代码
}

注:

  1. 同步代码块中的锁对象可以是任意的
  2. 必须保证多个线程使用的锁对象是同一个
  3. 锁对象的作用:将同步代码块锁定,同一时间只允许让一个线程进入同步代码块
  1. 同步函数
    步骤:
  1. 将可能发生线程安全问题的代码抽取到一个方法中
  2. 再在这个方法上添加 synchronized 修饰符

格式:

public synchronized 返回类型 方法名(参数){
// 可能发生线程安全问题的代码
}

注:

  1. 同步函数中有锁吗?有,同步函数中的锁是 this
  2. 在使用继承 Thread 的方式去创建线程时,使用同步函数不能保证线程同步,因为每一个线程对象都有属于自己的一份 this,所以多个线程使用的不是同一个锁
    解决办法:使用 static 修饰同步函数,此时同步函数中的锁还是 this 吗?
    不是,因为 static 不能访问 this,锁是 类名.class
  1. Lock 锁:
  1. Lock 是一个接口,在 jdk 1.5 后出现,它使用了比 synchronized 更简单更广泛的锁的操作方式
  2. 方法:lock() 获取锁,unlock() 释放锁
  3. 步骤:
  1. 在成员位置创建 ReentrantLock 对象
  2. 在可能会发生线程安全问题前调用 lock() 获取锁
  3. 在可能会发生线程安全问题后调用 unlock() 释放锁


同步技术的原理:

同步技术中使用到了锁对象,这个锁对象也称为同步锁

多个线程一起抢夺 CPU 的执行权,谁抢到了,谁就能进入同步代码块,

当一个线程抢夺到了 CPU 的执行权时,会判断是否有锁对象,

如果有锁对象,就会获取到这个锁,进入同步代码块,当这个线程执行完同步代码块,就会释放锁

如果没有锁对象,会处于阻塞状态,等待其他线程执行完同步代码块,等待其他线程释放锁,才能再进行 CPU 执行权的抢夺

使用同步锁会影响程序的执行效率:

因为获取 CPU 执行权的线程,在进入同步代码块之前,需要判断是否有锁,

如果有锁还需要进行获取的动作,

在执行完同步代码块后,需要释放锁。

(判断锁,获取锁,释放锁)

五,线程池

5.1 问题

在每次使用线程时,都需要创建线程对象,操作方式非常简便,但是存在一个问题:

如果并发的线程数量很多,每个线程执行完一个时间很短的任务就结束了,这样频繁创建线程会极大的降低程序的性能。

所以我们需要想一个办法能重复利用已创建的线程,来避免线程的过多创建。

5.2 概念

线程池本质上就是一个可以存放多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作。

5.3 好处

  1. 降低资源消耗,减少了创建线程的次数,每个线程都可以被重复利用
  2. 提高程序的响应速度
  3. 提高了对线程的管理


5.4 实现步骤

  1. 使用线程池的工厂类 Executors 中的静态方法 newFixedThreadPool 创建一个包含指定线程数量的线程池 ExecutorService
  2. 创建 Runnable 接口的实现类,重写 run() 方法
  3. 调用 ExecutorService 中的 submit() 方法,传入实现类对象,该方法用于开启线程并调用 run() 方法
  4. 调用 ExecutorService 中的 shutdown() 方法,来销毁线程池,一旦被销毁,就无法再从池中获取线程对象



举报

相关推荐

0 条评论