线程基础以及创建线程两种方式对比

进程与线程

进程是什么(process)

对一个程序的运行状态,以及在运行中所占用的资源(内存,CPU)的描述
一个进程可以理解为一个程序;但是反之,一个程序不能说是一个进程

进程的特点:

  1. 独立性:不同的进程之间是相互独立的,相互之间资源不共享
  2. 动态性:进程在程序中不是静止不动的,而是一直活动状态
  3. 并发性:多个进程可以在一个处理器上同时运行,互不影响

线程是什么(thread)

是进程的一个组成部分,一个进程中可以包含多个线程,每一个线程都可以区处理一项任务

进程在开辟的时候,会自动的创建一个线程,这个线程叫做主线程
一个进程包含多个线程,且至少是一个,如果一个进程中没有线程了,这个进程会被终止
多线程的执行是抢占式的,多个线程在同一个进程中并发执行任务,其实就是CPU快速的在不同的线程之间进行切换

进程与线程的关系和区别

  1. 一个程序运行后,至少有一个进程
  2. 一个进程包含多个线程,至少一个线程
  3. 进程之间是资源不共享的,但是线程之间是资源共享的
  4. 系统创建进程的时候,需要为进程重新分配系统资源,而创建线程则容易很多,因此使用多线程在进行并发任务的时候,效率比多进程高

多线程的实现

继承Thread类

Thread是所有线程类的父类,实现了对线程的抽取和封装
使用继承Thread类实现多线程的步骤:

  1. 继承Thread类,写一个Thread的子类
  2. 在子类中重写父类中的run方法,run方法就代表了这个线程需要处理的任务(希望这个线程处理什么任务,就把这个任务写到run方法中)因此,run方法也被称为线程执行体
  3. 实例化这个子类对象,即是开辟了一个线程
  4. 调用start方法,来执行这个线程需要处理的任务(启动线程)
/**
 * @description 使用Thread开辟一个线程
 * @author: T-WHONG
 * @create: 2019-03-28 17:12:02
 **/
public class ProgramMyThread {
    public static void main(String[] args) {

        //1. 实例化一个MyThread对象, 即实例化了一个线程
        MyThread t0 = new MyThread ("子线程0...");
        // 设置线程的名字
        //t0.setName("子线程0");
        // 2. 启动这个线程
        t0.start ();

        MyThread t1 = new MyThread ("子线程1...");
        //t1.setName ("子线程1");
        t1.start ();

        for (int i = 0; i < 100; i++){
            System.out.println (Thread.currentThread ().getName () + "主线程" + i);
        }
    }
}

class MyThread extends Thread{
    public MyThread() {}

    /**
     * 构造方法,在实例化一个线程的同时,设置名字
     */
    public MyThread(String name) {
        super(name);
    }

    /**
     * 把需要在这个线程中出处理的任务,写到这个run方法里
     */
    @Override
    public void run() {
        for (int i = 0; i < 100; i++){
            System.out.println (this.getName () + i);
        }
    }
}

上述代码即主线程和两个子线程争抢cpu时间片,截取部分结果片段如下:
main主线程0
main主线程1
main主线程2
main主线程3
main主线程4
main主线程5
main主线程6
子线程0…0
子线程0…1
main主线程7
main主线程8
main主线程9
main主线程10
main主线程11
main主线程12
main主线程13
main主线程14
main主线程15
main主线程16
main主线程17
main主线程18
子线程0…2
子线程0…3
子线程0…4
main主线程19
子线程1…0
main主线程20
子线程0…5
main主线程21
main主线程22
main主线程23


实现Runnable接口

实现Runnable接口, 并创建线程的步骤:

  1. 设计一个类, 实现Runnable接口, 并重写接口中的run方法, 在run方法中, 写上这个线程要处理的任务
  2. 创建Runnable实现类的对象, 并把这个对象作为Thread的target进行Thread对象的实例化, 这个Thread对象才是真正的线程对象
  3. 调用start方法, 来启动线程

/**
 * @description 通过Runnable接口实现类来开辟线程
 * @author: T-WHONG
 * @create: 2019-03-28 17:48:58
 **/
