Thread:
jvm虚拟机的启动是单线程的还是多线程的?
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
线程有两种调度模型:
分时调度模型
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
多线程的实现
方案一:
继承Thread类 ,重写run()方法,创建自定义对象,调用start();
方案二:(一般使用方式二)
实现runnable接口,重写run()方法,
创建该对象,并且
通过Thread的代参构造,
public Thread(runnble r); 创建线程对象。
调用start()方法
为什么要重写run()方法?
run(),里面 封装的是被线程执行的代码。
run()和start()方法的区别
run():仅仅是封装被线程执行的代码,直接调用是普通方法
start():首先启动了线程,然后再由JVM去调用该线程的run()方法。
实现接口方式的好处
可以避免由于Java单继承带来的局限性。
适合多个相同程序的代码去处理同一个资源的情况,
把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
Thread类的基本获取和设置方法
public final String getName()
public final void setName(String name)
其实通过构造方法也可以给线程起名字
思考: 如何获取main方法所在的线程名称呢?
public static Thread currentThread()
这样就可以获取任意方法所在的线程名称
如何设置和获取线程优先级
public final int getPriority()
public final void setPriority(int newPriority) :传递一个int值,范围1-10之间。
优先级:1-10依次整大。1优先级最低,10优先级最高,默认为5。优先级越高,抢到的cpu概率就越大,但是要在多次运行的时候,才能看到比较好的效果。
线程状态:
线程休眠
public static void sleep(long millis)
线程加入
public final void join() :为了让该线程走完了,其他线程加入。等待该Thread线程终止时间为Millis,如果Millis时间之后还未终止,则执行下面代码。//如果已经终止了直接就执行。等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。
线程礼让
public static void yield() :暂停当前线程,并执行其他线程,保证多线程执行更和谐,但是并不保证绝对公平。
后台线程
public final void setDaemon(boolean on) :将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
中断线程
public final void stop() :让线程停止,已经过时,不建议使用。
public void interrupt() :中断线程,把线程的状态终止,并且抛出一个InterruptedException;
面试题:线程的生命周期?
线程安全问题:
相同的票出现多次
CPU的一次操作必须是原子性的
还出现了负数的票
随机性和延迟导致的
解决线程安全问题的基本思想 :
首先想为什么出现问题?(也是我们判断是否有问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境。 怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
解决线程安全问题实现1
同步代码块
格式:
synchronized(对象){需要同步的代码;}
同步可以解决安全问题的根本原因就在那个对象上。
锁对象必须是同一个对象
该对象如同锁的功能。
同步代码块的对象可以是哪些呢?
任意对象
同步的特点:
同步的前提
多个线程
多个线程使用的是同一个锁对象
同步的好处
同步的出现解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
解决线程安全问题实现2
同步方法
就是把同步关键字synchronized加到方法上
同步方法锁对象:
同步方法的锁对象是方法内部的this,不需要明确写出
如果是静态方法,同步方法的锁对象是当前类的class文件对象 obj.class
那么,我们到底使用谁?
如果锁对象是this,就可以考虑使用同步方法。
否则能使用同步代码块的尽量使用同步代码块。
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象
Lock接口
Lock void lock()
void unlock()
使用子类ReentrantLock 创建锁对象。
同步弊端
效率低
如果出现了同步嵌套,就容易产生死锁问题
死锁问题及其代码
是指两个或者两个以上的线程在执行的过程中,
因争夺资源产生的一种互相等待现象
Java等待唤醒机制
(让生产者,消费者模式的线程一进一出均匀分配)
object类中提供了三个方法:
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程
注意:wait()等待执行的时候,立刻释放锁。
唤醒的时候,从等待的地方执行。
唤醒之后,继续抢夺CPU资源。
为什么这些方法不定义在Thread类中呢?
因为这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象,
所以,这些方法定义在object类中。
线程状态转换图以及常见执行情况:
Java线程组
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组(main线程组)。
public final ThreadGroup getThreadGroup()
可以通过ThreadGroup 里面的getName(),获取线程组的名称。
我们也可以给线程设置分组
Thread(ThreadGroup group, Runnable target, String name)
Java线程池:
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newCachedThreadPool()
创建一个具有缓存功能的线程池
缓存:百度浏览过的信息再次访问
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用的,具有固定线程数的线程池
public static ExecutorService newSingleThreadExecutor()
创建一个只有单线程的线程池,相当于上个方法的参数是1
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,
可以执行Runnable对象或者Callable对象代表的线程。
ExecutorService提供了如下方法 :
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
方法步骤:
创建线程池对象
创建Runnable实例
提交Runnable实例
关闭线程池
多线程程序实现方案3
实现Callable接口
步骤和刚才演示线程池执行Runnable对象的差不多。
匿名内部类方式使用多线程
匿名内部类方式使用多线程
new Thread(){代码…}.start();
New Thread(new Runnable(){代码…}).start();
多线程常见的面试题:
1:多线程有几种实现方案,分别是哪几种?
两种。
继承Thread类
实现Runnable接口
扩展一种:实现Callable接口。这个得和线程池结合。
2:同步有几种方式,分别是什么?
两种。
同步代码块
同步方法
3:启动一个线程是run()还是start()?它们的区别?
start();
run():封装了被线程执行的代码,直接调用仅仅是普通方法的调用
start():启动线程,并由JVM自动调用run()方法
4:sleep()和wait()方法的区别
sleep():必须指时间;不释放锁。
wait():可以不指定时间,也可以指定时间;释放锁。
5:为什么wait(),notify(),notifyAll()等方法都定义在Object类中
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。
而Object代码任意的对象,所以,定义在这里面。
6:线程的生命周期图
新建 -- 就绪 -- 运行 -- 死亡
新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡
建议:画图解释。
线程状态:
新建:当一个线程产生
就绪:线程调用开始方法就进入就绪状态。
运行:当线程抢到cpu控制权,进入运行状态,执行自己的代码逻辑
等待:线程调用wait方法,进入等待状态,只有被notify之后,才会继续去抢。
睡眠:线程调用sleep方法之后,进入睡眠状态,当休眠时间结束之后,会自动唤醒,继续抢夺cpu控制权。
挂起:
阻塞:blocked,一般是等待io事件。
死亡:当代码逻辑执行完毕,线程死亡。
当一个程序开启时,cpu开启一个进程,为该进程产生一个主线程,和一个垃圾回收线程,当主线程结束的时候,整个程序就会结束。
线程的生成方式:
继承thread类。
实现runnable接口。
线程同步的方式:
synchronized方法:在方法的返回值前面上加Synchronized
synchronized修饰的代码块(对象锁):在需要同步的代码块上加synchronized(boject)关键字。
Lock:在需要同步的代码前使用lock()方法加锁,在结束同步的地方用unlock取消。
Thread常用方法:
Thread.start():开始一个线程.
Thread .sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
Thread .isAlive():测试线程是否处于活动状态。
Thread.join();等待该线程终止,如果一直未终止,当前线程将不会继续进行。
Thread .notify(),唤醒该线程。唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
Thread .holdsLock(Object obj) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
Thread .isDaemon()测试该线程是否为守护线程。
谈谈多线程:
线程的概念:
线程是程序执行流的最小单元
运行时产生一个或多个进程(process),而每一个进程又对应一至多个线程(thread),每一个线程又可以分为一至多个任务(task).
创建线程的两种实现方式:
1)继承Thread类
2)实现Runnable接口
多线程的安全隐患以及同步机制
1)使用关键字“synchronized”对资源进行封锁,此方式称作“隐式加锁”;
2)采用java内部工具类“Lock”进行“显式加锁”。
高并发、分布式、高负载、缓存方面