多线程概述

简介: 一.线程相关概念(1)程序是为了完成特定任务、用某种语言编写的一组指令的集合。简单的说就是我们写的代码。(2)进程①进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;②进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。比如我们使用的QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。

多线程概述



一.线程相关概念


(1)程序

是为了完成特定任务、用某种语言编写的一组指令的集合。简单的说就是我们写的代码。


(2)进程

①进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;

②进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

比如我们使用的QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。


(3)线程

线程是由进程创建的,是进程的一个实体;一个进程可以拥有多个线程。例如:使用浏览器同时下载多个文件,就是一个进程有多个线程。

①单线程

同一个时刻,只允许执行一个线程

②多线程

同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口


(4)并发

同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发


9b15e76281c14d39b3959f9bde0130a1.png


(5)并行

同一个时刻,多个任务同时执行。多核cpu可以实现并行。


286b57e189c641219fd8096cf749b0a4.png


2.线程的基本使用


267e2505a94143c3bf883908a86ef117.png


2.1 创建线程的两种方式


(1)继承Thread类,重写run方法


(2)实现Runnable接口,重写run方法

①Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类方法来实现创建线程显然不可能了。

②Java设计者提供了另外一种创建线程的方法,就是通过实现Runnable接口来创建线程。实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable


2.2实现步骤

(1)通过继承Thread类


public class DemoThread01 {
    public static void main(String[] args) throws InterruptedException {
//创建Cat对象,当做线程使用
        Cat cat = new Cat();
//说明:当main线程启动一个子线程 Thread-0,主线程不会被阻塞,会继续执行
// 这时,主线程和子线程是交替执行
        cat.start();//启动线程 -> 最终会执行cat的run方法
//不能写成 cat.run();  
//因为run方法就是一个普通的方法,没有真正的启动一个线程,把run方法执行完毕,才会继续向下执行 
        for (int i = 0; i <= 10; i++) {
            System.out.println("主线程执行" + i + Thread.currentThread().getName());
            Thread.sleep(1000);
        } } }
//当一个类继承了Thread类,该类就可以当做线程使用
class Cat extends Thread {
    int times = 0;
    @Override
    public void run() {//重写run方法,写上自己的业务逻辑
        while (true) {
            System.out.println("喵喵,我是小猫咪" + (++times) + "线程名=" + Thread.currentThread().getName());
//   让线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 10) {
                break;
            } } } }


(2)通过实现Runnable接口


public class DemoThread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        //创建Thread对象,把dog对象放入Thread
        Thread thread = new Thread(dog);
        thread.start();
    } }
class Dog implements Runnable {//通过实现Runnable接口,开发线程
    int count = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("小狗汪汪叫" + (++count)+":" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);  //休眠一秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10){
                break;
            } } } }


2.3 通知线程退出

(1)当线程退出后,会自动退出

(2)还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式


public class Thread_exit {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.start();
//        如果希望main线程去控制t1 线程的终止,可以修改 loop
//        让t1 退出run方法,从而终止 t1线程 -> 通知方式
//        让主线程休眠 10秒,再通知 t1线程退出
        Thread.sleep(10 * 1000);
        t.setLoop(false);
    } }
class T extends Thread {
    private int count = 0;
    //    设置一个控制变量
    private boolean loop = true;
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
    @Override
    public void run() {
        while (loop) {
            System.out.println("我是" + (++count) + "线程名=" + Thread.currentThread().getName());
//        让线程休眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            } } } }


2.4 实现原理

(1) start()方法源码:

① public synchronized void start() {

start0();

}

② start0() 是本地方法,是JVM调用,底层是c/c++实现

真正实现多线程效果的是start0(),而不是 run

private native void start0();

*/

(2



a42ae2471d96403ea6997fb6fc91274f.png


3.线程常用方法


(1)setName: 设置线程名称,使之与参数name相同

(2)getName:返回该线程的名称

(3)start: 使该线程开始执行;Java虚拟机底层调用该线程的start0 方法

(4)run: 调用线程对象的run方法

(5)setPriority: 更改线程的优先级

(6)getPriority: 获取线程的优先级

(7)sleep: 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)

(8)interrupt: 中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠的线程

(9)yield: 线程的礼让。让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功

(10)join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务

—interrupt例子:


public class ThreadMethod {
    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.setName("老汉");
        t1.setPriority(Thread.MIN_PRIORITY);
        t1.start();//启动子线程
//        主线程打印5个hi ,然后我就中断 子线程的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi"+i);
        }
        t1.interrupt();
    } }
class T1 extends Thread {
    private int times = 0;
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "吃包子~~~~~" + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + "休眠中~~~");
                Thread.sleep(20000);//20秒
            } catch (InterruptedException e) {
//  当该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的逻辑代码
//  InterruptedException 是捕获到一个中断异常
                System.out.println(Thread.currentThread().getName() + "被interrupt了");
            } } } }


