一、守护线程(Daemon Thread)
1.1 简介
比如垃圾回收线程,就是最典型的守护线程。
Java程序入口就是由JVM启动main
线程,main
线程又可以启动其他线程。当所有线程都运行结束时,JVM退出,进程结束。
如果有一个线程没有退出,JVM进程就不会退出。所以,必须保证所有线程都能及时结束。
但是有一种线程的目的就是无限循环,例如,一个定时触发任务的线程:
如果这个线程不结束,JVM进程就无法结束。问题是,由谁负责结束这个线程?
然而这类线程经常没有负责人来负责结束它们。但是,当其他线程结束时,JVM进程又必须要结束,怎么办?
答案是使用守护线程(Daemon Thread)。
守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。
因此,JVM退出时,不必关心守护线程是否已结束。
如何创建守护线程呢?方法和普通线程一样,只是在调用`start()`方法前,调用`setDaemon(true)`把该线程标记为守护线程:
在守护线程中,编写代码要注意:守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失。
1.2 方法
方法名称 | 方法描述 |
---|---|
public fifinal void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程(为true) |
public fifinal boolean isDaemon() | 测试该线程是否为守护线程 |
1.3 创建线程的2种方法:
-
继承Thread类,重写run方法,实例化线程对象,调用start()方法,因为java是单继承,这种方法不适合共享资源
-
实现Runnable接口,重写run方法,实例化实现了接口的对象,实例化线程对象,调用start()方法,是一种代理模式
Thread是实现了Runnable接口的类,使用run支持多线程。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。
start0()方法是底层方法,由JVM调用,真正实现多线程。
run方法是一个普通的方法,没有真正启动一个线程。
1.4 使用内部类的方式来创建线程
package com.xxx;
public class Test01 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}.start();
}
}
二、线程通信
wait⽅法与notify⽅法必须要在同步代码块或同步函数中使用,并由同⼀个锁对象调⽤。
wait⽅法与notify⽅法是属于Object类的⽅法的。因为:锁对象可以是任意对象。
-
wait() 让当前线程处于等待状态,并释放锁
-
notify() 唤醒某个等待中的线程
-
notifyAll() 唤醒所有等待中的线程
-
wait 是Object顶级类的方法,只能在同步方法或者同步块中使用。wait会释放锁,要用notify()唤起,时长大于等于sleep。
-
sleep 是Thread线程类的静态方法,可以在任何地方使用,不会释放锁,它也不需要占用锁。
-
BlockingQueue自带阻塞
三、单线程
同一时刻,只允许执行一个线程
四、多线程
同一时刻可以执行多个线程
-
⼀个程序运⾏后⾄少有⼀个进程,⼀个进程中可以包含多个线程
-
并发:指两个或多个事件在同⼀个时间段内发⽣,多任务交替执行。
-
并⾏:指两个或多个事件在同⼀时刻发⽣(同时发⽣),需要多核cpu。
-
Runnable的实现方式是实现其接口 implements
-
Thread的实现方式是继承其类 extends
-
run()方法只是一个类中的普通方法,调用run方法跟调用普通方法一样
-
而start()方法是启动线程,它创建线程等一系列工作,然后自己调用run里面的任务内容。start0()真正的多线程
五、锁
5.1 synchronized重量级
-
同步代码块(性能好)
-
同步方法
同一个锁才会有互斥现象
类锁和静态方法锁是同一把锁
1、修饰普通方法(锁住的是当前实例对象)
-
同一个实例调用会阻塞
-
不同实例调用不会阻塞
2、同步代码块传参this(锁住的是当前实例对象)
-
同一个实例调用会阻塞
-
不同实例调用不会阻塞
3、同步代码块传参变量对象 (锁住的是变量对象)
- 同一个属性对象才会实现同步
4、同步代码块传参class对象(全局锁)
- 所有调用该方法的线程都会实现同步
5、修饰静态方法(全局锁)
- 所有调用该方法的线程都会实现同步
5.2 对象锁(this)和类锁(class)区别?
1、对于静态方法,由于此时对象还未生成,所以只能采用类锁;
2、只要采用类锁,就会拦截所有线程,只能让一个线程访问。
3、对于对象锁(this),如果是同一个实例,就会按顺序访问,但是如果是不同实例,就可以同时访问。
4、如果对象锁跟访问的对象没有关系,那么就会都同时访问。
当使用 synchronized 加锁 class 时,无论共享一个对象还是创建多个对象,它们用的都是同一把锁,而使用 synchronized 加锁 this 时,只有同一个对象会使用同一把锁,不同对象之间的锁是不同的。
5.3 Lock(更轻量)
1.lock Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
2.方法
void lock() 获取锁对象
void unlock() 释放锁对象
5.4 死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
由于线程被无限期地阻塞,因此程序不可能正常终止。
线程1
synchronized(obj1){
synchronized(obj2){}
}
线程2
synchronized(obj2){
synchronized(obj1){}
}
六、线程中常用方法
方法名称 | 方法描述 |
---|---|
public fifinal void stop() | 终止线程 |
public void interrupt() | 中断线程(只是标记改变中断状态而已,它不会中断一个正在运行的线程。) |
public static void yield() | 暂停当前正在执行的线程对象,并执行其他线程(礼让) |
public fifinal void join() | 等待该线程终止(执行当前线程 再执行其它的线程)必须是在开启之后调用 |
|
Thread.interrupt的作用其实不是中断线程,而是通知线程应该中断了,如果线程处于正常活动状态,线程将继续正常执行,不受影响。
notify方法:
从类 java.lang.Object 继承的方法
用于唤醒在此对象监视器上等待的单个线程。
七、Object****提供的常用的方法
方法名称 | 方法描述 |
---|---|
public fifinal void wait(long timeout) | 即时等待 |
public fifinal void wait() | 无限等待 |
public fifinal void notify() | 唤醒在此对象监视器上等待的单个线程(唤醒一个) |
public fifinal void notifyAll() | 唤醒在此对象监视器上等待的单个线程(唤醒多个) |