Java 多线程 01

进程和线程

-------------------

进程:process,线程:thread

进程是资源分配的基本单位;线程是程序执行的基本单位。

什么是进程:是资源分配的基本单位。系统运行一个程序即是一个进程从创建,运行到消亡的过程。(任务管理器中可以看到进程)

什么是线程:线程是CPU调度和分派的基本单位

---------------------

线程是进程的一部分,一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

同一进程的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,

多核计算机(6核12线程)看到的是线程,指的是同时有12个线程可以并行

 

从 JVM 角度说进程和线程之间的关系

 

 

 从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的程序计数器、虚拟机栈** 和 本地方法栈。

 线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。

程序计数器私有:程序计数器用于记录当前线程执行的位置

虚拟机栈和本地方法栈私有:

 

 

 

说说并发与并行的区别?

  • 并发: 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
  • 并行: 单位时间内,多个任务同时执行。

 

 

为什么要使用多线程呢?

  • 线程间的切换和调度的成本远远小于进程

  • 多核 CPU 时代意味着多个线程可以同时运行
  • 多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。

多线程可能带来的问题

内存泄漏、死锁、线程不安全

 

----------------------------------------------------------

 

线程的启动方式:

继承Thread类方式,实现Runnable接口方式,Callable接口方式。

继承Thread方式:

public class ThreadMine extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"计数第"+i+"次");
        }
    }

    public static void main(String[] args) {
        ThreadMine threadMine = new ThreadMine();
        threadMine.setName("secondThread");
        threadMine.start();
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"计数第"+i+"次");
        }
    }
}

直接继承Thread类,重写run方法,new新类的实例,调用.start()即可。

实现Runnable接口的方式:

public class ThreadRun implements Runnable{
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"计数第"+i+"次");
        }
    }

    public static void main(String[] args) {
        ThreadRun threadRun = new ThreadRun();
        new Thread(threadRun,"Thread1").start();
        new Thread(threadRun,"Thread2").start();
        new Thread(threadRun,"Thread3").start();

    }
}

新类实现Runnable接口,重写run方法。new Thread类的实例,参数中放入新类,直接.start()即可。

之所以我们重写的run方法,但调用start方法,是因为静态代理模式,Thread类代理了Runnable接口。

显然采用Runnable接口重写方式有几个好处

  • 这个逻辑关系更像实现接口而非继承
  • 这样可以用同一个类(同一种run方法)开好多个新的线程

Lamada表达式

Lamada表达式来直接简写创建线程:

 

 上下文切换

线程状态

 

 

 

System.out.println(threadMine.getState().toString());//可以输出某个线程的状态

线程状态控制

如何结束线程

 

  • 用stop()和suspend()方法终止,但这两种方法已经过时了,不建议使用。原理上,stop我的理解是会解锁对象,导致有的对象发生不一致。suspend则会有导致死锁的倾向。(这里的原理还需要进一步了解)
  • 置标志位停止,其实是让run方法自行结束。注意,把这个标志位设成volatile的,这表示一个时间内只有一个线程能访问它(但是这个关键字的原理并不是加锁,而是告诉了cpu这个共享的值随时可能被其他线程修改-可见性):https://zhuanlan.zhihu.com/p/42497046
public class ThreadStatus extends Thread{
    public volatile boolean flag=true;

    public void run() {
        while (flag)
        {
            System.out.println(Thread.currentThread().getName() + "线程正在跑着");
        }
        System.out.println("线程结束了");
    }

    public static void main(String[] args) {
        ThreadStatus threadStatus = new ThreadStatus();
        threadStatus.setName("second");
        threadStatus.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadStatus.flag=false;
    }
}

 这种方式只有当run方法在运行的时候才有用,如果线程处于非运行状态(等待,计时等待,阻塞),这种方式就没用了。如果线程被阻塞,它便不能核查共享变量,也就不能停止。此时可以用interrupted()方法打破这种状态。其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,我们可以通过捕获InterruptedException异常来终止线程的执行,具体可以通过return等退出或改变共享变量的值使其退出。

具体来说,当对一个线程,调用 interrupt() 时,
① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。
② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。

interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
也就是说,一个线程如果有被中断的需求,那么就可以这样做。
① 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
② 在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)
public class ThreadInterrupt implements Runnable{
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted())
            {
                Thread.sleep(1000);//模仿线程定时等待
                System.out.println("该线程在跑着");
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        finally {
            System.out.println("该线程结束");
        }


    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new ThreadInterrupt());
        thread.start();
        Thread.sleep(3000);
        thread.interrupt();

    }
}

 当线程被I/O阻塞的时候,调用interrupt()的情况是依赖与实际运行的平台的。在Solaris和Linux平台上将会抛出InterruptedIOException的异常,但是Windows上面不会有这种异常。

所以处理方式也是在run方法中捕获异常,只是捕获的异常不一样

 

Thread.sleep()定时等待与wait()方法

好像面试常问

整体的区别其实是有四个:

1、sleep是线程中的方法,但是wait是Object中的方法。

2、sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中。

3、sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。

4、sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。

 第一点:好理解,sleep是Thread的静态方法,wait是Object类中的方法。sleep()是线程用来控制自身流程的,把自身暂停一段时间,把执行机会让给其他线程。wait方法用于线程间的通信,这个方法会使拥有该对象锁的进程等待,直到其他线程调用notify()方法,也可以加参数定时。

第二点:sleep是不会释放锁的,看如下例子

public class ThreadSleep implements Runnable{
    public static Object lock=new Object();
    public void run() {
        synchronized (lock)
        {
            System.out.println(Thread.currentThread().getName() + "在运行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "结束运行");
        }
    }

    public static void main(String[] args) {
        ThreadSleep threadSleep = new ThreadSleep();
        new Thread(threadSleep,"线程1").start();
        new Thread(threadSleep,"线程2").start();
    }
}

输出结果:

 

 线程1在sleep时并没有释放锁lock,只有它完成后线程2才能拿到锁开始执行。

public class Test {
    private final static Object lock = new Object();
    public static void main(String[] args) {
        Stream.of("线程1", "线程2").forEach(n -> new Thread(n) {
            public void run() {
                Test.testWait();
            }
        }.start());
    }
    private static void testWait() {
        synchronized (lock) {
            try {
                System.out.println(Thread.currentThread().getName()
                                   + "正在执行");
                lock.wait(10_000);
                System.out.println(Thread.currentThread().getName()
                                   + "wait结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

 第三点:依赖同步方法好理解,wait锁的是同步方法中的对象,没有同步方法怎么行。

 

由于sleep不会释放锁,容易导致死锁问题产生,更加推荐wait方法

 

线程礼让 yield

当前线程由运行状态转为就绪状态。

和sleep不一样,只会给相同/更高优先级的线程运行的机会

也不会释放锁

 

线程插队 join

据说了解一下就行

 

 

 输出线程状态的方式

 

 守护线程

 

posted @ 2020-11-24 10:24  将来的事  阅读(88)  评论(0编辑  收藏  举报