【Java:线程与进程 详解+案例】

进程与线程

1.什么是进程

进程就是正在运行的程序,它会占用对应的内存区域,由CPU进行执行与计算

1.1进程的特点

独立性

进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间

动态性

进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中的指令集合,程序加入了时间的概念后称为进程。

并发性

多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响

2.什么是线程

线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位. 一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。

特性:抢占式运行(CPU在执行的时候,按照时间片来执行的,单位的时间片是抢占是执行),资源共享性

2.1线程与进程的关系

进程与线程的关系

2.2进程和线程的区别

进程是一个应用程序,是独立的

线程是进程中最小的基本单位

一个进程由多个线程组成

进程有独立性和互斥性

线程有抢占式资源共享特性

3.并发和并行

并发:同时发生,轮流交替执行

并行:同时执行

4.创建线程的两种方式方式

4.1继承Thread类

重写Thread中的run方法,实现该子类,然后调用start()方法

class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 500; i++) {
            System.out.println("我是myThread1线程:" + i);
        }
    }
}
class MyThread2 extends Thread {
    @Override
    public void run() {//run方法中写功能代码  就是一个线程中执行的一个功能
        for (int i = 0; i < 500; i++) {
            System.out.println("我是mythread2线程:" + i);
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        //官方手册中说,要去实例化Thread的子类,并启动线程
        MyThread1 myThread1 = new MyThread1();
        //启动线程 使用start方法  在主线程中开启子线程
        myThread1.start();
        MyThread2 myThread2 = new MyThread2();
        myThread2.start();
        //一个是MyThread1线程  一个是主线程(main)  一个垃圾回收机制线程
        for (int i = 0; i < 500; i++) {
            System.out.println("主函数线程:" + i);
        }
    }
}
4.2实现Runnable接口

使用一个类去实现Runnable接口,创建这个类的对象,然会调用start()方法

class MyThread3 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("MyThread3:" + i);
        }
    }
}
class MyaThread4 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("MyThread4:" + i);
        }
    }
}
public class Demo2 {
    public static void main(String[] args) {
        MyThread3 myThread3 = new MyThread3();
        //Thread(Runnable target)  参数是Runnable这个接口对象
        //分配一个新的 Thread对象。
        Thread thread = new Thread(myThread3);
        thread.start();
        MyaThread4 myaThread4 = new MyaThread4();
        Thread thread1 = new Thread(myaThread4);
        thread1.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main主线程:" + i);
         }
    }
}

5.线程下面的方法

构造方法:

方法名作用
Thread()分配一个新的Thread对象,无构造方法
Thread(Runnable target)分配一个新的Thread对象,有参构造方法
Thread(Runnable target,String name)分配一个新的Thread对象,重命名

线程方法:

返回值方法名作用
static ThreadcurrentThread()返回对当前正在执行的线程对象的引用
StringgetName()返回此线程的名称。
voidsetName(String name)将此线程的名称更改为等于参数 name
intgetPriority()返回此线程的优先级。
voidsetPriority(int newPriority)更改此线程的优先级。设置优先并不一定优先,只是增加了执行的概率。最小值是1,最大值是10,默认的是5
static voidsleep(long millis)使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread thread = Thread.currentThread();
                thread.setName("MyThread线程");
                System.out.println(thread.getName());
                thread.setPriority(1);
                System.out.println(thread.getPriority());
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 10; i++) {
                    System.out.println("吃大盘鸡"+i);
                }
            }
        }).start();

        Thread thread = Thread.currentThread();
        thread.setName("主线程");
        thread.setPriority(10);
        System.out.println(thread.getPriority());
        System.out.println(thread.getName());
        Thread.sleep(1000);
        for (int i = 0; i < 10; i++) {
            System.out.println("吃大米饭"+i);
        }
    }
}
10
主线程
MyThread线程
1
吃大米饭0
吃大米饭1
吃大米饭2
吃大米饭3
吃大米饭4
吃大米饭5
吃大米饭6
吃大米饭7
吃大米饭8
吃大米饭9
吃大盘鸡0
吃大盘鸡1
吃大盘鸡2
吃大盘鸡3
吃大盘鸡4
吃大盘鸡5
吃大盘鸡6
吃大盘鸡7
吃大盘鸡8
吃大盘鸡9

