JUC笔记(一)

同步和异步

这个概念相对于调用者来说,如果调用者需要等待方法返回后才能继续运行,就是同步,如果不需要等待方法返回即可继续运行,就是异步。在多线程里,同步的扩展意思是多线程之间协调一致。

IO操作

IO操作不占用CPU,但是线程被阻塞

创建和启动线程

  1. 直接使用Thread
Thread thread = new Thread(){
    
    
    @Overide
    public void run(){
    
    
    
    }
};
//必须调用start启动线程
thread.start()
  1. 使用Runnable
Runnable runnable = new Runnable() {
    
    
     @Override
     public void run() {
    
    
                
    }
};
new Thread(runnable).start();
  1. 使用FutureTask和Tread配合
package com.example.demo;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Juc {
    
    

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
    
    
            @Override
            public Integer call() throws Exception {
    
    

                System.out.println("sleep 1000 ms后返回");
                Thread.sleep(1000);
                return 0;
            }
        });

        Thread myThread = new Thread(futureTask,"t1");
        myThread.start();

        Integer integer = futureTask.get();
        System.out.println("接收t1返回:"+integer);
    }
}

线程执行的顺序

  1. 交替执行
  2. 先后顺序受调度,不受代码控制

查看进程和线程

  1. linux:
  ps -ef 查看进程信息 
  ps -fT -p <PID> 查看进程下的所有线程信息 
  jps 只列出java进程
  kill <PID>  杀死进程
  top -H -p <PID> 查看某个进程中线程信息 -H是查看所有线程
  jstack <JPID> 查看线程信息(快照形式)
  jconsole 图形界面工具 可以连接JVM,监控线程信息 需要启动Java程序时添加一些参数

栈和栈帧

  1. 每个栈都是由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  2. 每个线程都有一个活动栈帧,对应着当前正在执行的那个方法
  3. 栈帧包括局部变量表,返回地址,锁记录,操作数栈

线程上下文切换

原因

  1. 线程CPU时间片用完
  2. 垃圾回收
  3. 有更高优先级的线程要运行
  4. 线程主动让出CPU,比如sleep,yield,wait,join,park,synchronized,lock等方法时

线程切换

切换时操作系统要记录下一条JVM指令地址,记录这个地址的东西在JVM中叫程序计数器,线程私有。除了保存下一条指令地址,还会保存栈帧,如局部变量,操作数栈,返回地址等,频繁切换会影响性能

线程中常见方法

1. start():启动新线程使用,start调用后线程会进入就绪状态,等待调度,不一定直接运行,同一线程智能调用一次start方法,多了会报错
2. run():线程运行时会调用的方法
3. join():等待线程运行结束
4. join(long n):等待线程运行结束,最多等待n毫秒
5. getId():获取线程唯一ID
6. getName():获取线程名
7. getState():获取线程状态,java中线程状态:NEW,RUNNABLE,BLOCKED,WAITING,TIMED:TIMED_WAITING,TERMINATED
8. isInterrupted():判断是否被打断,不会清除**打断标记**
9. isAlive():线程是否存活
10. interrupt():打断线程,如果被打断线程正在sleep,wait,join则会导致打断线程抛出InterruptedException异常,并清除打断标记,如果打断正在运行的线程则会设置打断标记,PARK的线程被打断,也会设置打断标记。
11. interrupted():判断当前线程是否被打断,会清除打断标记。
12. sleep(long n):休眠n毫秒,线程进入TIMED_WAITIING状态,建议使用TimeUnit.SECONDDS.sleep(int n)方法,加一个sleep防止CPU空转导致CPU占用100%

    while(true){
    
    
        try {
    
    
           TimeUnit.MICROSECONDS.sleep(300);
       } catch (InterruptedException e) {
    
    
           e.printStackTrace();
       }
    }

13. yield():让出CPU时间片,主要用于测试,会让线程从RUNNING进入到RUNNABLE

join方法的使用

private static volatile int result = 0;

public static void main(String[] args) {
    
    

    Thread myThread = new Thread(()->{
    
    
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        result = 10;
    });
    myThread.start();
    try {
    
    
        myThread.join();
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }

    System.out.println(result);
}

以上例子中,主线程调用了myThread的join方法,等待myThread执行完成。这样主线程就可以拿到myThread线程的result。

private static volatile int result1 = 0;

private static volatile int result2 = 0;