4.用户线程和守护线程


(1)用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

(2)守护线程:一般是为工作线程服务的,当所有用户线程结束,守护线程自动结束

常见的守护线程:垃圾回收机制

—把线程设置为守护线程例子


public class ThreadMethod3 {
    public static void main(String[] args) throws InterruptedException {
        T3 t3 = new T3();
//        如果我们希望当main线程结束后,子线程自动结束
//        只需将子线程设为守护线程即可
        t3.setDaemon(true);
        t3.start();
        for (int i = 0; i < 10; i++) {//主线程
            System.out.println("别卷了..."+i);
            Thread.sleep(1000);
        } } }
class T3 extends Thread{
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);//休息1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("在学Java...");
        } } }


5.线程的生命周期


Java中用Thread.State枚举表示了线程的几种状态。

public static enum Thread.State extends Enum<Thread.State>线程状态。线程可以处于下列状态之一:


(1)NEW

至今尚未启动的线程。

(2)RUNNABLE

正在 Java 虚拟机中执行的线程

(3)BLOCKED

受阻塞并等待获得同步代码块的锁的线程

(4)WAITING

无限期地等待另一个线程来执行某一特定操作

(5)TIMED_WAITING

等待另一个线程来执行取决于指定的等待时间操作

(6)TERMINATED

已退出的线程。

注意:RUNNABLE 是否真的运行取决于调度器,由操作系统内核来决定,可以细分为Ready就绪和Running运行


6.线程安全


如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

我们通过一个案例,演示线程的安全问题:

电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。

我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)

需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟

模拟票:


public class Ticket implements Runnable {
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while (true) {
if (ticket > 0) {//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
} /
/获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket‐‐);
}}}}
//测试类:
public class Demo {
public static void main(String[] args) {
//创建线程任务对象
Ticket ticket = new Ticket();
//创建三个窗口对象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
//同时卖票
t1.start();
t2.start();
t3.start();
} }


b98f95a741c6405eb33a1a055ae5e329.png


7.线程同步机制


(1)在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。


(2)也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。


(3)同步具体方法-Synchronized


①同步代码块
synchronized(对象) { //得到对象的锁,才能操作同步代码
// 需要被同步的代码;
}
②synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m(String name) {
// 需要被同步的代码
}


8.互斥锁(就是上面的对象锁)


(1)在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性


(2)每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问


(3)关键字synchronized 用来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。


(4)同步的局限性:导致程序的执行效率要降低


(5)同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)


(6)同步方法(静态的)的锁为当前类本身


注意事项:


①同步方法如果没有使用static修饰,默认锁对象为this

②如果方法使用static修饰,默认锁对象:当前类.class

例子:


//1.同步方法没有使用static修饰
public class SellTicket02 {
    public static void main(String[] args) {
        T2 t2 = new T2();
        new Thread(t2).start();//第一个线程窗口
        new Thread(t2).start();//第二个线程窗口
        new Thread(t2).start();//第三个线程窗口
    } }
class T2 implements Runnable {
    private int ticketNum = 100;//让多个线程共享 ticketNum
    private boolean loop = true;
    Object object = new Object();
    public void sell() {
        synchronized (/*this*/object) {
            if (ticketNum <= 0) {
                System.out.println("售票结束。。。");
                loop = false;
                return;
            }
//            休眠50秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票"
                    + "剩余票数=" + (--ticketNum));
        } }
    @Override
    public void run() {
        while (loop) {
            sell();
        } } }