6.线程的同步和锁

6.1线程同步代码块(多用)

作用:把出现线程安全问题的核心代码给上锁。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

语法格式:

synchronized(锁对象) {
    多条语句操作共享资源的代码
}

线程锁的对象要求:

原则上:锁对象只要对于当前并发的线程来说是同一个对象就可以了

规范上:建议使用共享资源作为锁对象。

对于实例方法建议使用this作为锁对象,此时this应该代表共享资源对象才优雅(例如账户对象)

对于静态方法建议使用字节码类名.class对象作为锁对象。

6.2线程同步方法

作用:把出现线程安全问题的核心方法给上锁。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行

语法格式:

修饰符 synchronized 返回值类型 方法名称(形参列表) {
    多条语句操作共享资源的代码
}
6.3Lock锁

为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。

方法名称作用
public ReentrantLock获得Lock锁的实现类对象
void lock()获得锁
void unlock()释放锁

案例:

class MyLock implements Runnable{
    private Lock lock = new ReentrantLock();
    private int title = 20;
    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//上锁
                if (title > 0) {
                System.out.println(Thread.currentThread().getName() + ",正在销售:" + title);
                    title--;
                } else {
                    System.out.println("卖完啦");
                    break;
                }
            } catch (Exception e) {
                e.getMessage();
            } finally {
                lock.unlock();//释放锁
            }
        }
    }
}
public class Test4 {
    public static void main(String[] args) {
        MyLock myLock = new MyLock();
        Thread thread1 = new Thread(myLock,"线程1");
        thread1.start();
        Thread thread2 = new Thread(myLock,"线程2");
        thread2.start();
        Thread thread3 = new Thread(myLock,"线程3");
        thread3.start();
    }
}
线程1,正在销售:20
线程3,正在销售:19
线程3,正在销售:18
线程3,正在销售:17
线程3,正在销售:16
线程3,正在销售:15
线程3,正在销售:14
线程3,正在销售:13
线程3,正在销售:12
线程3,正在销售:11
线程3,正在销售:10
线程3,正在销售:9
线程3,正在销售:8
线程3,正在销售:7
线程3,正在销售:6
线程3,正在销售:5
线程3,正在销售:4
线程3,正在销售:3
线程3,正在销售:2
线程3,正在销售:1
卖完啦
卖完啦
卖完啦
6.4死锁

1.死锁是指两个或两个以上的进程在执行过程中,由于竞争资源(锁)或者由于彼此通信而造成的一种阻塞的现象。

2.若无外力作用,它们都将无法推进下去。

3.然后这个时候系统就处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

产生死锁的必要条件:

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用。

2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

3.请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

4.循环等待,即存在一个等待循环队列:p1要p2的资源,p2要p1的资源,就形成了等待环路。

死锁的现象:

多线程间由于竞争资源导致互相僵持的一种状态

synchronized嵌套请求是死锁的一种形式

7.线程通信(生产者消费者模式)

多个线程因为在同一个进程中,所以互相通信比较容易的。
线程通信的经典模型:生产者与消费者问题。生产者负责生成商品,消费者负责消费商品。生产不能过剩,即消费不能没有。一般是生产->消费,生产-消费这样一种模型。

注意:
线程通信通常是多个线程在操作同一个资源才需要进行通信。
线程通信必须先保证线程安全,否则毫无意义,代码也会报错!

为了体现生产和消费过程中的等待和唤醒,有以下几个方法(Object类中)

方法名称作用
void wait()让当前线程等待,直到另一个线程调用该对象的notify()方法或者notifyAll()方法
void notify()唤醒正在等待对象监视器(锁对象)的单个线程
void notifyAll()唤醒正在等待对象监视器(锁对象)的所有线程

上述方法必须使用锁对象(对象监视器)进行调用

案例:

生活中例子:

​		卖家:  汽车厂商

​		买家: 咱们75名学生

​		张启想买一辆 比亚迪汉   , 告知汽车厂商我要买车。这个张启会进入倒等待状态

​		等到比亚迪厂家造完完以后,再通知张启来提车。如果比亚迪厂家有现车,张启就直接提车。

​		如果产品需要生产的话,消费者进入到阻塞状态

​		如果产品不需要生产的话,消费者直接购买

