当前位置:   article > 正文

Java进阶—并发与并行,线程和进程_java 进程和线程的区别?并行和并发的区别?了解协程么?

java 进程和线程的区别?并行和并发的区别?了解协程么?

一、并发与并行

1.并发
指两个或多个事件在同一个时间段内发生。
例如在某一个时间段内,CPU来执行任务1,然后再另一个时间段内,CPU执行任务2.任务1和任务2交替执行。相当于一个人吃两个馒头。
【即并发是交替执行】。

2.并行
指两个或多个事件在同一时刻发生(同时发生)。相当于两个人吃两个馒头。
【即并行是同时执行】。

3.并行的速度要比并发快

二、线程和进程

1.进程
指【一个内存中运行的应用程序】,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;
进程也是程序的一次执行过程,是系统运行程序的基本单位;
系统运行一个程序即是一个进程从创建、运行到消亡的过程。

硬盘:永久存储ROM
内存:所有的应用程序都需要进入到内存中执行,临时存储RAM

点击应用程序执行,进入到内存中,占用一些内存执行,【进入到内存的程序叫做进程】。
任务管理器:--->结束进程,那么就把进程从内存中删除了。
  • 1
  • 2
  • 3
  • 4
  • 5

2.线程
线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

CPU:中央处理器,对数据进行计算,指挥电脑中软件和硬件干活
CPU分类:
    AMD
    Inter:例如Inter Core(核心)i7 4核心8线程
    8线程:同时执行8个任务

例如腾讯电脑管家:点击运行,会进入到内存中,就是一个【进程】。
点击功能(病毒查杀、清理垃圾、电脑加速)执行,就会开启一条应用程序到cpu的执行路径,CPU就可以通过这个路径执行功能,这个路径有一个名字,叫
【线程】。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

线程属于进程,是进程中的一个执行单元,负责程序的执行,【即通向CPU的一条执行路径】。

单核心单线程CPU:CPU在多个线程之间做高速的切换,轮流执行多个线程,效率低、但是切换的速度很快(1/n毫秒)。

4核心8线程:有8个线程,可以同时执行8个线程,8个线程在多个任务之间做高速的切换,速度是单线程CPU的8倍(每个任务执行到的几率都被提高到了8倍)
  • 1
  • 2
  • 3

多线程的好处:

1.效率高
2.多个线程之间互不影响
  • 1
  • 2

三、总结
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。

四、线程调度

1.分时调度
所有线程【轮流使用】CPU的使用权,平均分配每个线程占用CPU的时间。

2.抢占式调度
优先让【优先级高】的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),【java使用的为抢占式调度】。

    大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:一边使用编辑器,一边使用画图等软件。
    此时,这些程序是在同时运行,“感觉这些软件好像在同一时刻运行着”。
    实际上,CPU使用抢占式调度模式,在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而CPU的在多个线程间
    切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
  • 1
  • 2
  • 3
  • 4

其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

五、主线程

主线程:执行主方法(main方法)的线程

单线程程序:java程序中只有一个线程
执行从main方法开始,从上到下依次执行

JVM执行main方法,main方法会进入到【栈内存】
JVM会找操作系统开辟一条main方法【通向CPU】的执行路径,CPU就可以通过这个路径来执行main方法,而这个路径有一个名字,叫做【main(主)线程】。

六、创建多线程程序的【第一种方式】:创建Thread类的子类

java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
  • 1

实现步骤:

    1.创建一个Thread类的子类
    
    2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
    
    3.创建Thread类的子类对象
    
    4.调用Thread类中的start方法,开启新的线程,执行run方法
       void start():使该线程开始执行,Java虚拟机调用该线程的run方法。
       结果是两个线程并发的运行,当前线程(从调用返回给start方法,即main线程)和另一个线程(执行其run方法,即创建的新线程)。
       多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。即一个线程只能调用一次。
       
   java程序属于抢占式调度,哪个线程的优先级高,哪个线程就优先执行;同一个优先级,随机选择一个执行。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

问题分析:

    JVM执行main方法,找OS开辟一条main方法指向CPU的路径,这个路径就是main线程,即主线程
    CPU通过这个线程,可以执行main方法,

    new MyThread();
    mt.start()会开辟一条通向cpu的新路径,用来执行run方法

    此时,通向CPU的路径就有两条了,CPU就有了选择的权利,执行哪条路径是随机的。
    所以就有了程序的【随机打印结果】。两个线程,一个main线程,一个新线程,一起抢夺CPU的执行权(执行时间)。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

