小言_互联网的博客

简述Java线程(一)

350人阅读  评论(0)

本人小白一枚,欢迎大家一起讨论学习,如有错误,还望大家指教。

简述:

在我们阐述线程时,我们先了解下进程,进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到小王的过程。而线程是进程的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,而一个进程中可以存在多个线程,简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。我们把操作系统中的多个任务称为进程,而把程序中的多个任务称为线程。
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。

线程调度:

  • 分时调度: 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
  • 抢占式调度: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度,在电脑里我们可以自己手动设置线程的优先级。抢占式具有随机性,因为多个线程都在获取CPU的执行权,谁获取了CPU的执行权,谁就运行,明确一点,在某一个时刻,只能有一个程序在运行(多核除外),CPU在做着快速切换,切换的速度相对我们而言很快,从而我们看上去像是在同时运行。

线程使用:

创建线程有两种方法:一种是继承java.lang.Thread,另一种是实现java.lang.Runnable类
方法一:继承Thread类

  • 创建子类,继承java.lang.Thread类
  • 重写Threa父类中的run方法。
  • 创建子类对象并调用start()方法启动线程。


执行流程:当程序启动运行时,java的JVM会启动一个进程,main方法运行时会创建一个主线程,当我们创建子类线程对象并调用start方法时,另一个线程也启动了,当执行线程的任务结束了,线程会自动在栈内存释放,只有当所有线程都结束时,进程才会结束。我们可以看下图来了解下内存中的变化:start方法有两个作用:1、开始线程 2、调用run方法

Thread方法:

  • 构造方法:
    • public Thread():创建一个新的线程对象。
    • public Thread(String name):创建线程对象并指定线程名称。
    • public Thread(Runnable target):这是创建的第二种方法,我们在下面进行介绍。
    • public Thread(Runnable target, String name):同上,并指定线程名称。
  • 常用方法:
    • public String getName():获取当前线程的名称。
    • public void srart():开启线程并调用此线程的run方法。
    • public void run():此线程要执行的任务代码。
    • public static void sleep():使当前正在执行的线程以指定毫秒数暂停(暂时停止执行)。
    • public static Thread currentThread():返回对当前正在执行线程对象的引用。

方法二:实现Runnable接口

  • 定义Runnable接口的实现类,并重写该接口的run方法。
  • 创建实现类对象,并将此对象作为Thread的参数创建Thread对象。
  • 调用Thread对象的start方法类启动线程。
public class RunnableDmeo1 implements Runnable {
    @Override
    public void run() {
        for (int i = 1;i <= 5;i++) {
            System.out.println(Thread.currentThread().getName() + "---->" + i);
        }
    }
}
public class MainDemo {
    public static void main(String[] args) {
        // 创建Runnable实现类对象
        Runnable runnable = new RunnableDmeo1();
        Thread thread = new Thread(runnable);
        thread.start();
        for (int i = 1;i <= 5;i++) {
            System.out.println(Thread.currentThread().getName() + "---->" + i);
        }
    }
}

main---->1
Thread-0---->1
Thread-0---->2
main---->2
main---->3
main---->4
main---->5
Thread-0---->3
Thread-0---->4
Thread-0---->5

Thread和Runnable的区别:

继承Thread类:线程代码放在Thread子类中的run方法中。
实现Runnable接口:线程代码放在实现类中的润方法中。
实现Runnable接口比继承Thread类的好处:

  • 避免了单继承的局限性。
  • 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  • 适合多个相同的程序代码的线程去共享一个资源。
  • 线程池只能放入实现Runnable或Callable类线程,不能直接放入继承Thread的类。

扩展:在java中,每次程序运行至少启动两个线程,一个是main线程,一个是垃圾收集线程,因为每当使用java命令去执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实就是在操作系统中启动了一个进程。

线程安全:

什么是线程安全问题:就是当多条语句都在操作同一个线程共享的数据时,一个线程只执行了一部分还没有执行完,另一个线程就参与起来,导致了共享数据的错误 。解决的办法就是 对于多条操作共享数据的语句,只能让一个线程执行完,在执行的过程中,其他的线程是不可以参与进来的,java对于多线程安全问题,提供了一下三种的解决方式:

  • 同步代码块
  • 同步方法
  • 锁机制

一、同步代码块:
使用synchronized关键字对方法中操作共享数据的区块进行标识,表示对这个区块的共享数据实行互斥访问。就是最多允许一个线程拥有同步锁,谁拿到同步锁就可以进步代码块,其他线程在外面等着该锁执行完。
格式:

synchronized (同步锁) {
    需要同步操作的代码
}

同步的前提:锁对象可以是任意类型。

  • 必须要有两个或两个以上的线程。
  • 必须是多个线程使用同一个锁。

案例:解决售票问题

public class Ticket implements Runnable {
    private int ticket = 100;
    Object lock = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
                }
            }
        }
    }
}

二、同步方法
使用synchronized修饰的方法。叫做同步方法,保证A线程执行该方法的时候,其他线程同样在方法外面等待。
格式:

public synchronized void method() {
    对于会产生线程安全问题的代码
}

案例:解决售票问题

public class Ticket implements Runnable {
    private int ticket = 100;
    
    @Override
    public void run() {
        while (true) {
            sellTicket();
        }
    }

    /**
     * 使用synchronized修饰,锁对象是this
     */
    private synchronized void sellTicket() {
        if (ticket > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
        }
    }
}

同步函数是使用哪一个锁呢?
函数需要被对象调用,那么函数都有一个对象的引用,就是this,所以同步函数使用的锁就是this。
如果同步函数被静态修饰,使用的锁是什么呢?
静态进内存中,没有本类对象,但是一定有该类对象的字节码类名.class,所以静态同步方法使用的锁就是该方法所在类的字节码文件对象,即类名.class。
三、Lock锁
java.util.concurrent.locks.Lock机制提供了比synchronized代码块synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外,Lock更体现了面向对象。
Lock锁使用如下:

  • public void lock():加同步锁。
  • public void unlock():释放同步锁。
public class Ticket implements Runnable {
    private int ticket = 100;
    //创建锁对象
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            sellTicket();
        }
    }
    
    private void sellTicket() {
        // 加同步锁
        lock.lock();
        if (ticket > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在卖:" + ticket--);
        }
        // 释放锁
        lock.unlock();
    }
}

转载:https://blog.csdn.net/qq_43138206/article/details/102054233
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场