1.进程:
当一个程序运行起来的时候就会变为一个进程,这个进程交给操作系统去管理。并且每一个进程都有自己独立的内存空间。
2.线程:
线程是CPU执行的最小单位,在一个进程中,会包含至少一个线程,也可以包含多个。每个线程都有自己独立的任务去执行,互不干扰。
3.在Java中实现多线程的方式
1)继承Thread类,也就是说只要要继承了Thread的子类就是一个线程类。然后Thread类中的run方法,而这个run方法就是线程的核心方法。线程所要做的事情都必须在这个方法中完成。
当程序运行时,会有一个主线程启动(main),然而通过这个主线程,可以开启任意个子线程(Thread-N)。如果主线程没有启动,是不能开启子线程的。
(注意:只要run方法结束了,那么当前线程就会自动销毁,等待JVM垃圾回收)
i.获取当前正在执行的线程(Thread.currentThread().getName())
ii.线程的命名(线程对象.setName())
iii.线程的睡眠(Thread.sleep(时间:单位毫秒))
重点:
1.线程的调度
当线程启动(调用start方法)之后,并不意味着该线程就立马开始执行,而是一个就绪的状态,等待操作系统去调度执行(而调度的过程就是操作系统随机抽取某个线程,并给它分配相应的执行时间。)一旦调度了该线程,操作系统就会将此线程交给CPU去执行。如果线程在执行的过程达到了指定的时间,这时操作系统就会将该线程挂起(不管该线程有没执行完任务)。然后执行下一轮的调度。所以,多线程在执行的过程是随机的,人为无法去控制。这一切都是操作系统在管理中。
2.线程的运行状态
每一个线程在运行的时候都有固定的运行时间,如果在指定的时间内没有完成任务,操作系统就会将当前运行的线程状态进行保存(通常保存在寄存器中),当下一次操作系统再次调度的时候,就会从寄存器中取出之前执行的状态继续往下执行,而不会产生重新运行的结果。
2) 实现Runnable接口
实现Runnable接口的对象表示一个可以被线程去执行的对象(注意:它本身不是一个线程)。而继承Thread类的子类就表示一个线程类。这是他们之间最根本的区别。
术语:临界资源。凡是可以被多个线程所共享的资源都称之为临界资源(也叫作共享资源)。所以这里的runnable对象其实就是一个临界资源。因为它是可以被多个线程所共享的。更确切的说,是多个线程去共享执行这个runnable对象中的run方法。
而如果使用继承Thread类的方式,那么就表示每个线程都有自己的run方法执行,他们之间是不会相互共享的。
重点:(线程安全)
如果多个线程在使用同一个临界资源的时候,是很容易就出现共享资源的数据信息错乱,这是由于操作系统在调度线程的时候产生的。因此这个情况我们就称之为“线程安全问题”。
线程安全的解决办法:
如果想让多个线程能正确使用临界资源,那么就必须使用线程同步的机制去解决。
线程同步:所谓的线程同步就是给临界资源加锁,并且这把锁会有一把(只有一把)对应的钥匙。那么当多个线程在并发执行的时候,谁先得到这把钥匙,那么谁就有权利去执行,其他的线程由于得不到这把唯一的钥匙而被拒绝在外。
加锁使用一个关键字,叫synchronized
这个关键字可以使用在两个地方:
1. 声明在方法上,称之为同步方法。某个线程在执行同步方法的时候会先获取这把钥匙。当整个方法执行完后,就会自动释放(归还)这把钥匙,一旦释放了钥匙,其他线程就可以去竞争这把钥匙了。
例如:
public synchronized void run(){
//同步方法中的代码都是同步并且安全的
}
(注意:同步方法的钥匙是一个隐式对象,就是当前对象this)
2. 声明在代码块中,称之为同步代码块。将synchronized关键字用在方法体内部的代码中。当某个线程执行完了同步代码块之后就会自动释放这把钥匙
例如:
synchronized(key){
//这里写的代码都是同步并且安全的代码
}
(注意:同步代码块的钥匙可以是任意的Object对象,只要保证唯一即可)
备注:
1. 钥匙key,它的术语叫做锁旗标,这个锁旗标有两个状态值1和0,当某个线程竞争到了这把锁旗标之后,就会更改其状态为1,默认是为0的。那么这个线程就有了执行的权利。
2. 锁池,它是由synchronized同步锁产生的一个集合,只要没有竞争到锁旗标的线程,都会统一放入锁池中,直到获取了key的线程将锁旗标的状态变为0,锁池中的线程就会出来进行key的竞争。
4.线程协作
很多时候我们需要两个或者多个线程之间按照一定的规律或者业务逻辑来执行,这样将表示需要控制线程的执行规律和顺序。因此,我们把这个流程称之为线程协作。
最典型的那里就是生产者消费者。
重点:线程同步的wait与notify。
1.调用key的wait方法可以将当先线程放入key对应的等待队列中,这是线程将
无法继续执行,wait方法之后的代码不会被执行。同时,进入等待队列的线程将会释放锁旗标(key)。
(注意:只有进入等待队列和执行完同步方法或同步代码块的线程才会释放锁旗标)
2. 当其他线程调用了key的notify方法会随机唤醒等待队列中的一个线程。
(notifyAll将唤醒所有的线程)。唤醒之后的线程会进入到锁池,将继续
去竞争key。
因此,合理使用key的wait和notify方法,可以很好的让线程之间协调工作。
5. 线程的几种状况
1)线程的死锁
有两把锁,线程1获取了锁A,同时尝试获取锁B。线程2获取了锁B,尝试获取锁A,这样就产生了死锁。
2)线程活锁
两个线程在执行期间,因为某些条件因素使两者之间反复的相互谦让,导致程序无法继续,这种情况就是活锁。(Oracle公司官方的权威解释是:好比两个人在同时经过一个独木桥,但是桥同时只能容纳一个人通过,第一个人尝试往左边走,另一个人尝试往右边,这是两者就产生碰撞。然后第一个人就尝试往右边,另一个人又尝试往左边,因此又继续导致碰撞)活锁在执行的过程是有几率可以解开的。
3)线程的饿死
由于操作系统的调度机制,导致某个线程一直没有机会执行,甚至根本没有执行的时机,这样就导致线程饿死。饿死的线程它是没有执行的机会,但它可能不会影响程序的执行。(买票的例子)
6. 线程的优先级别
通过调用线程对象的setPriority方法来设置线程的优先级别。这个方法的参数是一个int类型的数值,代表级别,数值越高,级别越高。(注意:这个方法只是通知操作系统,让当前的线程尽量的优先执行,但最终的决定权在于操作系统本身来决定,所以不要依赖于这个方法来设置线程的优先级别)
7. 合并线程
线程A在执行的过程中,join入线程B来执行。直到线程B执行完,然后再继续执行线程A剩余的部分。
(合并线程使用线程对象的join方法)
8. 线程让步
使当前线程放弃时间片(也就是放弃本次的CPU执行权利),回到一个可运行的状态,等待操作系统的再次调度。
(放弃执行权使用Thread.yield方法)
9. 守护线程
线程分为两类,一类是用户线程,一类是守护线程。
守护线程是指在程序运行的时候在后台提供一种通用服务的线程。
最典型的守护线程就是JVM的垃圾回收线程,它在后台根据特定的垃圾回收算法执行对象的回收。守护线程都是为用户线程所服务的,如果没有任何一个用户线程在执行,那么守护线程也就没有意义,那么JVM就会自动停止。
我们也可以在程序中将某个用户线程设置为守护线程,
通过调用线程对象的setDaemon(true)方法,将其设置为守护线程