【Java】进程&线程学习

目录

进程:

线程:

进程和线程的关系:

线程通信方法:

并行与并发:

同步异步(Synchronize, Asynchronize):

实现线程:

多线程的编程步骤:

sleep()和wait()的区别?

yield:

join:

如何正确的停止线程:

线程的暂时停止:

public void interrupt() 方法解释:

static boolean interrupted() 方法解释:

Synchronized实例方法和Synchronized阻挡

Synchronized类方法和Synchronized阻挡

Wait Set

线程的基本概念、线程的基本状态以及状态之间的关系(重要)



进程

进程可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)

进程是具有一定独立功能的程序、它是系统进行资源(内存)分配和调度的最小单位,重点在系统调度和单独的单位,也就是说进程是可以独立运行的一段程序。(进程是动态的,程序是静态的)

线程

线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。(拥有自己的栈)

进程和线程的关系:

一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。

2、系统将资源分配给进程,同一进程的所有线程共享该进程的所有资源

3、线程在执行过程中,需要协作同步(生产者消费者)。不同进程的线程间要利用消息通信的办法实现同步。

4、CPU是分给线程,即真正在CPU上运行的是线程

线程通信方法:

详情Java 线程间通信_WYSCODER的博客-CSDN博客_java线程通信
在Java的Object类中提供了wait、notify、notifyAll等方法,这些方法可以实现线程间的通信,因为Object类是所有类的基类,因此所有的对象都具有线程间通信的方法。

 void wait():调用一个对象的wait方法,会导致当前持有该对象的锁的线程等待,直到该对象的另一个持有锁的线程调用notify或者notifyAll唤醒。

void wait(long timeout):除了和wait相似,还具有超过定时的超时时间,时间到后或自动唤醒。

void wait(long timeout,int nanou):与 void wait(long timeout) 相同,不过提供了纳秒级别的更精确的超时控制。

void notify():调用一个对象的notify方法,会导致当前持有该锁的所有线程中的随机某一个线程被唤醒。

void notifyAll():调用一个对象的notifyAll方法,会导致当前持有该锁的所有线程被唤醒。

并行与并发:

单核 cpu 下,线程实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒,人能感知到的是0.1秒)分给不同的线程使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是同时运行的 。总结为一句话就是: 微观串行,宏观并行 。

一般会将这种线程轮流使用 CPU 的做法称为并发, concurrent。

多核 cpu下,每个核(core) 都可以调度运行线程,这时候线程可以是并行的。

同步异步(Synchronize, Asynchronize):

需要等待结果返回,才能继续运行就是同步

不需要等待结果返回,就能继续运行就是异步


实现线程

 1、继承Thread类

 2、实现Runnable接口 

 3、实现Callable接口,重写call方法(有返回值)

 4、线程池

1和2最大的区别是:

因为Java里面是单继承的,继承Thread类方式将单继承这个位置给占了,只能去实现接口,不能再去继承别的类了。实现Runnable接口这种方式不影响继承类也不影响实现其他接口。

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread());
    }
}
public class MyRunnable implements Runnable{
//Java是单继承的,使用Runnable接口不影响单继承
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
        MyThread myThread = new MyThread();
        MyRunnable runnable = new MyRunnable();
        Thread myThread1 = new Thread(runnable);
        //使用runnable仍然需要使用Thread去new

多线程的编程步骤:

1、第一步:创建资源类,在资源类创建属性和操作方法

2、第二步:在资源类中操作方法 

    1、判断(设置条件,比如容器有限)

    2、业务代码(干活)

    3、通知(notify或者notifyAll)

3、第三步:创建多个线程,调用资源类的操作方法。  (比如:生产者线程生产了蛋糕放到盘子panzi.putCake(),和消费者线程吃蛋糕panzi.getCake())


线程的创建中Thread(String name)name是给创建的线程签名。

只有start()才可以启动线程,线程中的run()方法只是普通的方法。

如果在一个线程中没有start(),直接run()就只是简单的顺序执行调用run()方法而已。

让线程暂时停止可以选择sleep()方法(nanos是精确到纳秒)。比如Thread.sleep(1000),当前线程睡眠1秒。需要知道的是,1秒后,线程是回到可执行状态,并不是执行状态,什么时候执行那是由虚拟机来决定的。所以sleep(1000)并不是在睡眠1秒后立即执行。sleep()方法并不会释放对象锁。


sleep()和wait()的区别?

sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,将执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。

wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。


yield:

解释它之前,先简述下,多线程的执行流程:多个线程并发请求执行时,由cpu决定优先执行哪一个,即使通过thread.setPriority(),设置了线程的优先级,也不一定就是每次都先执行它

Thread.yield();表示暂停当前线程,执行其他线程(包括执行yield这个线程), 执行谁由cpu决定

yield这个方法是让当前线程回到可执行状态,以便让具有相同优先级的线程进入执行状态(包括这个执行yield的线程,因为其也在可执行状态)

public static native void yield();

- Yield是一个静态的原生(native)方法

- Yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。

- Yield不能保证使得当前正在运行的线程迅速转换到可运行的状态

- 它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态。

join:

在某些情况下,如果子线程里要进行大量的耗时的运算,主线程可能会在子线程执行完之前结束,但是如果主线程又需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()。

a.join(),在API中的解释是,在B线程中调用a.join(),堵塞当前线程B,直到A执行完毕并死掉,再执行B。//Waits for this thread to die.

在join()方法内设定超时,使得join()方法的影响在特定超时后无效。当超时时,主方法和任务线程申请运行的时候是平等的。然而,当涉及sleep时,join()方法依靠操作系统计时,所以你不应该假定join()方法将会等待你指定的时间。

public final void join() throws InterruptedException

像sleep,join通过抛出InterruptedException对中断做出回应。

join()方法使用示例

public class JoinExample {

