一、相关概念
1、程序
为完成任务,用某种语言编写的一组指令的集合。
2、进程
进程是指运行中的程序。
进程是程序的依次执行过程,或是正在运行的一个程序,是一个动态过程:有自身产生、存在和消亡的过程。
3、线程
线程是由进程创建的,是进程的一个实体。
一个进程可以拥有多个线程,线程也可以创建线程。
4、单线程
同一时刻,只允许执行一个线程。
5、多线程
同一时刻,可以执行多个线程。
6、并发
同一时刻,多个任务交替执行,看起来像是"同时"进行,单核CPU实现多任务就是并发。
7、并行
同一时刻,多个任务同时进行。多核CPU可以实现并行。
二、线程的基本使用
1、创建线程
①继承Thread类,重写run方法
②实现Runnable接口,实现run方法
Thread子类直接使用即可,即重写其run()。
Runnable接口使用:
下图为Runnable接口的定义,可见其只有一个run()方法,并无start()等方法。
解决方法:
—代理模式
可见Thread类实现了Runnable接口
利用多态解决:
Thread类构造器:
可见,其有构造器,需要传入一个实现了Runnabel接口的类。
这样便可以将实现了Runnable接口的类变成一个Thread类的对象
然后使用Thread类的方法了。
2、机制
(可以使用
jconsole(
JVM可视化监控工具
)来监测线程,在终端输出jconsole即可
)
如下图:
①程序启动后,将自动创建一个mian线程。
②在main线程中,又启动了一个新的线程myThread(因为其继承了Thread,可以当作一个线程使用)
③这时就有两个线程,且两个线程同时执行,并发或并行,
不会阻塞。
3、为什么使用start(),而不是run()
①run()方法就是一个普通方法,调用它就是调用一个普通方法,不会开启一个线程。
②而start()方法则是会开启一个线程(源码):
如下图,调用Start方法,会先将该线程加入到group当中
然后使用
本地方法start0()方法将线程启动,该方法会调用本线程的run()方法。
4、线程方法
1、线程终止(不是自带方法)
①在线程完成任务之后,会自动退出。
②还可以通过
使用变量来
控制run方法退出的方式来停止线程,即通知方式。
比如在run()方法循环条件里使用一个变量,然后main方法可以改变该变量的值。
2、setName()
设置线程名称
3、getName()
返回线程名称
4、start()
执行该线程。
底层调用start0(),然后start0()会调用run()
5、run()
调用run()方法,一个普通方法。
6、setPriority()
更改线程优先级
7、getPriority()
获取线程优先级
8、sleep()
休眠该线程指定的时间
9、interrupt()
中断线程,并不是真正结束线程。一般用于中断休眠的线程。
一个线程在未正常结束之前, 被强制终止是很危险的事情.
因为它可能带来完全预料不到的严重后果比如会带着自己所持有的锁而永远的休眠,迟迟不归还锁等。
那么不能直接把一个线程搞挂掉, 但有时候又有必要让一个线程死掉, 或者让它结束某种等待的状态 该怎么办呢?
一个比较优雅而安全的做法是:
使用等待/通知机制或者给那个线程一个中断信号, 让它自己决定该怎么办。
10、yeild()
线程的礼让,让出CPU。
使得正在运行的线程变成就绪状态,重新变成就绪状态,然后重新竞争CPU的调度权。
但是它可能会获取到,也有可能被其他线程获取到。
或者多核,且竞争不激烈,就可能让不成功。
11、join()
线程插队。插队的线程一旦插队成功,则先执行完插入线程的所有任务。
三、用户线程和守护线程
1、用户线程
也叫工作线程,当线程任务执行完或通知方式结束。
2、守护线程
一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。
例如:垃圾回收机制
如果希望当前线程结束,子线程自动结束,只需要将子线程设为守护线程:
ChildThread.setDaemon(true); (注意,需要在线程start之前设置为守护线程)
四、线程声明周期
Thread.State枚举类中说明了以下状态:
-
NEW尚未启动的线程处于此状态。
-
RUNNABLE在Java虚拟机中执行的线程处于此状态。该状态可细化为两个状态:
-
RUNNING:运行状态
-
READY:就绪状态,等待分配资源运行。
-
-
BLOCKED被阻塞等待监视器锁定的线程处于此状态。
-
WAITING正在等待另一个线程执行特定动作的线程处于此状态。
-
TIMED_WAITING正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
-
TERMINATED已退出的线程处于此状态。
五、线程同步(Synchronized)
1.同步
在多线程编程中,一些敏感数据不允许被多个线程同时访问。
此时就使用同步访问技术,
保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
也可以理解为:
当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该
线程完成操作,其他线程才能对该地址进行操作。
2.实现同步的具体方法
①同步代码块
synchronized(对象){ //得到对象的锁,才能操作同步代码
//代码
}
②synchronized放在方法中,即同步方法
public synchronized void fun(){
}
3.互斥锁
①Java语言中引入了
对象互斥锁的概念,来保证共享数据操作的完整性。
②
每个对象都有一个称为
"互斥锁"的标记,这个标记用来保证任何一刻,只能有一个线程访问该对象。
③当某个对象使用synchronized修饰的时候,表明该对象在任意时刻只能由一个线程访问。
④局限性:导致程序执行效率下降。
⑤同步方法(非static)的锁可以是this,也可以是其他对象(要求是同一个对象)。
synchronized是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的
所有synchronized块。
⑥同步方法(static)的锁为当前类本身。
static synchronized是限制线程同时访问jvm中
该类的所有实例同时访问对应的代码块。且
一个类的所有静态方法公用一把锁。
如果在静态方法中,要使用同步代码块,则synchronized(这里填 类名.class )
4.锁的释放
①当前线程的同步方法、代码块执行完毕。
②当前线程在同步方法、代码块中遇到break、return。
③当前线程在同步方法、代码块中出现了未处理的 Error 和 Exception,导致异常结束
④当前线程在同步方法、代码块执行了wait()方法,当前线程暂停,释放锁。
以下情况不会释放锁:
①当前线程在执行同步方法、代码块时调用Thread.sleep()、Thread.yeild()方法暂停当前线程,不会释放锁。
②当前线程在执行同步方法、代码块时,
其他线程调用该线程的suspend()方法将该线程挂起,该线程不会释放锁。