注意事项:mt.run()和mt.start()的区别

    1.mt.run():main方法压栈执行,然后run方法再压栈执行,是单线程运行,即main线程运行,
    2.mt.start():会开辟新的栈空间,执行run方法。这才是多线程。CPU就有了选择的权利。
  • 1
  • 2

多线程的好处:

多个线程之间互不影响(因为在不同的栈空间)
  • 1

七、创建多线程程序的第二种方式:实现Runnable接口

java.lang.Runnable
    Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。
    类必须定义一个称为run的无参数方法。
    
java.lang.Thread类的构造方法:
    Thread(Runnable target) 分配新的Thread对象
    Thread(Runnable target, String name)    分配新的Thread对象
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

实现步骤:

    1.创建一个Runnable接口的实现类
    
    2.在实现类中重写Runnable接口的run方法,设置线程任务
    
    3.创建一个Runnable接口的实现类对象
    
    4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    
    5.调用Thread类中的start方法,开启新的线程执行run方法
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

实现Runnable接口创建多线程的好处:

1.避免了单继承的局限性
    一个类只能继承一个类(即只能有一个父类),类继承了Thread类就不能继承其他类了
    实现了Runnable接口,还可以继承其他的类,实现其他的接口

2.增强了程序的扩展性,降低了程序的耦合性(解耦)
    实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
    实现类中,重写了run方法:用来设置线程任务
    创建Thread类对象,调用start方法:用来开启新线程
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

八、Tread类的常用方法

1.获取线程的名称

1.使用Thread类中的方法getName()
        String getName():返回该线程的名称

2.可以先获取当前正在执行的线程,然后使用线程中的方法getName()获取线程的名称
        static Thread currentThread():返回对当前正在执行的线程对象的引用
  • 1
  • 2
  • 3
  • 4
  • 5

2.设置线程的名称:(了解)

1.使用Thread类中的方法setName(名字)
    void setName(String name):改变线程的名称,使之与参数name相同。
    
2.创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
    Thread(String name):分配新的Thread对象。

3.sleep
	
	public static void sleep(long millis)
	使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)毫秒数结束之后,线程继续执行
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

九、匿名内部类方式实现线程的创建

匿名:没有名字
内部类:写在其他类内部的类

匿名内部类作用:简化代码
    把子类继承父类,重写父类的方法,创建子类对象合在一步完成
    或者是
    把实现类实现类接口,重写接口中的方法,创建实现类对象和合在一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