   public static void main(String[] args) throws InterruptedException {

      Thread t1 = new Thread(new Runnable() {

         public void run() {

            System.out.println("First task started");

            System.out.println("Sleeping for 2 seconds");

            try {

               Thread.sleep(2000);

            } catch (InterruptedException e) {

               e.printStackTrace();

            }

            System.out.println("First task completed");

         }

      });

      Thread t2 = new Thread(new Runnable() {

         public void run() {

            System.out.println("Second task completed");

         }

      });

      t1.start(); 

      t1.join(); Main线程等待t1线程执行完毕,在执行下面的操作

      t2.start();

   }

}

Output:

First task started

Sleeping for 2 seconds

First task completed

Second task completed


如何正确的停止线程:

java中有三种停止线程方法

1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

2、使用stop方法方法强行终止线程,但是不推荐使用这个方法,因为stop不安全而且是已经被废弃的方法(加上了注解@Deprecated,废弃方法指的是不推荐使用的方法但是依然可以调用),还有suspend和resume都是废弃的方法。

3、使用interrupt方法中断线程

interrupt()方法仅仅使线程中打了一个停止的标记,并不是真的停止线程。

this.interrupted() 测试当前线程是否已经中断。

this.isInterrupted() 测试线程是否已经中断。

中断线程

线程的thread.interrupt()方法是中断线程,将会设置该线程的中断状态位,即设置为true,之后的结果:线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身,并不是一定中断这个线程。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。



线程的暂时停止:

参考:

什么时候会抛出InterruptedException,如何处理InterruptedException_liao0801_123的博客-CSDN博客_interruptedexception
InterruptedException_王胖胖儿的博客-CSDN博客_interruptedexception

try{

    Thread.sleep(1000);

}catch(InterruptedException e){

}

sleep方法的调用被放在try-catch里面,这是因为sleep方法可能会抛出一个称为InterruptedException的异常。catch是用来取消线程处理时的异常。

同样可能抛出异常的还有 wait(),join()

“半路唤醒”被Thread sleep暂停的线程,则可调用该线程的interrupt方法抛出异常,通过catch到异常以后在catch代码块中完成想要完成的操作。

public void interrupt() 方法解释:

代替废弃的stop()方法[暴力停止线程],不直接中止线程,而是传递给目标线程一个“应该关闭”的信号,由目标线程自行处理。

Thread.suspend, Thread.stop这些方式,是因为由于没有使用可中断机制而被Deprecated。

“应该关闭”的信号的具体体现:

    当目标线程处于阻塞状态,会抛出InterruptedException,而不会将标示位设置为true。

    当目标线程处于非阻塞状态,会将标示位设置为true,不会抛出异常。

举例:当用户发起一项请求,后端的代码正在进行(可能此任务的代码运行时间比较长),这个时候用户想取消这个请求,如果直接调用stop()方法,则此线程会“戛然而止”,我们都知道,web的请求绝大情况下都会有对于数据库的操作,然后此时线程退出,则事务没有提交,这就会造成数据不一致的情况;或者该线程正在持有redis中的锁,那么这样的话,就会造成锁不能及时的释放,所以要使用interrupted()方法,但是这还不够,我们需要在代码中适当的位置检查中断请求,使用Thread.interrupted()静态方法,如果返回true,则进行相应的取消处理。

static boolean interrupted() 方法解释:

作用:测试当前线程是否已被中断:返回中断标示位,并且重置中断标示位为false。

Synchronized实例方法和Synchronized阻挡

假设现在有一个类型如下的synchronized实例方法
synchronized void method(){
    …
}
在功能上和下面以synchronized阻挡为主的方法有异曲同工之妙。
void method(){
        s y nchronized(this){
            …
       }
}
换句话说synchronized方法是使用 this锁 去做线程的共享互斥。
this 指的是当前对象实例本身,所以,所有使用 synchronized(this) 方式的方法都共享同一把锁。

Synchronized类方法和Synchronized阻挡

假设现在有一个类型如下的synchronized的类方法,synchronized类方法有限制同时只能让一个线程执行。这部分和synchronized实例方法一样,但是两者是有不同的。
Class Something{
    static synchronized voidmethod(){
           …
     }
    static  synchronized void method2() {
     }
}
在功能上和下面以synchronized阻挡为主的方法有异曲同工之妙。
Class Something{
    static void method(){
       synchronized(Something.class) {
             …
         }
    }
}
换句话说synchronized的类方法是使用该类的 类对象 的锁去做线程的共享互斥。Something.class是对应Something类的java.lang.Class类的实例。
为了使加锁的范围缩小,可以自定义一个锁对象,在加锁时只对该对象加锁。
public class Something {
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    private Object lock3 = new Object();
    //这里synchronized关键字拿到的锁是对象object的锁
    //所有需要这个对象的锁的方法都不能同时执行。
    //这是最常用的高并发场景下要锁住某个方法所用的操作。锁住同一个变量的方法块共享同一把锁。
    public void lockMethod1() {
        synchronized (lock1) {
        }
    }