class Goods {
    private String name;//名字
    private double price;//价格
    private boolean isProduct;//是否有这个商品, true 需要生产
    //false   不需要生产商品


    public Goods(String name, double price, boolean isProduct) {
        this.name = name;
        this.price = price;
        this.isProduct = isProduct;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public boolean isProduct() {
        return isProduct;
    }

    public void setProduct(boolean product) {
        isProduct = product;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "name='" + name + '\'' +
                ", price=" + price +
                ", isProduct=" + isProduct +
                '}';
    }
}
//消费者线程
class Customer implements Runnable {
    private Goods goods;

    public Customer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while (true) {
            synchronized (goods) {
                //一直消费
                //goods.isProduct()  true  需要生产 没有商品
                //false  不需要生产的
                if (!goods.isProduct()) {
                    System.out.println("消费者购买了:" + goods.getName() + ",价格为:" + goods.getPrice());
                    //购买完以后 商品没了  isProduct 是true  没有商品了
                    goods.setProduct(true);
                    //唤醒生产者去生产
                    goods.notify();
                } else {
                    //需要生产  消费者进入阻塞
                    try {
                        goods.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        }

    }
}
//生产者线程
class Productor implements Runnable {
    private Goods goods;

    public Productor(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        int count = 0 ;
        while (true) {//一直生产商品
            synchronized (goods) {
                if (goods.isProduct()) {//true  需要生产者
                    //造车,奇数造一种车, 偶数的话造另外一种车
                    if (count % 2 == 0) {//偶数
                        goods.setName("玛莎拉蒂");
                        goods.setPrice(200);
                    } else {
                        goods.setName("五菱宏光");
                        goods.setPrice(400);
                    }
                    //生产完以后一定要记得 将标记改false
                    goods.setProduct(false);
                    System.out.println("生产者生产了:" + goods.getName() + ",价格为:" + goods.getPrice());
                    count++;
                    //生产者完了以后,人家消费者在等待这你呢
                    //唤醒消费者
                    goods.notify();
                } else {
                    //不需要生产车,你就等着就行
                    try {
                        goods.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }
}
public class Demo2 {
    public static void main(String[] args) {
        Goods goods = new Goods("东风", 89, false);
        //false不需要生产的  谁阻塞 生产者阻塞
        //最好是让生产者线程先执行
        Productor productor = new Productor(goods);
        new Thread(productor).start();
        Customer customer = new Customer(goods);
        new Thread(customer).start();
        /**
         * 消费者购买了:东风,价格为:89.0
         * 生产者生产了:玛莎拉蒂,价格为:200.0
         * 消费者购买了:玛莎拉蒂,价格为:200.0
         * 生产者生产了:五菱宏光,价格为:400.0
         * 消费者购买了:五菱宏光,价格为:400.0
         * 生产者生产了:玛莎拉蒂,价格为:200.0
         * isProduct = false
         * 走了else  代码是生产者阻塞了 消费在睡了一秒之后立马执行
         * 看消费者   有商品 买走了,之后   isProduct=true
         * 唤醒生产者了,
         * 生产者生产了:玛莎拉蒂,价格为:200.0  isProduct=false
         *
         */
    }
}

8.线程状态(生命周期)

1.新建状态(New 创建线程对象) : 当线程对象创建后就进入了新建状态.如:Thread t = new MyThread();
2.就绪状态(Runnable start方法): 当调用线程对象的start()方法,线程即为进入就绪状态.
(处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了t.start()此线程立即就会执行)
3.运行状态(Running): 当CPU调度了处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态。(就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态状态执行,先得处于就绪状态)
4.阻塞状态(Blocked 无法获得锁对象): 处于运状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行.
根据阻塞状态产生的原因不同:
阻塞状态又可以细分成三种:
(1)等待阻塞:运行状态中的线程执行
wait()方法
,本线程进入到等待阻塞状态
(2)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态
(3)其他阻塞:调用线程的**sleep()**或者join()或发出了I/O请求时,线程会进入到阻塞状态.当sleep()状态超时.join()等待线程终止或者超时或者I/O处理完毕时线程重新转入就绪状态
**5.死亡状态(Dead):**线程执行完了或者因异常退出了run()方法,该线程结束生命周期

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值