37.1 程序(program)
是为完成特定任务、用某种语言编写的一组指令集合
简单而言:就是自己写的代码
37.2 进程
- 进程是指运行中的程序,比如启动迅雷时,就启动了一个进程,操作系统就会为该进程分配内存空间。
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程
37.3 线程
- 线程由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程
单线程:同一个时刻,只允许执行一个线程
多线程:同一个时刻,可以执行多个线程
并发:同一个时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单的说,单核CPU实现的多任务就是并发
并行:同一个时刻,多个任务同时执行。多核CPU可以实现并行
37.4 创建线程的两种方式
在 java 中线程来使用有两种方法:
- 继承 Thread 类,重写 run 方法
- 实现 Runnable 接口,重写 run 方法
例子:
继承 Thread 类
public static void main(String[] args) {
A a = new A(a);
a.start();//启动线程
//说明:当main线程启动一个子线程 A,主线程不会阻塞,会继续执行
//这时 主线程和子线程是交替执行
}
......
class A extends Thread {
/*
1.当一个类继承了 Thread 类,该类就可以当做线程使用
2.会重写 run 方法,写上自己的业务代码
3.run Thread 类 实现了 Runnable 接口的run方法
*/
@Override
public void run() {
super.run();
}
}
启动线程调用 start() 方法底层是
public synchronized void start() { start0(); }
start0()
是本地方法,是 JVM 调用,底层是 c/c++实现
真正实现多线程的效果,是 start0(),而不是 run()
实现Runnable接口
- java 是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承 Thread 类方法来创建线程显然不可能了
- java 设计者们提供了另外一个方式创建线程,就是通过实现 Runnable 接口来创建线程
public static void main(String[] args) {
B b = new B();
Thread thread = new Thread(b);
thread.start();
}
......
class B implements Runnable{
@Override
public void run() {
...
}
}
继承Thread vs 实现Runnable的区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
public static void main(String[] args) {
T t = new T();
Thread thread01 = new Thread(t);
Thread thread02 = new Thread(t);
thread01.start();
thread02.start();
}
......
class T implements Runnable{
@Override
public void run() {
...
}
}
37.5 线程的常用方法
setName()
:设置线程名称,使之与参数 name 相同getName()
:返回该线程的名称start()
:使该线程开始执行-java虚拟机底层调用该线程的 start() 方法run()
:调用线程对象 run 方法,start底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程setPriority()
:更改线程的优先级getPriority()
:获取线程的优先级sleep()
:线程的静态方法,在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)interrupt()
:中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
yield()
:线程的礼让
让出CPU,让其它线程执行,但礼让的时间不确定,所以也不一定礼让成功
join()
:线程的插队
插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
用户线程和守护线程
setDaemon(true)
设置为守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
- 常见的守护线程:垃圾回收机制
37.6 线程的生命周期
RUN
尚未启动的线程处于此状态RUNNABLE
在Java虚拟机中执行的线程处于此状态BLOCKED
被阻塞等待监视器锁定的线程处于此状态WAITING
正在等待另一个线程执行特定动作的线程处于此状态TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态TERMINATED
已退出的线程处于此状态
37.7 线程的同步机制
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
- 线程同步,可以理解为当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
同步具体方法-Synchronized
- 同步代码块
synchronized(对象) { //得到对象的锁,才能操作同步代码
//需要被同步代码;
}
- synchronized 还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m (String name) {
//需要被同步的代码
}
互斥锁
- Java 在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
- 每个对象都对应与一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象
- 关键字 synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任意时刻只能由一个线程访问
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是 this,也可以是其他对象(要求是 同一个对象)
- 同步方法(静态的)的锁为当前类本身
- 同步方法如果没有使用 static 修饰:默认锁对象为 this
- 如果方法使用 static 修饰,默认锁对象:当前类.class
两个人轮流消费例子:
//当总金额 <=0 时,不能进行消费
public class Thread1 {
public static void main(String[] args) {
GetMoney g = new GetMoney();
Thread a = new Thread(g);
a.setName("小A");
Thread b = new Thread(g);
b.setName("小B");
a.start();
b.start();
}
}
class GetMoney implements Runnable {
int total = 10000;
//private boolean loop = true;
/*public synchronized void getM(){
if (total <= 0) {
System.out.println("余额不足...");
loop = false;
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
total -= 1000;
System.out.println(Thread.currentThread().getName()+" 余额还剩" + total);
}*/
@Override
public void run() {
while (true) {
//getM();
synchronized (this) {
if (total <= 0) {
System.out.println("余额不足...");
//loop = false;
break;
}
total -= 1000;
System.out.println(Thread.currentThread().getName()+" 余额还剩" + total);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
37.8 线程的死锁与释放锁
多个线程都占用了对方的锁资源,但不肯想让,导致了死锁,在编程是一定要避免死锁的发生。
下面的操作会释放锁
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到 break、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
下面操作不会释放锁
- 线程执行同步代码块或同步方法时,程序调用
Thread.sleep()
、Thread.yield()
方法暂停当前线程的执行,不会释放锁 - 线程执行同步代码块时,其他线程调用了该线程的
suspend()
方法将该线程挂起,该线程不会释放锁
应尽量避免使用
suspend()
和resume()
来控制线程,该方法已被弃用