public class ProgramRunnable {
    public static void main(String[] args) {

        //实例化一个Runnable接口的实现类对象
        MyRunnable r = new MyRunnable ();

        // 开辟一个线程, 实例化一个Thread对象
        Thread t1 = new Thread (r,"Thread-1");
        Thread t2 = new Thread (r,"Thread-2");

        t1.start ();
        t2.start ();

    }
}
/**
 *设计了一个Runnable接口的实现类, 并重写了run方法
 */
class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++){
            System.out.println (Thread.currentThread ().getName () + ":" + i);
        }
    }
}

同样截取这两个子线程争抢时间片的部分结果如下:
Thread-2:0
Thread-1:0
Thread-2:1
Thread-1:1
Thread-2:2
Thread-2:3
Thread-1:2
Thread-1:3
Thread-1:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
Thread-1:10
Thread-1:11
Thread-2:4
Thread-2:5
Thread-2:6
Thread-1:12
Thread-1:13
Thread-1:14
Thread-1:15
Thread-1:16
Thread-1:17
Thread-1:18
Thread-2:7


两种方式的不同点
实现Runnable接口的方式:
1. 线程对应的任务类, 只是实现了Runnable接口, 不会对原有的继承关系产生影响
2. 多个线程可以使用同一个Runnable接口实现类对象来实例化, 非常适合多个线程访问相同的资源情况
弊端:
3. 编程稍微复杂, 不直观
4. 如果需要获取到当前线程, 只能使用 Thread.currentThread() 来获取

继承Thread的方式:
1. 编程简单, 直观,
2. 如果需要访问当前线程, 直接用this即可, 也可以用 Thread.currentThread()
3. 弊端:因为线程类继承自Thread类, 因此这个类不能再继承自其他类

实际上, 大多数需要用到多线程的情况, 都是使用的Runnable接口的方式来实现的 [推荐使用匿名内部类]

strart() 和 run() 的区别
start方法会开辟一个线程, 然后在这个新的线程中执行run中的逻辑
但是如果直接调用run(), 则表示需要在当前的线程中执行逻辑

临界资源问题

被多个线程同时访问的资源

解决临界资源问题
如果一个线程在访问一个临界资源的时候, 不允许其他的线程对这个资源进行访问
如果一个线程在访问一个临界资源, 给这个临界资源上锁, 此时, 如果有其他的线程要访问这个临界资源, 需要先判断这个资源是否已经上锁了. 如果上锁了, 则等待. 线程访问完临界资源后, 对其解锁


对象锁 : 任意的一个对象都可以被用来当做一把锁
类锁 : 任意的一个类, 也可以被当做一把锁, 语法: 类.class

同步代码段

synchronized
语法:
synchronized(锁){
//对临界资源的访问
}
逻辑:

  1. 一个线程走到这个同步代码段中, 对其上锁, 其他的线程只能在锁外
  2. 执行完这段代码, 会自动的对其解锁, 然后其他的线程争抢时间片
    注意:
  3. 一定要保证多个线程看到的锁, 是同一把锁
  4. 在同步代码段中进行线程休眠, 则这个线程会释放CPU时间片, 但是不会释放锁
// 循环卖票
while (TicketCenter.restCount > 0) {
// 对象锁
synchronized("") {

// 数量-1
TicketCenter.restCount--;
// 加一个数量判断
if (TicketCenter.restCount < 0)
return;
// 输出信息
System.out.println(String.format("售票员: %s 卖出去一张票, 剩余数量: %d", this.getName(),
TicketCenter.restCount));
}
}
// 循环卖票
while (TicketCenter.restCount > 0) {
// 类锁
synchronized(TicketCenter.class) {
// 数量-1
TicketCenter.restCount--;
// 加一个数量判断
if (TicketCenter.restCount < 0)
return;
// 输出信息
System.out.println(String.format("售票员: %s 卖出去一张票, 剩余数量: %d", this.getName(),
TicketCenter.restCount));
}
}

同步方法
其实也是用同步锁来实现, 锁住当前方法中的全部的代码(也就是将同步代码段中的要处理的共同资源用方法给封装起来,然后对整个方法进行上锁,在run方法中调用方法即可)
如果是静态方法, 这个锁是类锁, 使用当前类.class
如果是非静态方法, 这个锁是对象锁, 使用 this

死锁
每个人都拥有别人需要使用的资源, 同时又在等待其他人对资源的释放, 并且每个人在获取到自己想要的资源之前, 都不会释放已有的资源
当多个线程同时访问一个资源的时候, 可能会出现死锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值