线程(贰)
1. 线程的分类
- 前台线程:我们所使用的线程例如main线程
- 后台线程:为前台线程服务的
前台转后台
//在启动线程之前,可以将其设置为后台线程,否则默认是前台线程
t.setDaemon(true);
2. 线程的优先级
1最小,10最大,优先级高的获得资源的概率增加
MIN_PRIORITY = 1;
NORM_PRIORITY = 5;
MAX_PRIORITY = 10;
设置和获得线程优先级
//设置优先级
t1.setPriority(Thread.MAX_PRIORITY);
//获得优先级
t1.getPriority()
3. 线程组
位于lang包下的java.lang.ThreadGroup
存在的作用
它可以对一批线程进行管理,对线程组进行操作,同时也会对线程组里面的这一批线程操作。
创建线程组
用户在主线程中创建的线程,属于默认线程组(名字叫"main"的线程组)
ThreadGroup group = new ThreadGroup("我的线程组")
//指定线程所属的线程组
Thread t = new Thread(group,"t线程");
4. 线程的状态
注意,其实 BLOCKED,WAITING,TIMED_WAITING 这三种都属于线程阻塞,只是触发的条件不同,以及从阻塞状态中恢复过来的条件也不同而已。
线程在这三种情况的阻塞下,都具备相同的特点:
线程不执行代码
线程也不参与CPU时间片的争夺
获取状态方法:
t1.getState();
一个线程从创建到启动、到运行、到死亡,以及期间可能出现的情况都在上图中进行了描述。
5. 线程的安全以及操作方法
5.1 方法一:sleep
位于Thread中静态方法,让当前线程睡眠
//当前线程休眠10毫秒
Thread.sleep(10);
5.2 方法二:join
位于Thread中非静态方法,等待另一个指定的线程运行结束后,当前线程才可以继续运行 。
无参join方法:
Thread t2 = new Thread("t2线程"){
@Override
public void run() {
try {
//t2线程调用t1.join方法
//t2线程进入阻塞状态
//t2线程要等到t1线程运行结束,才能恢复到RUNNABLE状态
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2线程结束");
}
};
5.3 含参方法:表示阻塞多久唤醒
Thread t2 = new Thread("t2线程"){
@Override
public void run() {
try {
//t2线程调用t1.join方法
//t2线程进入阻塞状态
//t2线程要等到t1线程运行结束,才能恢复到RUNNABLE状态
//2000表示,当前线程t2最多阻塞2秒钟,2秒钟之内t1线程没有结束,那么t2线程就自动恢复
t1.join(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2线程结束");
}
};
线程A中,调用了线程B对象的join方法,那么线程A就会进入到阻塞状态,这种阻塞有俩种情况,一种是调用了无参的join方法,那么此时线程A的状态为WAITING(无限期等待),
另一种是调用了有参的join方法,那么此时线程A的状态为TIMED_WAITING(有限期等待)线程A如果调用了sleep方法,那么线程A也会进入阻塞状态,此时线程A的状态为TIMED_WAITING
5.3 方法三:interrupt
线程类Thread中的 interrupt 方法 ,打断阻塞的状态,恢复就绪状态
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread("t1线程"){
@Override
public void run() {
try {
//t1线程休眠100秒
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1线程结束");
}
};
t1.start();
//让主线程休眠500毫秒,目的是为了给t1时间,让它调用sleep方法而进入阻塞状态
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打断t1由于调用sleep方法而进入的阻塞状态
t1.interrupt();
}
}
//运行结果:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.briup.sync.Test$1.run(Test.java:11)
t1线程结束
可以看出,本来t1线程调用了sleep方法进入了阻塞状态,需要100后才会恢复的,但是我们在主线程中调用了t1线程对象的打断方法 interrupt ,那么此时 Thread.sleep(100000); 这句代码就会抛出被打断的异常,同时t1线程从阻塞状态恢复到RUNNABLE状态,继续执行代码,输出了t1线程结束
interrupt()调用本地方法 interrupt0() 改变一个标识flag的值
isInterrupted():获得返回这个“打断标识”值,并且不会对这个值进行清除(true->false)
interrupted() :内部调用isinterrupted()方法,对状态值进行擦除。
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread("t1线程"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//判断是否有其他线程调用了自己的interrupt方法
//调用类中的静态方法:interrupted
System.out.println(Thread.interrupted());
}
System.out.println("t1线程结束");
}
};
t1.start();
t1.interrupt();
}
}
//运行结果:
true
false
false
false
false
false
false
false
false
false
t1线程结束
三个尚未理解的:
interrupt:是进行打断操作,将flag值改为true,之后该线程时刻检测该值是否变为true,如果变为true,则抛出异常,结束阻塞,改flag值为false。如果该线程为非阻塞状态,也可以使用,只是无效果。
查看标识值得俩个方法
interrupted:当由interrupt将flag修改为true后,而程序又不处在阻塞状态,获取该标识值为true,之后进行清除,将flag值改为false
isinterrupted:同interrupted,只是不会对flag进行擦除。
源码进行区分:
public class Thread{
public void interrupt() {
//...
interrupt0(); // Just to set the interrupt flag
}
private native void interrupt0();
public boolean isInterrupted() {
return isInterrupted(false);
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
*/
private native boolean isInterrupted(boolean ClearInterrupted);
public static native Thread currentThread();
}
6. 关于锁:线程同步以及线程安全
6.1 锁存在的必要性
对于共享变量得访问,不同的线程之间会产生线程安全的问题
因此有了锁的概念 synchronized
分析
- 线程同步的效果,就是一段加锁的代码,每次只能有一个拿到锁的线程,才有资格去执行,没有拿到的锁的线程,只能等拿到锁的线程把代码执行完,再把锁给释放了,它才能去拿这个锁然后再运行代码。
- 这样以来,本来这段代码是俩线程并发访问,“争先恐后”的去执行的,现在线程同步之后,这段代码就变成了先由一个拿到锁的线程去执行,执行完了,再由另一个线程拿到锁去执行。
- 相当于是大家每个线程不要抢,排好队一个一个去执行,那么这时候共享的变量的值,肯定不会出现线程安全问题!
6.2 对于synchronized
synchronized 关键字修饰非静态方法,默认使用 this 当做锁对象,并且不能自己另外指定
synchronized 关键字修饰静态方法,默认使用 当前类的Class对象 当做锁对象,并且不能自己另外指
定
这俩中情况的同步效果是一样的,只是锁对象不同而已
synchronized (obj){
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
6.3 wait和notify
前因后果:对于被加锁的时候我们使用Sleep进入睡眠也不会释放锁,因此产生了wait进入等待并且释放锁,而且进入wait状态不会自己唤醒需要notify(随机唤醒等待池的一个)去唤醒,任何对象都有三个方法。还有一个是notifyAll(唤醒全部等待池的)
注意:wait:必须写在锁的代码块当中
最终状态图:
]
6.4 死锁
俩个线程t1和t2,t1拿着t2需要等待的锁不释放,而t2又拿着t1需要等待的锁不释放,俩个线程就这样一直僵持下去。
如何避免死锁
- 避免多次锁定。尽量避免同一个线程对多个 Lock 进行锁定。
- 具有相同的加锁顺序。如果多个线程需要对多个 Lock 进行锁定,则应该保证它们以相同的顺序请求加锁。
- 使用定时锁。程序在调用 acquire() 方法加锁时可指定 timeout 参数,该参数指定超过 timeout 秒后会自动释放对 Lock 的锁定,这样就可以解开死锁了。
- 死锁检测。死锁检测是一种依靠算法机制来实现的死锁预防机制,它主要是针对那些不可能实现按序加锁,也不能使用定时锁的场景的。