线程与进程

单线程与多线程

单线程:先吃饭,然后学习,最后玩游戏

多线程:边吃饭,边学习,边玩游戏

线程和进程

在操作系统中,使用进程是为了使多个程序能并发执行,以提高资源的利用率和系统吞吐量。在操作系统中再引入线程,则是为了减少系统开销,使计算机操作系统具有更好的并发性。

由于进程是一个资源的拥有者,因而在创建、撤销和切换中,系统必须为之付出较大的系统开销。所以,系统中的进程数目不宜过多,进程切换的频率也不宜过高,这也就限制了系统并发性的进一步提高。

进程是资源分配的基本单位,所有与该进程有关的资源,如打印机,输入的缓冲队列等都被记录在进程控制块中,以表示该进程拥有这些资源或正在使用它们。与进程相对应,线程是进程内一个相对独立的、可调度的执行单元。线程属于某一个进程,并与进程内的其他线程一起共享进程的资源。

线程是操作系统中的基本调度单元,所以每个进程在创建时,至少需要同时为该进程创建一个线程,线程也可以创建其他线程。

多线程好处

  1. 系统开销小

创建和撤销线程的系统开销,以及多个线程之间的切换,都比使用进程进行相同操作要小的多。

  1. 方便通信和资源共享

如果是在进程之间通信,往往要求系统内核的参与,以提供通信机制和保护机制。而线程间通信在同一进程的地址空间内,共享主存和文件,操作简单,无须系统内核参与。

  1. 简化程序结构

用户在实现多任务的程序时,采用多线程机制实现,程序结构清晰,独立性强。

线程三状态

线程是相对独立的、可调度的执行单元,因此在线程的运行过程中,会分别处于不同的状态。通常而言,线程主要有下列几种状态。

  1. 就绪状态:线程已经具备运行的条件,等待调度程序分配 CPU 资源给这个线程运行。
  2. 运行状态:调度程序分配 CPU
  3. 资源给该线程,该线程正在执行。 阻塞状态:线程正等待除了 CPU 资源以外的某个条件符合或某个事件发生。

关系如下:
在这里插入图片描述

定义线程类的两种方式

直接继承 Thread 类

class 类名 extends Thread{
    //属性
    //其他方法
    public void run() { // 重写 `Thread` 类中的 run() 方法
        //线程需要执行的核心代码
    }
}
Thread 线程对象名 = new Thread(对象名);

实现 Runnable 接口

class 类名 implements Runnable{
    //属性
    //其他方法
    public void run() { // 实现Runnable接口中的 run() 方法
        //线程需要执行的核心代码
    }
}
Runnable 实现类名 对象名 = new  Runnable实现类名();

比较:

哪种方式比较好?

Runnable,继承Thread类就不能继承其他类,接口可以

线程操作常用方法

void start():使该线程开始执行,Java 虚拟机负责调用该线程的 run() 方法。
void sleep(long millis):静态方法,线程进入阻塞状态,在指定时间(单位为毫秒)到达之后进入就绪状态。
void join():只有当前线程等待加入的线程完成,才能继续往下执行。
void isAlive():判定该线程是否处于活动状态,处于就绪、运行和阻塞状态的都属于活动状态。
void setPriority(int newPriority):设置当前线程的优先级。
int getPriority():获得当前线程的优先级。

守护线程

守护线程是为其他线程的运行提供便利的线程。Java 的垃圾收集机制的某些实现就使用了守护线程。

守护线程会在所有非守护线程结束后自动强制终止,而不是等待其它线程执行完毕后才终止

设置线程为守护线程需要在线程启动前通过setDaemon(true)设置 并且可以使用 isDaemon() 方法的返回值( true 或false )判断一个线程是否为守护线程。

数据不一致问题

当一个数据被多个线程存取的时候,通过检查这个数据的值来进行判断并执行操作是极不安全的。因为在判断之后,有可能因为 CPU时间切换或阻塞而挂起,挂起过程中这个数据的值很可能被其他线程修改了,判断条件也可能已经不成立了,但此时已经经过了判断,之后的操作还需要继续进行。这就会造成逻辑的混乱

针对这种情况,Java 提供了同步机制,来解决控制共享数据的问题,可以使用 synchronized 关键字确保数据在各个线程间正确共享。

synchronized

线程对象在访问 synchronized 代码块前,会先主动尝试获取锁对象。并且只有成功的获取到了对象锁之后,线程对象才能执行synchronized 代码块。此外, synchronized 代码块可以让对象锁在同一时间内,只能被一个线程对象获取到。

对象锁机制

代码结构如下:

synchronized(obj){
//同步代码块
}

实例代码如下

private static class ShareThread1 implements Runnable {
        public void run() {
            //获取对象锁lock
            synchronized (lock) {
                while (data < 10) {
                    try {
                        Thread.sleep(1000);
                        System.out.println(data++);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

同步方法

代码结构如下:

访问修饰符 synchronized 返回类型 方法名{
//同步方法体内代码块
}

示例代码如下:

private static synchronized void doTask() {
        for (int i = 0; i < 10; i++) {
            System.out.println("正在输出:" + i);
        }
    }

死锁定义

线程死锁是指两个或两个以上的线程互相持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁。

死锁原因

  1. 系统资源不足。如果系统的资源充足,所有线程的资源请求都能够得到满足,自然就不会发生死锁。
  2. 线程运行推进的顺序不合适。
  3. 资源分配不当等。

死锁条件

  1. 互斥条件:一个资源每次只能被一个线程使用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

只要系统发生死锁,这四个条件就必然成立;反之,只要破坏四个条件中的任意一个,就可以避免死锁的产生。解决这类死锁问题最有效的方法是破坏循环等待条件。

线程间协作

JDK 的 Object 类提供了 void wait()、void notify()、void
notifyAll()三个方法,解决线程之间协作的问题。语法上,这三个方法都只能在 synchronized
修饰的同步方法或者同步代码块中使用,否则会抛出异常。下面是这三个方法的简单介绍。

void wait():当前线程等待,等待其他线程调用此对象的 notify() 方法或 notifyAll() 方法将其唤醒。
void notify():唤醒在此对象锁上等待的单个线程;如果有多个等待的线程,则随机唤醒一个。
void notifyAll():唤醒在此对象锁上等待的所有线程。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

( ̄o ̄) =>(= ̄ ρ ̄=)

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

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

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

打赏作者

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

抵扣说明:

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

余额充值