美文网首页
Java Thread类 线程

Java Thread类 线程

作者: 青果果 | 来源:发表于2018-04-23 21:25 被阅读0次

线程

Thread类在 API文档中的描述

线程是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。
每个线程都可以或不可以标记为一个守护程序。

当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,
并且当且仅当创建线程是守护线程时,新线程才是守护程序。
当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。

Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:
调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。
非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,
还是通过抛出一个传播到 run 方法之外的异常。

线程调度模型

分时调度模型

所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片

抢占式调用模型

优先让优先级高的线程使用CPU,如果线程的优先级相同,会随机选择一个
优先级高的线程获取的CPU时间片相对多一些

java使用的是抢占式调用模型

创建线程的方法

方法一 继承 Thread

继承 Thread 重写run()方法

public class ThreadDemo {
    public static void main(String args[]) {
        //方便演示直接new ,可以创建类 继承自Thread
        Thread thread = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(getName()+":"+i);
                }
            }
        };
        thread.setName("线程1");
        thread.start();
        //Thread.currentThread() 可以拿到当前所在线程
        System.out.println("main方法所在线程名字是 :"+Thread.currentThread().getName());
    }
}
运行结果

方法二,构造传入Runnable

可以避免单继承带来的局限性
适合多个相同程序的代码去处理同一个资源

       //Thread 是实现了 Runnable接口
       //Thread 带参构造  Runnable是接口,且只有一个run()方法
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });
        thread.start();

这样看,可能看不出区别,继续看

Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
        
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
运行结果,可能需要多次运行才能达到这种交替的效果

线程的优先级

线程优先级高仅仅表示线程获取的CPU时间片的几率高
因为存在随机性,所以并不是优先级高,就一定先执行

默认优先级是5,可以更改优先级,超过范围(1-10)会抛出异常

/**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

  //获取线程优先级
  thread1.getPriority();
  //设置线程优先级
  thread2.setPriority(8);

线程休眠,线程加入,线程礼让

有部分方法是静态方法,实际使用可能与下面示例不一致

        try {
            //线程休眠1秒
            thread1.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            //线程加入 等待该线程终止,才执行其他线程
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //线程礼让 暂停当前正在执行的线程对象,并执行其他线程
        thread1.yield();

守护线程

将该线程标记为守护线程或用户线程
当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用

有个例子,坦克大战,如果老窝被攻击,那就都死了
如果坦克都死了,老窝可以还活着

        //设置线程为守护线程
        thread1.setDaemon(true);
        //设置线程为守护线程
        thread2.setDaemon(true);
        thread1.start();
        thread2.start();
        //判断线程是否为守护线程
        thread1.isDaemon();
        thread2.isDaemon();

线程中断

stop()方法已经过时,因为具有不安全性
interrupt 中断线程,把线程设置为终止状态,线程在调用其他方法时抛出异常 InterruptedException
通过抛出异常来中断线程

        //中断线程
        thread1.interrupt();
        thread2.interrupt();

线程的生命周期

新建 创建线程对象
就绪 有执行资格,资源准备好,但没有执行权
运行 拿到执行权
阻塞:由于一些操作,让线程处于该状态,没有执行资格,没有执行权
而由于另一些操作却可以把它激活,激活后处于就绪状态
死亡 线程对象变成垃圾,等待被回收

线程的生命周期

线程安全问题

哪些原因会导致线程安全问题

  1. 是否多线程环境
  2. 是否有共享资源
  3. 是否有多条语句操作共享数据

可以通过同步代码块,同步方法解决,但会降低运行效率

同步代码块的锁可以是--任意对象

同步方法的锁是--this,静态同步方法的锁是--class

 //同步代码块
 synchronized (new Object()){
            ...
        }

集合想要线程安全,可以直接通过集合工具类的Collections的方法


获取线程安全的集合

JDK5之后可以用Lock锁

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作,此实现允许更灵活的结构

java.util.concurrent.locks  接口Lock

所有已知实现类: 
ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock 
//锁定和取消锁定出现在不同作用范围中时,
//必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。 
 Lock l = ...; 
     l.lock();
     try {
         // access the resource protected by this lock
     } finally {
         l.unlock();
     }

等待唤醒机制

Object类中的方法:
wait(): 唤醒在此对象监视器上等待的单个线程
notify(): |在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 |

为啥是在object类中,因为锁可以是任意对象

线程状态转换图

线程状态转换图

线程组 ThreadGroup

线程组表示一个线程的集合
线程默认情况下属于main线程组,和main线程都属于同一个组

public ThreadGroup(String name)
 //获取线程所在的线程组
 thread1.getThreadGroup();

第三种方式实现线程 Callable

public interface Callable<V>返回结果并且可能抛出异常的任务。
实现者定义了一个不带任何参数的叫做 call 的方法。

Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。
但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。

Executors 类包含一些从其他普通形式转换成 Callable 类的实用方法。

配合线程池,Callable接口,创建带返回值的线程

线程常见的面试题

1:多线程有几种实现方案,分别是哪几种?
继承Thread类
实现Runnable接口
扩展一种:实现Callable接口。这个得和线程池结合。
2:同步有几种方式,分别是什么?
同步代码块
同步方法

3:启动一个线程是run()还是start()?它们的区别?
start();
run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
start():启动线程,并由JVM自动调用run()方法

4:sleep()和wait()方法的区别
sleep():必须指时间;不释放锁。
wait():可以不指定时间,也可以指定时间;释放锁。

5:为什么wait(),notify(),notifyAll()等方法都定义在Object类中
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
而Object代码任意的对象,所以,定义在这里面。

同步异步,并行串行

  • 同步:同步很好理解,就是一段代码执行是按照顺序执行的
    上一行代码有返回结果才会执行下一行
int i = 0;
int k = functionA(i); // 同步执行 是在functionA返回结果后才执行下面的functionB方法
functionB();
  • 异步:在发起异步调用后不会马上得到结果,就执行下面一行代码

  • 串行:简单理解为单个线程执行同一时间执行单个任务,好比单行道

  • 并行:多CPU,同一时间可以执行多个任务,多行道

举例:下载一张图片耗时2秒, 如果要下载十张图片,串行的话就是一张图片一张图片的下载,
总共耗时20秒,而并行的话,如果2个线程并行,那么相当于一次同时下载两张图片,时间减半只需要10秒

并发并行

举例:
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以我认为它们最关键的点就是:是否是『同时』

链接:https://www.zhihu.com/question/33515481/answer/58849148
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在现实机器中:
单核的机器,都是并发 concurrence 执行的。
多核的机器,都是并行 parallel 中嵌套着并发 concurrence 运行的。
如果存在无限核心的机器,则所有任务都可以是并行运行的。

一个4核心的机器,同时运行4个长时间Thread,每个核心跑一个,【对于进程来说】,这就是并行运行。

一个4核心的机器,运行5个Thread,有一个核心跑了2个线程,他们在轮替要CPU执行上下文切换。 其余核心都是跑一个线程,【对于这个进程来说】,就是并行 parallel 中夹杂并发concurrence。

一个单核心的机器,无论运行多少个线程,都是concurrence,即使用了.NET中的TPL,也是concurrence。


image.png

并发:两个队列交替使用一台咖啡机
并行:两个队列同时使用两台咖啡机互不干扰
串行:一个队列使用一台咖啡机

并行和并发都可以是多个线程,就看这些线程能不能同时被多个CPU执行,
如果能,就是并行,如果不能就是并发,多个线程被一个CPU轮流执行

相关文章

网友评论

      本文标题:Java Thread类 线程

      本文链接:https://www.haomeiwen.com/subject/yjcflftx.html