通过JVM给我们的入口,可以知道运行期间有几个线程:
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos){
System.out.println(threadInfo);
}
一、启动线程的三种方式:
1.继承 Thread
2.实现Runnable接口
3.实现Callable接口(有返回值)
public class NewThread {
private static class UseRun implements Runnable{
@Override
public void run() {
System.out.println("I am implements Runnable");
}
}
private static class UseCall implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("I am implements Callable");
return "CallResult";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
UseRun useRun = new UseRun();
//Runnable接口能够直接作为Thread的构造参数
Thread thread1 = new Thread(useRun);
UseCall useCall = new UseCall();
//Callable接口不能作为Thread的构造参数
//需要通过FutureTask类的转换,将其转换为Runnable接口
//因为FutureTask实现了Runnable接口
FutureTask<String> futureTask = new FutureTask<>(useCall);
Thread thread2 = new Thread(futureTask);
thread2.start();
//通过futureTask的get方法得到返回值
//get方法是一个阻塞方法,一直等到有返回值后才会继续往下走
System.out.println(futureTask.get());
}
}
二、停止线程的三种方式:
1.自然执行完成死亡
2.在没有对异常进行处理时抛出异常会中止线程
3.早期stop()等因为过于强势,无法保证线程资源释放,容易导致死锁的问题‘
4.interrupt():中断一个线程,将中断标志位置为true
isinterrupted():判定当前线程是否处于中断状态
static方法interrupted():判定当前线程是否处于中断状态,同时将中断标志位改为False,这是2和3的最主要的区别
由于Java中线程是协作式的,interrupt方法只会通知线程应该中断了,并不会强制中断
目的:为了让每个线程有时间释放占有的资源
在实际操作过程中是要自己轮询标志位进行判断的,如果不对标志位进行判断,则不会有任何影响。
问:但是interrupt()和isinterrupted方法都是Thread类里面的方法,Runnable接口怎么办?
判断isinterrupted的时候用Thread.currentThread.isinterrupted()方法就可以了
package thread;
/*
如何安全地中断线程
*/
public class EndThread {
private static class UseThread extends Thread{
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!isInterrupted()){
System.out.println(threadName);
}
System.out.println(isInterrupted()+"中断标志位");
}
}
private static class UseThread2 extends Thread{
public UseThread2(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!Thread.currentThread().isInterrupted()){
System.out.println(threadName);
}
System.out.println(Thread.currentThread().isInterrupted()+"中断标志位");
}
}
private static class UseThread3 extends Thread{
public UseThread3(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (!isInterrupted()){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//这里打印的是false
System.out.println(isInterrupted()+"中断标志位");
//自行interrupt一次才会中断
interrupt();
e.printStackTrace();
}
//此时线程并没有中断
System.out.println("***");
}
System.out.println(isInterrupted()+"中断标志位while结束后");
}
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread3("endThread");
endThread.start();
Thread.sleep(10);
endThread.interrupt();
}
}
注意:当方法抛出InterruptedException异常的时候,线程的中断标志位会复位成False,只有在catch语句中再执行一次interrupt方法
(再次中断)才会中断。InterruptedException所有阻塞方法都会抛出异常
如果说用自己定义的标志位,阻塞时的判断就会不及时,不建议使用
yield():用的少,示意线程让出时间片,然后和其他线程一起抢,可能再次抢到。
三、其他知识:
·设置线程优先级1-10:Thread.setPriority(x);
·守护线程:
一般来说主线程结束后如果子线程还没处理完会继续运行
守护线程和主线程共死,如GC线程
特别注意:
Thread.setDaemon(true);//一定要在start()方法之前设置
try//finally语句块中的语句在守护线程里面不一定会执行
四、线程间的共享:
·synchronized关键字(内置锁):
确保任一时刻只能由一个线程处于同步方法或同步块中
锁的实际上是具体的一个对象。改变对象的标志位指向某一线程。
对象锁:在普通方法上加,或者synchronized(this){}也是对象锁
类锁 :在static方法上加,或者synchronized(xxx.class){}(锁的是class对象)
·volatile关键字:
每次获取值的时候都要从主内存中读取
每次修改完之后都要把值刷回主内存
保证了可见性,轻量同步机制,不能保证原子性()。
最大的用处是在一个线程写,多个线程读时最常用
·ThreadLocal线程变量:
确保每个线程只使用自己的那一个拷贝,主要用在连接池,保持每个线程的连接,内部用Map维护
package thread;
public class UseThreadLocal {
//例化了一个ThreadLocal对象。我们只需要实例化对象一次,并且也不需要知道它是被哪个线程实例化。
虽然所有的线程都能访问到这个ThreadLocal实例,但是每个线程却只能访问到自己通过调用ThreadLocal的
set()方法设置的值。即使是两个不同的线程在同一个ThreadLocal对象上设置了不同的值,他们仍然无法访问到对方的值。
static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
//设置初始值
@Override
protected Integer initialValue() {
return 1;
}
};
//运行三个线程
public void StartThreadArray(){
Thread[] runs = new Thread[3];
for (int i = 0; i < runs.length; i++) {
runs[i] = new Thread(new TestThread(i));
}
for (int i = 0; i < runs.length; i++) {
runs[i].start();
}
}
//测试线程,将ThreadLocal变量的值变化,并写回,看看线程之间是否会相互影响
public static class TestThread implements Runnable{
int id;
public TestThread(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"start");
Integer s = threadLocal.get();
s = s+id;
threadLocal.set(s);
System.out.println(Thread.currentThread().getName()+"当前"+threadLocal.get());
}
}
public static void main(String[] args) {
UseThreadLocal test = new UseThreadLocal();
test.StartThreadArray();
}
}
五、线程间协作
·等待和通知的标准范式
wait()、notify/notifyall()是Object中的方法。
等待方:
1、获取对象的锁
2、循环里判断条件是否满足,不满足调用wait方法
3、条件满足执行业务逻辑
通知方:
1、获取对象的锁
2、改变条件
3、通知所有等待该对象的线程
尽量使用notifyall,防止发生信号丢失情况
例子:Express类
package thread.waitNotify;
public class Express {
public final static String CITY = "shanghai";
private int km;//里程数
private String site;//快递到达地点
public Express() {
}
public Express(int km, String site) {
= km;
this.site = site;
}
//变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理
public synchronized void changeSite(){
this.site="beijing";
notifyAll();
}
//变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理
public synchronized void changeKm(){
= 101;
notifyAll();
}
public synchronized void waitKm(){
while (<=100){
try {
wait();
System.out.println("check km thread"+
Thread.currentThread().getId()+
"is notified");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("the km is"+);
}
public synchronized void waitSite(){
while (this.site.equals(CITY)){
try {
wait();
System.out.println("check site thread"+
Thread.currentThread().getId()+
"is notified");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("the site is"+this.site);
}
}
测试代码:
package thread.waitNotify;
import java.util.concurrent.TimeUnit;
public class TestWN {
private static Express express = new Express(0,Express.CITY);
//检查里程数的线程,不满足条件就一直等待
private static class CheckKm extends Thread{
@Override
public void run() {
express.waitKm();
}
}
//检查地点的线程,不满足条件就一直等待
private static class CheckSite extends Thread{
@Override
public void run() {
express.waitSite();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new CheckKm());
Thread thread2 = new Thread(new CheckSite());
thread1.start();
thread2.start();
TimeUnit.MILLISECONDS.sleep(100);
express.changeKm();
}
}
·等待超时模式(连接池的实现):
假设等待时间为T,当前时间now+T以后超时
long overTime = now+T;
long remain = T;//等待的持续时间
while(result 不满足条件 && remain>0){
wait(remain);
remain = overTime-now;//剩下的持续时间
}
return result;
数据库连接池模拟:
//在mills时间内拿不到数据库连接,返回一个null
public Connection fetchConn(long mills){
synchronized(poolList){
long overTime = System.currentTime+mills;
long remian = mills;
while(poolList.isEmpty && remian>0){
poolList.wait(remain);
remian = overtime - now();
}
Connection result = null;
if(!poolList.isEmpty()){
result = poolList.removeFirst();
}
return result;
}
}
//放回数据库连接
public void releaseConn{
synchronized(poolList){
poolList.addLast(conn);
poolList.notifyAll();
}
}
·join()方法:
面试点:线程A执行了线程B的join方法,那么线程A就必须要等待B执行完成了之后才能继续执行(请美女排你前面插队)
六、调用yield、sleep、wait、notify等方法对锁有何影响
线程在执行yield以后,持有的锁是不是放的,cpu仍可能调度这个线程
sleep方法也不会释放锁
在调用wait方法之前必须要持有锁,在调用wait方法之后这个锁就会被释放,当wait方法返回的时候线程会重新持有锁
notify在调用之前也必须要持有锁,在同步代码块跑完之后才会释放锁