public static void main(String[] args) throws InterruptedException {
    
    

    Thread myThread1 = new Thread(()->{
    
    
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(2000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        result1 = 10;
    });

    Thread myThread2 = new Thread(()->{
    
    
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        result2 = 20;
    });
    myThread1.start();
    myThread2.start();
    long startTime = System.currentTimeMillis();
    myThread1.join();
    myThread2.join();
    System.out.println("result1=" + result1 + " result2=" + result2);
    System.out.println("耗时:" + (System.currentTimeMillis() - startTime) + " ms");
}

上面的例子输出:

result1=10 result2=20
耗时:2001 ms

是两秒的原因是myThread1.join()等待了两秒,这期间myThread2已经sleep完1秒然后线程结束运行了,然后调用它的join方法不需要时间,所以是2秒

interrupt方法

  1. 打断阻塞状态的线程 (sleep,wait,join)
 Thread myThread1 = new Thread(()->{
    
    
    try {
    
    
        System.out.println("开始睡眠 myThread1");
        TimeUnit.MILLISECONDS.sleep(5000);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    }
});

myThread1.start();
TimeUnit.MILLISECONDS.sleep(1000);
System.out.println("开始打断 myThread1");
myThread1.interrupt();
System.out.println("打断标记:"+myThread1.isInterrupted());

输出:

开始睡眠 myThread1
开始打断 myThread1
打断标记:false
java.lang.InterruptedException
	at java.lang.Thread.sleep(Native Method)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.example.demo.Juc.lambda$main$0(Juc.java:12)
	at com.example.demo.Juc$$Lambda$1/0x0000000000000000.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:823)

interrupt打断睡眠中的线程会导致睡眠中的线程抛出一个InterruptedException异常,然后打断标记被设置为false

  1. 打断正常状态的线程
Thread myThread1 = new Thread(() -> {
    
    
    System.out.println("开始执行 myThread1");
    int i = 0;
    while (true) {
    
    
        i++;
    }
});

myThread1.start();
TimeUnit.MILLISECONDS.sleep(1000);
System.out.println("开始打断 myThread1");
myThread1.interrupt();

System.out.println("打断标记:" + myThread1.isInterrupted());

输出:

开始执行 myThread1
开始打断 myThread1
打断标记:true

此时虽然打断标记是true,但是,myThread1并没有停止运行,此时要想停止运行,就需要myThread1判断打断标记,自己退出运行。
优雅的解决方案:两阶段停止

package com.example.demo;

import java.util.concurrent.TimeUnit;

public class Juc {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    

        TowPhaseTermination towPhaseTermination = new TowPhaseTermination();
        towPhaseTermination.startMonitor();
        TimeUnit.MILLISECONDS.sleep(3500);
        towPhaseTermination.stop();
    }
}


class TowPhaseTermination{
    
    

    private Thread monitorThread;

    public void startMonitor(){
    
    
        monitorThread = new Thread(()->{
    
    
            while(true){
    
    

                Thread current = Thread.currentThread();
                if(current.isInterrupted()){
    
    
                    System.out.println("线程" + current.getName() + "被打断,开始处理退出逻辑");
                    break;
                }
                try {
    
    
                    Thread.sleep(1000);
                    System.out.println("执行监控");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                    //如果在sleep期间当前线程被打断,这里会catch住异常
                    //但是也会清除打断标记,这里手动加上打断标记
                    current.interrupt();
                }
            }
        });

        System.out.println("启动监控线程");
        monitorThread.start();
    }

    public void stop(){
    
    
        System.out.println("调用了stop方法");
        monitorThread.interrupt();
    }
}

输出:

启动监控线程
执行监控
执行监控
执行监控
调用了stop方法
线程Thread-3被打断,开始处理退出逻辑
java.lang.InterruptedException
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:953)
	at com.example.demo.TowPhaseTermination.lambda$startMonitor$0(Juc.java:31)
	at com.example.demo.TowPhaseTermination$$Lambda$1/0x0000000000000000.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:823)

上面的线程明显是在monitorThread调用了sleep没有睡眠完成时被打断了,那么catch中就捕获了异常,然后再一次进行了打断,在while循环中被判断出来,就将当前线程退出了,好处就是在退出时可以处理一些退出逻辑,比如锁的释放,直接被打断立即停止掉线程可以有并发问题,比如CompareAndSet锁的状态再也没有机会被当前线程释放了,Thread.stop()就是因为这样被禁用了,两阶段停止是优雅停止一个线程的最佳时间方案。

猜你喜欢

转载自blog.csdn.net/u013014691/article/details/122288999