使用格式:
    new 父类/接口(){
        重写父类/接口中的方法
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

十、线程安全问题

线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权,让其他线程只能等待。
  • 1

解决线程安全问题的第一种方案:【使用同步代码块】

格式:
    synchronized(锁对象){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }

注意:
    1.通过代码块中的锁对象,可以使用任意的对象
    2.但是必须保证多个线程使用的锁对象是同一个
    3.锁对象作用:可以把同步代码块锁住,只让一个线程在同步代码块中执行

同步技术的原理:
    使用了一个锁对象,这个锁对象叫同步锁,也叫对象监视器
    3个线程一起抢夺CPU的执行权,谁抢到了谁执行run方法进行卖票
        t0抢到了CPU的执行权,执行run方法,遇到synchronized代码块,这时t0会检查
        synchronized代码块是否有锁对象,发现【有】,就会获取到锁对象,进入到同步中执行。

        t1抢到了CPU的执行权,执行run方法,遇到synchronized代码块,这时t1就会检查
        synchronized代码块是否有锁对象,发现【没有】,t1就会进入【阻塞状态】,会一直
        等待t0线程归还锁对象,一直到t0线程执行完同步中的代码,会把锁对象归还给同步代码块,
        t1才能获取到锁对象进入到同步中执行。
    总结:
        同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步

同步保证了只有一个线程在同步中执行共享数据,保证了安全。

但是频繁的判断锁,获取锁,释放锁,程序的效率会降低。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

解决线程安全问题的第二种方案:【使用同步方法】

使用步骤:
    1.把访问了共享数据的代码抽取出来,放在一个方法当中
    2.在方法上添加synchronized修饰符

格式:
    修饰符 synchronized 返回值类型 方法名称(参数列表){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }
    
同步方法也会把方法内部的代码锁住,只让一个线程执行

同步方法的锁对象:就是实现类对象,即 new RunnableImpl(),也就是this
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

解决线程安全问题的第三种方案:【Lock锁】

java.util.concurrent.locks.Lock接口

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

Lock接口中的方法:
    void lock():获取锁
    void unlock():释放锁

Lock接口的实现类:
    java.util.concurrent.locks.ReentrantLock implements Lock

使用步骤:
    1.在成员位置创建一个ReentrantLock对象
    2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
    3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

十一、线程状态

1.NEW:至今尚未启动的线程处于这种状态
新建状态
new Thread();
new Thread子类();

2.RUNNABLE:正在Java虚拟机中执行的线程处于这种状态
运行状态

3.BLOCKED:受阻塞并等待某个监视器锁的线程处于这种状态
阻塞状态
阻塞状态和运行状态之间是可以相互转换的,多个线程之间相互抢夺CPU的执行时间

4.WAITING:无限期地等待另一个线程来执行某一特定操作的线程处于这种状态
无限(永久)等待状态
Object.wait():永久睡眠,不会自动唤醒
Object.notify():唤醒

5.TIME_WAITING:等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态
休眠(睡眠)状态:可以自动唤醒
计时等待
Object.wait(long)
Thread.sleep(long)

进入到TimeWaiting(计时等待)有两种方式

1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态

2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

唤醒的方法:
    void notify():唤醒在此对象监视器(锁对象)上等待的单个线程。
    void notifyAll():唤醒在此对象监视器上等待的所有线程
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

6.TERMINATED:已退出的线程处于这种状态
死亡状态
run()方法结束

7.总结

1.TIMED_WAITING和WAITING两种状态都属于冻结状态

2.阻塞状态:具有CPU的执行资格,等待CPU空闲时执行

3.休眠状态:放弃CPU的执行资格,CPU空闲时,也不执行
  • 1
  • 2
  • 3
  • 4
  • 5

8.Object类中的方法

void wait():在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待

void notify():唤醒在此对象监视器(锁对象)上等待的单个线程,会继续执行wait方法之后的代码

注意:
	1.不同线程必须使用同步代码块包裹起来,保证等待和唤醒只有一个在执行
    2.同步使用的锁对象必须保证唯一
    3.只有锁对象才能调用wait和notify方法
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

十二、线程池

1.线程池就是一种容器–>集合(ArrayList、HashSet、LinkedList、HashMap)

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

2、使用流程.

当程序第一次启动的时候,创建多个线程,保存到一个集合中.就和队列一样。

当我们想要使用线程的时候,就可以从集合中取出线程使用。

如果ArrayList集合:
    Thread t = list.remove(0);  //返回的是被移除的元素(线程只能被一个任务使用)
如果是LinkedList集合:
    Thread t = linked.removeFirst();
  • 1
  • 2
  • 3
  • 4

当我们使用完毕线程,需要把线程归还给线程池:

list.add(t);
或者
linked.addLast(t);
  • 1
  • 2
  • 3

在JDK1.5之后,JDK内置了线程池,我们可以直接使用,不需要自己在创建集合了。

3.合理使用线程池的好处:

1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多
  的内存,而把服务器累趴下(每个线程大约需要1MB内存,线程开的越多,消耗的内存也就越大,最后死机)
  • 1
  • 2
  • 3
  • 4

4.java.util.concurrent.Executors:线程池的工厂类,用来生成线程池

Executors类中的静态方法:
    static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池
    参数:
        int nThreads:创建线程池中包含的线程数量
    返回值:
        ExecutorService接口,返回的是ExecutorService接口的实现类对象,
        可以使用ExecutorService接口接收(面向接口编程)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

5.java.util.concurrent.ExecutorService:线程池接口

用来从线程池中获取线程,调用start方法,执行线程任务
    submit(Runnable task):提交一个Runnable任务用于执行,并返回一个表示该任务的Future
关闭/销毁线程池的方法
    void shutdown()
  • 1
  • 2
  • 3
  • 4

6.线程池的使用步骤:

    1.使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool,生产一个指定线程数量的线程池
    
    2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
    
    3.调用ExecutorService中的方法submit,传递线程任务,开启线程,执行run方法
    
    4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/793894
推荐阅读
相关标签
  

闽ICP备14008679号