java多线程基础学习归纳总结

一、基本概念:

1.1、什么是进程

进程是资源分配的基本单位,是程序执行的一个实例。

程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入到进程就绪队列中,

进程调度器选中它的时候就会为它分配CPU执行时间,程序开始得到运行。

1.2、什么是线程

线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。

一个进程可以由多个线程组成,同一个进程下的线程间共享该进程的资源,每个线程有自己的堆栈,和局部变量。

线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。

1.3、两者的区别与优劣

区别:

:线程与资源的分配无关,它属于某一个进程,并与进程内的其他线程共享进程的资源(全局变量、静态变量等数据)。

:每个进程都有自己一套独立的资源,供其内的所有线程共享。

优劣:

:创建一个线程的开销比创建一个进程要小很多,CPU切换一个线程的花费远比进程要小很多。

:线程之间的通信更方便,进程之间的通信需要以通信的方式(IPC)进行

:但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

二、生命周期

java多线程状态图:

java多线程的五种状态:

2.1、新建状态(New)

当程序通过new关键字来创建一个线程之后,例如:

Thread thread = new Thread("test");

此时线程处于新建状态,但已经有了相应的内存空间和其他资源 。

2.2、就绪状态(Runnable)

当调用了线程的start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。

2.3、运行状态(Running)

如果处于就绪状态的线程获得了CPU执行权,开始执行run()方法的线程执行体,则该线程处于运行状态,只有处于就绪状态中的线程才有机会进入运行状态。

2.4、阻塞状态(Blocked)

在线程运行的过程中,出于某种原因,线程失去了CPU的执行权而进入了阻塞状态,当被阻塞线程的阻塞解除后就会进入就绪状态再次等待CPU的调度才有机会再进入运行状态。

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

等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

同步阻塞 : 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

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

2.5、终止状态(Dead)

线程执行完了或因异常退出了run()方法,该线程结束生命周期。

三、实现方式

Thread类的中的三种构造方法:

Thread(String name)

Thread(Runnable target)

Thread(Runnable target,String name)

参数:target 称为被创建线程的目标对象。创建目标对象target的类负责实现 Runnable接口,给出该接口中run()方法的方法体。

2.1、继承Thread方式

定义一个线程类,它继承Thread类并重写其中的run()方法,此时初始化这个类的实例是taeget可以为null,用这种方法创建将不能再继承其他类。

public class MyThread extends Thread{
    @Override
    public void run() {

        for (int i =0;i<10;i++){
            //获取当前线程名
            System.out.println(getName()+":"+i);
        }
    }
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}

2.2、实现Runnable接口方式

