进程与线程

1.什么是进程?什么是线程?

进程:是一个软件(或应用程序)。

线程:是一个进程中的一个执行单元,是进程的最小基本单位。

2.进程和线程之间的关系

进程:可以看做一个车间。

线程:可以看做一个车间中流水线上的员工。

注意:进程与进程之间的内存独立不共享。

例:A车间生产的产品和B车间生产的产品是没有关系的,二者不共享资源。

两个线程之间是什么关系?

在Java中:线程A和线程B,堆内存和方法区是内存共享的。但是栈内存是相互独立的,一个线程一个栈。

例:现在如果有5个线程,就会有5个栈空间,每个栈和每个栈之间互不干扰,是独立执行的。这是多线程的并发。

Java中的多线程机制是为了提高程序的处理效率。

进程与线程的区别总结:

根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

3.线程的生命周期

线程的生命周期,主要有五种状态:

①新建状态(New):当线程对象创建后就进入了新建状态;

②就绪状态(Runnable):当调用线程对象的start()方法,线程即为进入就绪状态;

注意:处于就绪(可运行)状态的线程,只是说明线程已经做好准备,随时等待CPU调度执行,并不是执行了start()方法此线程立即就会执行

③运行状态(Running):当CPU调度处于就绪状态的线程时,此线程才是真正的执行,即进入到运行状态;

注意:就绪状态是进入运行状态的唯一入口,也就是线程想要进入运行状态执行,先得处于就绪状态

④阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态才有机会被CPU选中再次执行;

根据阻塞状态产生的原因不同,阻塞状态又可以细分成三种:

(1)等待阻塞:运行状态中的线程执行wait()方法,此线程进入到等待阻塞状态。

(2)同步阻塞:线程在获取synchronized同步锁失败(锁被其他线程占用),它会进入同步阻塞状态。

(3)其他阻塞:调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态。方sleep()状态超时、join()等待线程终止或者I/O处理完毕是线程重新转入就绪状态。

⑤死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

4.在Java中,实现线程有两种方式

第一种方式:一个类直接继承java.lang.Thread,重写run方法

步骤:(1)创建对象,new继承线程的类;

          (2)启动线程,调用线程对象的start()方法

public class ThreadTest02 {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        // 启动线程
        t.start();
        // 这里的代码还是运行在主线程中。
        for(int i = 0; i < 1000; i++){
            System.out.println("主线程--->" + i);
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        // 编写程序,这段程序运行在分支线程中(分支栈)。
        for(int i = 0; i < 1000; i++){
            System.out.println("分支线程--->" + i);
        }
    }
}

注意:

.t.run()不会启动线程,只是普通的通过对象调用方法。不会分配新的线程。

.t.start()方法的作用是启动一个分支线程,在JVM中开辟一个新的栈空间。

第二种方式:一个类实现java.lang.Runnable接口,实现run方法。

步骤:(1)创建对象,new线程类,传入可运行的类或接口。

           (2)启动线程,调用start()方法。

// 定义一个可运行的类
public class MyRunnable implements Runnable {
	public void run(){
	
	}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
public class ThreadTest03 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable()); 
        // 启动线程
        t.start();
        
        for(int i = 0; i < 100; i++){
            System.out.println("主线程--->" + i);
        }
    }
}

// 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i = 0; i < 100; i++){
            System.out.println("分支线程--->" + i);
        }
    }
}

使用匿名内部类创建(常用):

public class ThreadTest04 {
    public static void main(String[] args) {
        // 创建线程对象,采用匿名内部类方式。
        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                for(int i = 0; i < 100; i++){
                    System.out.println("t线程---> " + i);
                }
            }
        });

        // 启动线程
        t.start();

        for(int i = 0; i < 100; i++){
            System.out.println("main线程---> " + i);
        }
    }
}

注意:第二种方式,实现接口比较常用,因为一个类实现了接口,还可以继承其它类,相对于第一种方式,第二种更灵活。

5.获取当前线程对象、名字、修改线程对象名字的几个方法

方法名作用
static Thread currentThread()获取当前线程的对象
String getName()获取当前线程的名字
void setName(String name)修改当前线程的名字

在没有设置当前线程名字的时候,线程会有默认的名字,如下:

.Thread-0

.Thread-1

.Thread-2

....

