1.什么叫线程:
线程就是程序中单独顺序的流控制。线程本身不能运行,它只能用于程序中。Java中如果我们自己没有产生线程,那么系统就会给我们产生一个线程,这个线程叫主线程,main方法就在主线程运行。
2.线程与进程的区别:
(1)多个进程的内部数据和状态都是完全独立的,而多线程是共享一块内存空间和一组系统资源,有可能互相影响。
(2)线程本身的数据通常只有寄存器数据,以及一个程序执行时使用的堆栈,所以线程的切换比进程切换的负担要小。
3.什么是多线程:
多线程指在单个程序中可以同时运行多个不同的线程执行不同的任务
多线程编程的目的,就是“最大限度地利用CPU资源”,当某一线程的处理不需要占用CPU而只和I/O等资源打交道时,让需要占用CPU资源的其他线程有机会获得CPU资源。从根本上来说,这就是多线程编程的最终目的。
4.Java实现线程:
(1)继承Thread类并重写run方法
(2)实现Runnable接口并实现run方法(创建一个Thread类的实例,入参为Runnable接口的构造方法,如果用无参构造方法,将为空执行)
执行实例的start方法,启动线程。start方法首先为线程的执行准备好系统资源,然后再调run方法。
5.对于单核cpu来说,某一时刻只能有一个线程在执行(微观串行),从宏观角度来看,多个线程在同时执行(宏观并行)。对于双核或者双核以上的CPU来说,微观也是并行的。
6.停止线程的推荐方式(不能使用stop方法)
run方法执行完毕,线程自然消亡
7.线程的生命周期
(1)创建状态
(2)可运行状态
(3)不可运行状态
(4)消亡状态
状态转换图
8.synchronized关键字
(1)当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
JAVA中的每个对象都有一个锁(lock)或者叫监视器(monitor),当访问某个对象的synchronized方法(或者synchronized块)时,表示将该对象上锁,此时,其他对象都无法访问这个synchronized方法了,直到之前的那个线程执行方法完毕后(或者抛出了异常),那么将该对象的锁释放,其他线程才有可能再去访问该synchronized方法。
注意:synchronized关键字锁的是对象,就是说,实例A有2个synchronized方法method1和method2,如果有1条线程正在访问method1,则其他线程无法再访问其他的synchronized方法/块(包括method1和method2)。(如果不采用这种机制,其他synchronized方法也能修改成员变量,synchronized关键字将变得毫无意义)
(2)当synchronized关键字修饰一个静态方法的时候,则锁的对象是该对象所属的Class对象,其他的分析如上述分析类似
注意:锁Class对象的时候,并不会锁定当前实例,就是说,如果一个实例访问普通的synchronized方法,另一个实例访问静态的synchronized方法,两个方法会并行执行
(3)synchronized块
synchronized(this) {
// TODO somethings
}
括号里对象就是要锁定的对象,很经常是锁定this对象
意思就是,如果括号里的对象是锁定的,则不会执行synchronized块的代码(并不是说不会执行锁定对象的synchronized方法,假设括号里的对象是Object类型对象,如果被锁定了,一样不执行synchronized块代码)
(4)synchronized的问题
synchronized方法/块处理的事太久,如果很多线程在访问,则等待的时间会非常久,synchronized方法/块没办法告诉用户不要再等了,所以说,synchronized是个比较重量级的同步控制机制
此时,需要java的并发包(java.util.concurrent)解决这个问题
java.util.concurrent包有个机制,线程来了,可以设置超时时间,如果超过超时时间,会返回等待超时
(5)synchronized的代码实例请参考文章 https://blog.csdn.net/mweibiao/article/details/80338240
9.死锁(deadlock)
两个线程A/B,A线程掌握着实例1的锁,要访问实例2的synchronized方法,但是实例2的锁被B所掌握着,然而B这时候又要访问实例1的synchronized方法,两个线程一直等着对方释放实例1/实例2的锁,造成程序无法进行下去,这种现象称为线程访问的死锁
代码示例:https://blog.csdn.net/mweibiao/article/details/80343521
避免死锁的方法:
(1)避免一个线程同时获取多个锁
(2)避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
(3)尝试使用定时锁,用lock.tryLock(timeOut)来代替使用内部锁机制
(4)队友数据库锁,加锁和解锁必须在一个数据库链接里,否则会出现解锁失败的情况
10.线程间的通信
(1)wait()方法:
作用为此线程暂停执行,等到该实例的notify()/notifyAll()方法执行时,才继续执行。调用此方法,当前线程一定要获得这个对象的锁(就是要在synchronized方法/块中才能调用)
请注意,调用该方法会释放这个对象锁的所有权,线程进入阻塞状态,这点跟sleep方法不同,sleep方法是不会释放锁的,其他线程的锁实例调用了notify()/notifyAll()方法后,该线程有机会重新获得这个锁实例的拥有权(也有可能被其他线程获得),从而程序继续在wait()方法之后执行
(2)notify()/notifyAll()方法:
作用为唤醒一个正在等待的(执行了锁对象的wait()方法)线程,如果多个线程正在等待,那么会随机选择其中的一个进行唤醒。跟wait()一样,该线程只有拥有锁对象的锁时(即在synchronized方法/块中),才能调用
wait和notify方法,都指的是锁对象的方法,方法是定义在Object类中的(注意:不是定义在Thread类中的),并且wait和notify基本是在同一个类上成对出现的
notify()与notifyAll()的区别:notify执行以后,会随机唤醒一个线程,其他线程继续wait;notifyAll执行以后,会唤醒所有线程,但是这些线程会同时竞争这把锁,优先级高的获取锁的几率大,此时,所有线程都不处于wait状态了
代码参考:https://blog.csdn.net/mweibiao/article/details/80341875
11.volatile关键字
作用:使变量在多个线程间可见(但是不具备原子性,即多个线程可以同时访问)
代码参考:https://blog.csdn.net/mweibiao/article/details/76158490
ps:sychronized块/方法所使用的变量具有原子性和可见性,所以,里面的变量不需要再用volatile关键字修饰
12.HashMap与HashTable
之所以HashTable是线程安全的,是因为HashTable底层的方法都是加了synchronized关键字的,也有很多方法是调用了Collections.synchronizedMap这些类似的方法来保证线程安全。
显然,只允许一条线程方法,这个实现不满足性能的要求