以下为博主整理网络资料而成,如有错误望请指正,感谢!
线程的状态
一般来讲,线程共有5种状态:
1. 新建(NEW):新建了一个线程对象,没有调用start方法之前
2. 就绪(又称可运行,RUNNABLE):调用start方法后进入就绪状态
3. 运行(RUNNING):就绪状态的线程得到了CUP时间片,执行run方法中的代码
4. 阻塞(BLOCKED):阻塞状态是指线程因为某些原因放弃了CPU使用权,暂时停止运行
阻塞有三种:
1. 等待阻塞:运行状态的线程执行wait方法,JVM把该线程放入等待队列
2. 同步阻塞:运行状态的线程在获取对象的同步锁时,同步锁被别的线程占用,
则JVM会把该线程放入锁池中
3. 其他阻塞:如运行状态的线程执行了sleep方法或join方法或发出I/O请求;
当sleep超时、join等待线程终止或I/O处理完毕时,线程进入可运行状态
5. 死亡(DEAD):线程正常执行结束,或因异常退出了run方法
线程常用方法
-
start()
启动一个线程,使其进入就绪状态 -
run()
包含该线程的执行内容,当该线程进入运行状态自动执行run方法 -
sleep()
在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。sleep()使当前线程进入阻塞状态,在指定时间内不会执行。 -
wait()【Object的方法】
在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。
当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。
唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。
wait()和notify()必须在synchronized函数或synchronized block中进行调用。
如果在non-synchronized函数或non-synchronized block中进行调用,
虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常 -
yield()
暂停当前正在执行的线程对象。
yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
yield()只能使同优先级或更高优先级的线程有执行的机会(注意是有机会,不是一定)。
实际上,yield()执行后只是让该线程和优先级高于等于该线程的其他线程一起抢夺CPU时间片 -
join()
等待该线程终止。
等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测 -
setPriority()
更改线程的优先级。 -
stop()
已过时,强行终止线程(不安全) -
notify()/notifyAll()【Object的方法】
唤醒在等待中的某个线程/所有线程 -
currentThread()
获取当前正在执行的线程
实现多线程的三种方式(前两种常用)
- 创建一个线程子类实现Thread类,重写run方法
- 创建一个实现类实现Runnable接口,重写run方法
实现Runnable接口创建多线程的优点:
1. 避免了单继承的局限性(继承Thread类就不能继承其他类,实现接口则不一样)
2. 增强了程序的扩展性,降低了程序的耦合性。实现接口的方式,把设置线程任务和开启新线程进行了分离
3. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类 - 创建一个实现类实现Callable,重写call方法;与第二种方法不一样的是有返回值
start()和run()的区别
1. 用start方法来启动线程,真正实现多线程的运行,而run方法只是线程中的一个普通方法,
如果直接调用run方法,程序中只有主线程中执行(仍然按顺序执行)
2. start方法不能被重复调用,run方法可以
3. 调用start后执行的run方法代码可以不执行完就继续执行下面的代码(进行线程切换)
直接调用run方法必须等待其代码全部执行完才能执行下面的代码
java中断(终止)线程的三种方法
1. 使用stop()
强行终止线程。不安全,已过时,不能保证线程资源的释放
2. 使用退出标识符
3. 使用interruupt方法(异常退出法)【推荐】
守护线程(Daemon)
用户线程:我们平常创建的线程普通线程(包括main线程)
守护线程:用来服务于用户线程(如JVM的GC),实现方式和普通线程类似,只是要先设置setDaemon(true)
daemon的特点:
1. daemon线程的创建过程中需要先调用setDaemon方法进行设置,
而且setDaemon(true)必须在start方法之前设置,否则会抛出IllegalThreadStateException异常
2. daemon线程的终止条件是当前是否存在用户线程。
3. daemon线程不能用于访问固有资源(如进行读写操作、计算)
4. daemon线程中产生的新线程也是daemon线程
死亡的线程能不能复活
进入DEAD状态不能复活;执行完run方法的线程即进入DEAD状态,对象可能仍然存在,
但是不能调用start(),否则抛出IllegalThreadStateException异常。
对于其对象,可能不会被GC回收(可以自救)
2019-09-25添加:
线程局部变量ThreadLocal
1. ThreadLocal的作用和目的
用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,
而在另外线程中运行时又共享另外一份数据,线程的数据是独享的
2. ThreadLocal的实现原理
每个线程调用全局ThreadLocal对象的set方法,在set方法中,首先根据当前线程获取当前线程的ThreadLocalMap对象,
然后向这个map中插入一条记录,key是ThreadLocal对象,value是各自的set方法传进去的值。
在线程结束时可以调用ThreadLocal.remove()方法,这样更快释放内存,也可以调用ThreadLocal.clear()立即释放内存;
也可以都不调用,因为线程结束后自动释放相关ThreadLocal变量
1. 应用场景例子:
银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,
它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法
3. [实例可参考](https://juejin.im/post/5ac2eb52518825555e5e06ee)
synchronized和volatile关键字的作用
volatile:
一但一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个共享变量的值,
这新值对其他线程来说是立即可见的。
2. 禁止进行指令重排序
volatile的本质是告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取
synchronized:
synchronized是锁定当前变量,只有当前线程可以访问变量,其他线程被阻塞住
区别:
1. volatile仅能使用在变量级别;synchronized可以使用在变量、方法、类级别
2. volatile仅能修改变量的可见性,不能保证原子性;synchronized则可以保证变量的原子性和可见性
3. volatile不会造成线程阻塞;synchronized可能会造成线程的阻塞
4. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化