    public void lockMethod2() {
        synchronized (lock1) {
        }
    }

    public void lockMethod3() {
        synchronized (lock2) {
        }
    }

    // this
    public synchronized void method1() {
    }

    public void method2() {
        synchronized (this) {
        }
        // ...
    }

    // Something.class
    public static synchronized void staticMethod1() {
    }

    public static synchronized void staticMethod2() {
        synchronized (Something.class) {
        }
    }
}

Wait Set

每个实例都有个线程的休息室 wait set
Wait方法——把线程放入wait set
使用wait方法时,线程便进入wait set,假设现在已经执行如下语句:obj.wait();
则目前的线程停止执行,进入实例obj的wait set.这个操作称为:线程在obj上wait().(这个obj不是线程而是对象)
语句wait()与this.wait()意义相同
故执行wait的线程就会进入this的wait set.此时就变成了在this上wait.
如欲执行wait()方法,线程需获取 锁定 (这是规则)。
但是当线程进入wait set时,已经 释放 了该实例的锁定。
wait set中的线程只能被以下四种方式唤醒:
1、其他线程对该线程执行notify()方法。
2、其他线程执行notifyAll()方法。
3、其他线程对该线程执行interrupt方法,以中断方式唤醒。
4、wait方法已经到期。

Notify方法——从wait set拿出线程

      使用notify()(通知)方法时,可以从wait set拿出一个线程。

obj.notify();(这个obj也是对象,不是线程)则从wait set里的线程中挑出一个,唤醒这个线程。被唤醒的线程便退wait set。

Notify后的线程

    被notify唤醒的线程不是在notify后立即执行,因为在notify的那一刻,执行notify 的线程还握着锁定不放,所以其他线程无法获取该实例的锁定。

Notify如何选择线程

      假设执行notify方法时,wait set里面正在执行的线程不止一个。规格并没有注明此时该选择哪一个线程。究竟是选择等待线程里面的第一个,随机选择或是另以其他方式选择,则以java处理系统而异。

NotifyAll()方法——从wait set 拿出所有线程
obj.notifyAll()会唤醒所有留在实例obj的wait set里的线程。
跟wait方法和notify方法一样,线程必须要获取要调用实例的锁定,才能调用notifyAll方法。
被唤醒的线程便开始去获取刚才wait时释放掉的锁定,那么现在这个锁定现在是在谁的手中呢?没错,锁定就是在刚才执行notifyAll方法的程序手里,因此即使所有线程都退出了wait set,但他们仍然在去获得锁定的状态下,还是有阻挡。要等到刚才执行notifyAll方法的线程释放出锁定后,其中一名幸运儿才会实际执行。
线程要是没有锁定呢?
   若没有锁定的线程去调用wait,notify或notifyAll时,便会抛出异常              java.lang.IllegalMonitorStateException.
调用notify方法还是notifyAll方法?
   Notify方法和NotifyAll方法两者非常相似,到底该用哪一个?选择notify,因为要唤醒的线程比较少,程序处理速度要比notifyAll略胜一筹。但是选择notify时,若这部分程序处理的不好,可能会有程序挂掉的危险性,一般说来,选择notifyAll所写出来的程序代码要比选择notify可靠。除非你能确定程序员对程序代码的意义和能力限度一清二楚,否则选择notifyAll应该比较稳扎稳打
wait,notify,notifyAll是Object类的方法
obj.wait()是把现在的线程放到obj的wait set
obj.notify()是从obj的wait set里唤醒一个线程
obj.notifyAll()是唤醒所有在obj的wait set里的线程
换句话说,把wait、notify、notifyAll三者均解释为对 实例对象的wait set 的操作,说他们是对线程的操作更贴切,由于所有实例都会有wait set,所以wait、notify、notifyAll才会是Object类的方法。
虽然三者不是Thread类固有的方法,不过,因为Object类是Java所有类的祖先类,所以wait、notify、notifyAll也是Thread的方法。

线程的基本概念、线程的基本状态以及状态之间的关系(重要)

线程在Java中的状态定义(注意没有可运行状态)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值