public class MyThread extends Thread{
    @Override
    public void run() { 
        
        for (int i =0;i<10;i++){
            //获取当前线程名
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}

运行截图:

2.3、Callable 方式

public class CallableTest {

    public static void main(String[] args) {

        CallableDemo cd = new CallableDemo();
        // 执行 Callable 方式,需要 FutureTask 实现类的支持
        // FutureTask 实现类用于接收运算结果, FutureTask 是 Future 接口的实现类
        FutureTask<Integer> ft = new FutureTask<Integer>(cd);
        Thread t = new Thread(ft);
        t.start();
        try {
            System.out.println(ft.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
    static class CallableDemo implements Callable<Integer>{

        @Override
        public Integer call() {
            int i =0;
            while (i<100){
                i++;
            }
            return i;
        }
    }
}

2.4、三者的比较

继承方式
优点可直接this获取当前线程,缺点不能多继承;
采用实现Runnable、Callable接口的方式
优点:1、线程类只是实现了Runnable接口与Callable接口,还可以继承其他类。
2、在这种方式下,多个线程可以共享一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,较好地体现了面向对象的思想。
3、Callable方式有返回值

缺点:如果需要访问当前线程,则必须使用Thread.currentThread()方法。

四、常用方法

4.1、Thread.currentThread()

返回当前正在执行的线程对象的引用

4.2、isAlive()

测试当前线程是否在活动

4.3、interrupt()

若此时调用线程的interrupt()方法,将线程中断标记设置为true,处于阻塞状态,中断标记将被清除同时产生一个IntertuptedException异常,将异常放在适合的位置将会终止线程。

4.4、isInterrupted()

判断线程中段标记是否为true,默认为false,当调用线程的interrupt()方法时会isInterrupted() 返回true

4.5、sleep(long millis)

使当前执行的线程处于休眠状态(临时停止),此时线程不会释放所拥有的锁。等到指定休眠时间过过后重新进入就绪状态。

4.6、join(long millis)

 join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。

  join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。(其实join()中调用的是join(0))

join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。

源码如下:

public final void join() throws InterruptedException {
        join(0);
    }
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

4.7、yield()

yield():和sleep类似,让线程从“运行状态”到“就绪状态”,但是不会阻塞线程,只是让当前线程停一会儿,让同优先级的其他线程获得被执行的机会,但是不保证一定会被执行。yield和sleep一样不会释放同步锁。

五、线程同步

由于java的每个对象都有一个内置锁,当用synchronized关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。synchronized可以修饰方法和代码块,但是不能修饰构造器、成员变量等。

public class ThreadTTest {
    public static void main(String[] args) {
        Runnable r = new ThreadA();
        Thread a = new Thread(r, "thread-a");
        Thread b = new Thread(r, "thread-b");
        a.start();
        b.start();
    }
     static class ThreadA implements Runnable {
         @Override
         public synchronized void run() {
             for (int i =0;i<10;i++){
                 System.out.println(Thread.currentThread().getName()+i);
             }

         }
     }
}

也可以用ReentrantLock(可重入锁)来实现,

import java.util.concurrent.locks.ReentrantLock;

public class ThreadTTest {
    public static void main(String[] args) {
        Runnable r = new ThreadA();
        Thread a = new Thread(r, "thread-a");
        Thread b = new Thread(r, "thread-b");
        a.start();
        b.start();
    }
     static class ThreadA implements Runnable {
         private final ReentrantLock loc = new ReentrantLock();
         @Override
         public void run() {
             loc.lock(); //加锁
             try {
                 for (int i =0;i<10;i++){
                     System.out.println(Thread.currentThread().getName()+i);
                 }
             }finally {
                 loc.unlock(); //解锁
             }
         }
     }
}

六、线程通信

Object类提供了wait、notify、notifyAll三个方法:

wait():让当前线程放弃CPU、共享资源,处于等待阻塞状态,直到其他线程调用该同步监视器的notify(),notifyAll()方法来唤醒该线程,进入就绪状态。wait()会释放对当前线程的同步监视器的锁定。

:无时间参数的wait:一直等到,直到其他线程通知唤醒

:带时间参数的wait:等待指定时间后自动唤醒。

notify():唤醒在此同步监视器上等待的单个线程。若监视器上有很多个线程等待,则任意唤醒一个。

notifyAll():唤醒在此同步监视器上等待的所有线程。

Java.lang.Object提供的这三个方法只有在synchronized方法或代码块中才能使用,否则会报出java.lang.IllegalMonitorStateException异常

七、线程中断

当线程调用了yield、join、sleep等方法进入阻塞状态,若此时调用线程的interrupt()方法,将线程中断标记设置为true,处于阻塞状态,中断标记将被清除同时产生一个IntertuptedException异常,将异常放在适合的位置将会终止线程。

public class MyThread extends Thread{
    @Override
    public void run() {
        int i =0;
        while (!isInterrupted()){
            System.out.println(getName()+":"+i++);
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.start();
        Thread.currentThread().sleep(10);
        System.out.println(t.isInterrupted());
        //设置线程中断标记为true
        t.interrupt();
        System.out.println(t.isInterrupted());
    }
}

八、守护线程

Java的线程分为两种:用户线程和守护线程(后台线程),可以用isDaemon()方法来区别:如果返回false则为用户线程,否则为守护线程,JVM的垃圾回收线程就是典型的守护线程(后台线程)

后台线程的特征:如果前台线程都死亡,后台线程会自动死亡。

可以用setDaemon(true)将指定线程设定为后台线程。

public class MyThread extends Thread{
    @Override
    public void run() {
        int i =0;
        while (true){
            System.out.println(getName()+":"+i++);
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.setDaemon(true);
        t.start();
        for (int i =0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

发布了17 篇原创文章 · 获赞 70 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/zzqaaasss/article/details/103384683
今日推荐