线程的生命历程
线程的五大状态
- 创建状态:简而言之,当创建线程对象的代码出现的时候,此时线程就进入了创建状态。这时候的线程只是行代码而已。只有调用线程的start()方法时,线程的状态才会改变,进入就绪状态
- 就绪状态:在这个状态下的线程,已经做好了随时运行的准备,但是并不意味着会立刻开始运行。还需要等待CPU的随机调度,随机运行。只有当线程被CPU调度运行成功,此时的线程才算是进入下一个状态——运行状态。
- 运行状态:线程处于运行状态,主要是在运行线程中的代码块。
- 阻塞状态:在线程运行过程中,当线程代码块中调用了线程的sleep(),yield(),同步锁定或者其他使线程阻塞的方法,此时的线程无法继续运行下去,进入了阻塞状态(线程代码块的自身逻辑混乱也可以使线程阻塞)。当造成线程阻塞的阻塞事件解决之后,线程不会回到运行状态,而是回到就绪状态,再次等待CPU的调度运行。需要注意的是,阻塞并不意味着线程运行终止
- 死亡状态:当线程成功运行完所有的代码之后,线程就结束了,也进入了死亡状态。线程一旦死亡,就无法再次启动,注意这里和阻塞状态的不同。同样的,当线程运行一半的时候被强行结束终止,也算进入死亡状态,也无法被再次启动。
线程的方法
Java中的thread类自带有线程的一些方法,这些方法可以让线程睡眠,插队,提高线程调度的优先级等等,它们提供了改变线程状态的操作手段。(不过在JDK帮助文档中,一些方法已经不推荐使用)
线程方法中的一些有趣的地方
- 线程睡眠是以毫秒为单位的。一秒等于一千毫秒。一般在测试程序中调用睡眠方法,是为了提高程序问题的发生性,或者说为了发现bug
- 线程停止,由于Java中自带的停止方法不太好用,所以一般都是自己写一个停止的方法,标定一个布尔类型的Flag作为线程执行的标志,当flag为真时线程运行,当flag为假时线程停止。
- 线程礼让是将正在运行的线程暂停回到就绪状态,而不是变为阻塞状态。有趣的是礼让不是一定会成功的,因为线程由就绪状态进入运行状态是由CPU随机调度的。所以礼让的线程有可能在下次的调度中再次提前调度,提前运行。
- 线程插队(join方法),强制阻塞其他线程,只有插入的线程执行完成之后,其他线程才能继续执行
- 线程虽然有优先级的区别(1-10),但是在实际运行中还是得看CPU的心情调度运行,优先级高只是被调度的概率高一点。Java中自带有线程优先级的查看和改变方法(线程的优先级设置最好在线程启动之前)
public class ttp {
public static void main(String[] args) {
//主线程的默认优先级
System.out.println(Thread.currentThread().getName()+"--->" + Thread.currentThread().getPriority());
MyPriorty mm = new MyPriorty();
Thread t1 = new Thread(mm);
Thread t2 = new Thread(mm);
Thread t3 = new Thread(mm);
t1.setPriority(10);
t1.start();
t2.setPriority(4);
t2.start();
t3.setPriority(6);
t3.start();
}
}
//Runnable接口实现接口,run方法为打印线程名称和线程的优先级
class MyPriorty implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->" + Thread.currentThread().getPriority());
}
}
//这里的输出有多种结果,因为优先级只是增加了线程被调度运行的机率
用户线程和守护线程。守护线程的作用是保证用户线程的执行过程正常,例如Java中的内存回收线程和后台记录操作日志等等,这些都是守护线程。虚拟机必须等待用户线程执行完毕,不用等待守护线程执行完毕。当用户线程完成后,虚拟机就自动关闭,守护线程也就自动死亡了。
//Java的Thread类自带设置守护线程的方法
Thread.setdaemon(true) //设置为守护线程
//一般我们创建的线程默认都是用户线程
线程同步。线程同步是出现多个线程访问同一个对象并都想对其进行操作的时候必须考虑的问题。不进行线程同步(并发)控制的多线程是不安全的。
//线程不安全,出现了-1张票以及有两个线程拿到同一张票的错误,所以这是一个不安全的线程
public class test05 {
public static void main(String[] args) {
buyTicket b1 = new buyTicket();
Thread t1 = new Thread(b1,"you");
Thread t2 = new Thread(b1,"i");
Thread t3 = new Thread(b1,"he");
Thread t4 = new Thread(b1,"she");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class buyTicket implements Runnable{
//剩余票数
private int ticketNums = 12;
private boolean flag = true;
@Override
public void run() {
while(flag){
buy();
}
}
private void buy(){
if(ticketNums <= 0){
flag = false;
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "买到了第" + ticketNums-- + "张票");
}
}
线程同步
线程同步实质上是一个等待机制。线程同步时会将多个线程放入对象等待池中进行排队(队列),等待前一个线程执行操作完毕,再有下一个线程进行执行操作。每个对象都有一个独有的锁(排他锁),每个线程执行时都会获得对象的排他锁,这样只有获得锁的线程可以对对象进行操作,执行结束后排他锁被下一个线程获得。总结来说,线程同步的形成条件就是:队列+锁。
在访问时加入锁机制synchronized,当一个线程获得对象得排他锁,独占资源,其他线程必须等待,使用后释放锁即可
线程同步也有一些存在的问题(大部分是以牺牲性能以保证安全)
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
一般来说,synchronized是方法声明中添加,默认对方法中的this对象资源添加锁。如果要对其他共享资源对象进行锁定,则要使用同步监视器
- 一号线程访问,锁定监视器,开始执行中间的代码
- 二号线程访问,发现监视器被锁,无法访问,挂起
- 一号线程执行完毕,解锁监视器
- 二号线程访问,监视器无锁,锁定并执行代码
public void xxx(){
//其中ob就是想要锁住的任意的共享资源对象
//代码块是放在同步监视器中的
synchronized(obj){
....
}
}
需要注意的是,这样的锁理论上是可行的 ,但是在实际运行中虽然加了锁,但是还是有可能出现不安全的现象
到此这篇关于Java线程状态及同步锁的文章就介绍到这了,更多相关Java线程状态及同步锁内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!