// 2.同步方法为静态方法
public class SellTicket05 {
    public static void main(String[] args) {
        T2 t2 = new T2();
        new Thread(t2).start();//第一个线程窗口
        new Thread(t2).start();//第二个线程窗口
        new Thread(t2).start();//第三个线程窗口
    } }
class T5 implements Runnable {
    private static int ticketNum = 100;//让多个线程共享 ticketNum
    private static boolean loop = true;
    public static void sell() {
        synchronized (T5.class) {
            if ( ticketNum<= 0) {
                System.out.println("售票结束。。。");
                loop = false;
                return;
            }
//            休眠50秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票"
                    + "剩余票数=" + (--ticketNum));
        } }
    @Override
    public void run() {
        while (loop) {
            sell();
        } } }


9.线程死锁


多个线程都占用了对方的锁资源,但不肯想让,导致了死锁,在编程时一定要避免死锁的发生


public class DeadLock {
    public static void main(String[] args) {
        DemoDeadLock A = new DemoDeadLock(true);
        DemoDeadLock B = new DemoDeadLock(false);
        A.start();
        B.start();
    } }
class DemoDeadLock extends Thread {
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag;
    public DemoDeadLock(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
//业务逻辑分析:
// 1.如果flag 为T,线程A 就会先得到 o1 对象锁,然后尝试去获取 o2 对象锁
// 2.如果线程A 得不到 o2 对象锁,就会Blocked
// 3.如果flag 为F,线程B 就会先得到 o2 对象锁,然后尝试去获取 o1 对象锁
// 4.如果线程B 得不到 o1 对象锁,就会Blocked
        if (flag) {
            synchronized (o1) {//对象互斥锁,下面就是同步代码
                System.out.println(Thread.currentThread().getName() + "进入1");
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + "进入2");
                } }
        } else {
            synchronized (o2) {//对象互斥锁,下面就是同步代码
                System.out.println(Thread.currentThread().getName() + "进入2");
                synchronized (o1) {
                    System.out.println(Thread.currentThread().getName() + "进入1");
                } } } } }


10.释放锁


(1)当前线程的同步方法、同步代码块执行结束


(2)当前线程在同步代码块、同步方法中遇到break、return


(3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束


(4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁


下面操作不会释放锁:


①线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁


②线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁

提示:应尽量避免使用suspend()和resume()来控制线程,该方法不再推荐使用

目录
相关文章
|
3月前
|
Java 调度
|
3月前
|
存储 Java 调度
线程池的概述和创建
线程池的创建,构造器需要分别传入什么参数
线程池的概述和创建
|
5月前
|
Java 调度
|
存储 缓存 资源调度
多线程的概述
多线程的概述
|
缓存 Java 开发者
Java中多线程创建方式对比与线程池相关原理概述汇总(超详细)
Java中多线程创建方式对比与线程池相关原理概述汇总(超详细) 准备加入阿里开发者社区,同时也是很久没有写文章了,一时也不知道从何写起,那就先从多线程来吧,这次想把多线程与高并发的相关知识进行一下梳理,从多线程的几种创建方式开始,逐步到线程池原理分析,再到java中常用锁的使用场景与原理分析,再到高并发的处理方案,以及后面分布式锁等知识点,分成批次来进行梳理,这次先分析一下多线程的创建方式的异同点与线程池的执行原理。
1309 3
Java中多线程创建方式对比与线程池相关原理概述汇总(超详细)
|
监控 并行计算 Java
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(四)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(四)
|
Java 调度
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(三)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(三)
|
监控 Java Linux
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(二)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(二)
|
存储 Java 应用服务中间件
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(一)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)
《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(一)
《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全(三)
《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全
《JUC并发编程 - 基础篇》JUC概述 | Lock接口 | 线程间通信 | 多线程锁 | 集合线程安全(三)

热门文章

最新文章