class MyThread2 extends Thread {
    public void run(){
        for(int i = 0; i < 100; i++){
            // currentThread就是当前线程对象。当前线程是谁呢?
            // 当t1线程执行run方法,那么这个当前线程就是t1
            // 当t2线程执行run方法,那么这个当前线程就是t2
            Thread currentThread = Thread.currentThread();
            System.out.println(currentThread.getName() + "-->" + i);

            
        }
    }
}

6.线程的sleep方法

方法名作用
static void sleep(long millis)让当前线程休眠milis秒

(1)该方法是静态方法

(2)参数是毫秒

(3)作用:让当前线程进入休眠,进入阻塞状态

public class ThreadTest06 {
    public static void main(String[] args) {
    	//每打印一个数字睡1s
        for(int i = 0; i < 10; i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);

            // 睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

7.Java中与线程调度有关的方法

7.1实例方法:

方法名作用
int getPriority()获得线程优先级
void setPriority(int newPriority)设置线程优先级
void join()讲一个线程合并到当前线程中,当前线程受阻,加入的线程执行到结束

说明:线程的默认优先级是5,最低优先级是1,最高优先级是10。

优先级高的线程获取CPU的时间片可能会多一点(不是绝对的)。

class MyThread1 extends Thread {
	public void doSome(){
		MyThread2 t = new MyThread2();
		t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
	}
}

class MyThread2 extends Thread{

}

7.2常量: 

常量名备注
static int MAX_PRIORITY最高优先级为10
static int MIN_PRIORITY最低优先级为1
static int NORM_PRIORITY默认优先级为5

7.3静态方法:

方法名作用
static void yield()让当前线程暂停,回到就绪状态,让给其它线程

yield()方法不是阻塞方法。是把当前线程让位,让给其它线程使用。

yield()方法的执行会让当前线程从运行状态回到就绪状态。

public class ThreadTest12 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable6());
        t.setName("t");
        t.start();

        for(int i = 1; i <= 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

class MyRunnable6 implements Runnable {

    @Override
    public void run() {
        for(int i = 1; i <= 10000; i++) {
            //每100个让位一次。
            if(i % 100 == 0){
                Thread.yield(); // 当前线程暂停一下,让给主线程。
            }
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

8.数据在多线程并发环境下会存在的安全问题

需要满足三个条件:

①多线程并发;

②有共享数据;

③共享数据有修改的可能

如何解决?

让线程排队执行(不能并发)。这种机制被称为线程同步机制。

9.线程安全

9.1synchronized 线程同步

语法格式:

synchronized(){
	// 线程同步代码块。
}

重点:synchronized后面的小括号中传的是一个数据对象,这个数据对象必须是多线程共享的数据

。才能达到多线程排队。

9.2在方法上也可以使用synchronized

synchronized出现在实例方法上,锁的一定是this,不能是其他对象。这种方式不灵活,有局限性

10.死锁

在开发中是禁止出现死锁的,但在面试的时候会问。应用场景:并发场景,多个线程。线程之间存在共享数据的时候互不相让。

死锁是一种状态,当两个线程互相持有对方所需要的资源的时候,这两个线程又都不主动释放资源,就会导致死锁。代码无法正常执行,这两个线程就会僵持住。

class DeadLock implements Runnable {
    private boolean flag;//标记
    private Object obj1;//对象1
    private Object obj2;//对象2

    public DeadLock(boolean flag, Object obj1, Object obj2) {
        this.flag = flag;
        this.obj1 = obj1;
        this.obj2 = obj2;
    }

    @Override
    public void run() {
        if (flag) {//如果flag = true 让线程1执行这个if语句里面的代码
            synchronized (obj1) {
                System.out.println(Thread.currentThread().getName() + "拿到了obj1资源");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1执行了");
                synchronized (obj2) {//想用obj2这个资源
                    System.out.println(Thread.currentThread().getName() + "拿到obj2资源");
                }
            }
        }
        if (!flag) {//如果flag=false 线程2 执行这个if语句里面的代码
            synchronized (obj2) {
                System.out.println(Thread.currentThread().getName() + "拿到了obj2资源");
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程2执行了");

                synchronized (obj1) {
                    System.out.println(Thread.currentThread().getName() + "拿到obj1资源");
                }
            }

        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = new Object();
        DeadLock deadLock1 = new DeadLock(true, obj1, obj2);
        new Thread(deadLock1, "线程1").start();
        DeadLock deadLock2 = new DeadLock(false, obj1, obj2);
        new Thread(deadLock2, "线程1").start();
    }
}

11.守护线程

11.1Java中线程分为两大类:

一类是:用户线程(非守护线程)

一类是:守护线程(后台线程)

其中最有代表性的是:垃圾回收线程(守护线程)

11.2守护线程的特点

一般情况下守护线程是一个死循环,所有的用户线程只要结束,守护线程就会自动结束。

其中,主线程main方法是一个非守护线程(用户线程)。

class MyThread implements  Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("守护线程:" + i);
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.isDaemon());//false  非守护线程
       // thread.setDaemon(true);//设置为守护线程
        Thread thread1 = new Thread(new MyThread8());
        //System.out.println(thread1.isDaemon());
        thread1.setDaemon(true);
        thread1.start();
        for (int i = 0; i < 200; i++) {
            System.out.println("主线程:" + i);
        }


    }
}

12.关于Object类下的三个方法

方法名作用
void wait()让当前对象的线程等待(会释放之前占有的锁)
void notify()唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁)
void notifyAll()唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁)

方法的详解:

①wait和notify方法不是线程对象的方法地方,在Java中任何一个Java对象都有这两个方法,因为这两个方法时Object类中自带的。

注意:wait方法和notify方法不是通过线程对象调用的

Object o = new Object();

o.wait();

以上代码的作用:让正在对象o上活动的线程进入等待状态,直到被唤醒为止。

o.wait()方法的调用会让当前线程(正在对象o上活动的线程)进入等待状态。

②notify()方法

Object o = new Object();

o.notify();

以上代码的作用:唤醒正在对象o上等待的线程。

③notifyAll()方法

Object o = new Object();

o.notifyAll();

以上代码的作用:唤醒对象o上处于等待的所有线程。

总结:

(1)wait和notify方法不是线程对象的方法,是Java对象都有的方法。

(2)wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。

(3)wait方法的作用:让正在对象上活动的线程进入等待状态,并且释放掉线程之前占有的对象的锁。

(4)notify方法的作用:让正在对象上等待的线程唤醒,只是通知,不会释放对象上之前占有的锁。

13.生产者消费者模式(wait()和notify())

13.1什么是“生产者和消费者模式”?

.生产线程负责生产,消费线程负责消费。

.生产线程和消费线程要达到均衡。

.这是一种特殊的业务需求,在这种情况下需要使用wait方法和notify方法。

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;
    }
}

class Customer implements Runnable {//消费者线程
    private Goods goods;

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

    @Override
    public void run() {
        //消费者一直消费  生产者一直生产
        while (true) {
            synchronized (goods) {
                //需要一直消费 判断商品是否有无
                // //true需要生产    false 不需要生产
                if (!goods.isProduct()) {
                    //不需要生产直接购买的
                    System.out.println("消费者购买:" + goods.getName() + ",价格为:"+ goods.getPrice());
                    //购买完以后,商品没了,商品标记为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) {
            try {
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (goods) {
                // true需要生产    false 不需要生产
                if (goods.isProduct()) {//true
                    //造车,如果是奇数的话,造玛莎拉蒂  如果是偶数的话,造劳斯莱斯
                    if (count % 2 == 0) {
                        //偶数
                       goods.setName("劳斯莱斯");
                       goods.setPrice(8.9);

                    } else {
                        //奇数
                        goods.setName("玛莎拉蒂");
                        goods.setPrice(7.6);
                    }
                    //生产者生产者完以后,将isProcut标记为
                    goods.setProduct(false);
                    System.out.println("生产者生产了:" + goods.getName() + ",价格为:" + goods.getPrice());
                    count++;
                    //生产者生产完以后 唤醒消费者。
                    goods.notify();
                } else{
                    //不需要生产的
                    try {
                        goods.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Goods goods = new Goods("红旗", 9.9, false);

        Customer customer = new Customer(goods);
        Productor productor = new Productor(goods);
        new Thread(customer).start();
        new Thread(productor).start();


        /**
         * 消费者购买:红旗,价格为:9.9
         * 生产者生产了:劳斯莱斯,价格为:8.9
         * 消费者购买:劳斯莱斯,价格为:8.9
         * 生产者生产了:玛莎拉蒂,价格为:7.6
         * 消费者购买:玛莎拉蒂,价格为:7.6
         * 生产者生产了:劳斯莱斯,价格为:8.9
         * 消费者购买:劳斯莱斯,价格为:8.9
         * 生产者生产了:玛莎拉蒂,价格为:7.6
         * 消费者购买:玛莎拉蒂,价格为:7.6
         * 生产者生产了:劳斯莱斯,价格为:8.9
         */
    }
}

14.线程池

线程池是一个容纳了多个线程的容器,其中的线程可以反复的使用。省去了频繁创建线程的对象的操作,无需反复创建线程而消耗更多的资源。

在Java中,并发编程都是通过创建线程池来实现的,而线程池的创建方式也有很多种,每个线程池的创建方式都对应了不同的使用场景,总体来说线程池的创建可以分为以下两类:

①通过ThreadPoolExecutor手动创建线程池。

②通过Executors执行器自动创建线程池。

以上两类创建线程池的方式又有7种具体的实现方法,如下:

(1)Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。

(2)Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。

(3)Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池。

(4)Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。

(5)Executors.newWorkStealingPool:创建一个抢占执行的线程池(任务执行顺序不确定)(jdk1.8添加)。

(6)创建一个可以缓存的线程池,若线程数超过任务所需,那么多余的线程会被缓存一段时间后才被回收,若线程数不够,则会新建线程。

(7)ThreadPoolExecutor:手动创建线程池的方式,它创建时最多可以设置7个参数。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

下面介绍ThreadPoolExecutor的7个参数:

①corePoolSize:核心线程数。线程池中保持最小活动数的线程数量。当线程池的线程数量小于核心线程数时,一个新的任务请求被提交上来时,不管其它线程是否处于空闲状态,都会新建一个线程来处理这个请求。

②MaximumPoolSize:最大线程数。线程池运行的最大线程数量。

③keepAliveTime:存活时间。空闲线程等待工作的时间(以纳秒为单位)。如果当前线程池中的线程数,超出的部分线程如果空闲的时长大于存活时长,那么它们将会被终止运行。当线程池不被频繁使用的时候,这提供了一种减少资源消耗的方法。

④TimeUnit:存活时间的单位。默认是纳秒,可以更改。

⑤BlockingQueue:阻塞队列。任何实现了BlockingQueue接口的实现类都可以用来传输和保存提交的任务,阻塞队列的使用和线程池大小相关。

如果运行的线程少于核心线程数,Executor总是倾向于添加一个新线程而不是排队;

如果核心线程数或更多线程正在运行(不超过最大线程数),Executor总是倾向于排队请求,而不是添加一个新线程;

如果没有达到最大线程数并且队列未满,将创建新的线程执行任务,如果线程数大于最大线程数,任务将会被拒绝。

无界队列:是一个没有预定义容量的队列,使用无界队列LinkedBlockingQueue将导致新任务一直在等待,当核心线程数的线程处于工作状态时。所以,不会有超过核心线程数的线程被创建,也就是说最大线程数是不起作用的。、

有界队列:如ArrayBlockingQueue,它能在有限的最大线程数内防止资源耗尽,但是它也更难调整和控制。

拒绝任务:在调用execute提交任务时,在Executory已经关闭或者有界队列的最大线程数和队列满的情况下会被拒绝。

⑥ThreadFactory:

⑦ RejectedExecutionHandle:

第7中方式的案例:

import java.util.LinkedList;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo5 {
    public static void main(String[] args) {
        test();
    }
    public static void test () {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        //执行任务
        for (int i = 0; i < 21; i++) {
            int index = i;
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(index + "被执行, 线程为:" + Thread.currentThread().getName());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            //threadPoolExecutor.shutdown();
        }
    }
}

以上代码的分析:

LinkedBlockingQueue:最大线程数不起作用,无界队列。

5个任务->核心5个线程        任务不会放到队列中排队,队列中是0个任务;

6个任务->核心5个线程        队列中1个任务,只要线程执行完5个以后,对于最后一个任务会随机从5个线程中抽取一个然后执行最后的线程。

12个任务->核心5个线程        队列中有7个......

ArrayBlockingQueue<>(10):有界队列,只能放10个任务,最大线程数是10

5个任务->核心5个线程        队列中没有任务;

8个任务->核心5个线程        队列中有3个任务;

12个任务->核心5个线程        队列中有7个任务,有界队列中可以放10个任务;

17个任务->5个核心已经用上了,队列中也已经存够10个,多出来两个任务,再开2个

20个任务->5个核心用上,10个队列用上,再开5个新线程。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值