黑马程序员并发编程笔记(二)--java线程基本操作和理解

3.java进程的基本操作

3.1.创建进程

方法一,直接使用 Thread

// 构造方法的参数是给线程指定名字,,推荐给线程起个名字(用setName()也可以)
Thread t1 = new Thread("t1") {
 @Override
 // run 方法内实现了要执行的任务
 public void run() {
 log.debug("hello");
 }
};
t1.start();

方法二,使用 Runnable 配合 Thread

把【线程】和【任务】(要执行的代码)分开,Thread 代表线程,Runnable 可运行的任务(线程要执行的代码)Test2.java

// 创建任务对象
Runnable task2 = new Runnable() {
 @Override
 public void run() {
 log.debug("hello");
 }
};
// 参数1 是任务对象; 参数2 是线程名字,推荐给线程起个名字
Thread t2 = new Thread(task2, "t2");
t2.start();

1.8后用lambda表达式来简化写法

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第1张图片

// 创建任务对象
Runnable task2 = () -> log.debug("hello");
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = neaw Thread(task2, "t2");
t2.start();

也可以更简略一点

// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = neaw Thread(() -> log.debug("hello"), "t2");
t2.start();

idea里 lamba表达式简化快捷键: alt +enter

小结

方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了,用 Runnable 更容易与线程池等高级 API 配合,用 Runnable 让任务类脱离了 Thread 继承体系,更灵活。通过查看源码可以发现,方法二其实到底还是通过方法一执行的!

需要注意的是:

  • 如果直接运行Thread的run()方法,则是主线程调用

方法三,FutureTask 配合 Thread

FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况 Test3.java

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 实现多线程的第三种方法可以返回数据也可以抛出异常,返回的数据需要用get接收
        FutureTask futureTask = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("多线程任务");
                Thread.sleep(100);
                return 100;
            }
        });
        
  
        new Thread(futureTask,"我的名字").start();
        log.debug("主线程");
        //{}表示占位,实际值是后买你的参数,获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
        log.debug("{}",futureTask.get());
    }

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成。

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Future提供了三种功能:

  1. 判断任务是否完成;
  2. 能够中断任务;
  3. 能够获取任务执行结果。

3.3 查看进程线程的方法

windows

任务管理器可以查看进程和线程数,也可以用来杀死进程

tasklist 查看进程

taskkill 杀死进程

tasklist | findstr java 查看所有的java线程

linux

ps -fe 查看所有进程

ps -fT -p  查看某个进程(PID)的所有线程

kill 杀死进程

top 按大写 H 切换是否显示线程

top -H -p  查看某个进程(PID)的所有线程

ps -fe | grep java 查看所有的java进程

Java

jps 命令查看所有 Java 进程

jstack  查看某个 Java 进程(PID)的所有线程状态

jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

jconsole 远程监控配置

需要以如下方式运行你的 java 类

java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -
Dcom.sun.management.jmxremote.authenticate=是否认证 java类

修改 /etc/hosts 文件将 127.0.0.1 映射至主机名

如果要认证访问,还需要做如下步骤

复制 jmxremote.password 文件

修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写

连接时填入 controlRole(用户名),R&D(密码)

例子:

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第2张图片

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第3张图片

3.2 线程运行原理

3.2.1.虚拟机栈与栈帧

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第4张图片

拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,是属于线程的私有的。当java中使用多线程时,每个线程都会维护它自己的栈帧!每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

3.2.2.线程上下文切换(Thread Context Switch)

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完(每个线程轮流执行,看前面并行的概念)
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleepyieldwaitjoinparksynchronizedlock 等方法

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念 就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的.

3.3 Thread的常见方法

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第5张图片

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第6张图片

3.3.1 start 与 run

调用start

    public static void main(String[] args) {
        Thread thread = new Thread(){
          @Override
          public void run(){
              log.debug("我是一个新建的线程正在运行中");
              FileReader.read(fileName);
          }
        };
        thread.setName("新建线程");
        thread.start();
        log.debug("主线程");
    }

输出:程序在 t1 线程运行, run()方法里面内容的调用是异步的 Test4.java

11:59:40.711 [main] DEBUG com.concurrent.test.Test4 - 主线程
11:59:40.711 [新建线程] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中
11:59:40.732 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] start ...
11:59:40.735 [新建线程] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 3 ms

调用run

将上面代码的thread.start();改为 thread.run();输出结果如下:程序仍在 main 线程运行, run()方法里面内容的调用还是同步的

12:03:46.711 [main] DEBUG com.concurrent.test.Test4 - 我是一个新建的线程正在运行中
12:03:46.727 [main] DEBUG com.concurrent.test.FileReader - read [test] start ...
12:03:46.729 [main] DEBUG com.concurrent.test.FileReader - read [test] end ... cost: 2 ms
12:03:46.730 [main] DEBUG com.concurrent.test.Test4 - 主线程

小结

直接调用 run() 是在主线程中执行了 run(),没有启动新的线程使用的是main线程, 使用 start() 是启动新的线程,通过新的线程间接执行 run()方法 中的代码

3.3.2 sleep 与 yield

sleep

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第7张图片

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,那么被打断的线程这时就会抛出 InterruptedException异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】
  3. 睡眠结束后的线程未必会立刻得到执行(需要分配到cpu时间片)
  4. 建议用 TimeUnit 的 sleep() 代替 Thread 的 sleep()来获得更好的可读性

小知识:TimeUint的sleep()

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第8张图片

yield

  1. 调用 yield 会让当前线程从Running 进入 Runnable 就绪状态,然后调度执行其它线程
  2. 具体的实现依赖于操作系统的任务调度器(就是可能没有其它的线程正在执行,虽然调用了yield方法,但是也没有用)

3.3.3.线程优先级

  • 线程优先级提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它, 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
  • 对应的操作
thread1.setPriority(Thread.MAX_PRIORITY); //设置为优先级最高

3.3.4. join方法

  • 主线程中调用t1.join,则主线程等待t1线程执行完之后继续执行
private static void test1() throws InterruptedException {
    log.debug("开始");
    Thread t1 = new Thread(() -> {
        log.debug("开始");
        sleep(1);
        log.debug("结束");
        r = 10;
    },"t1");
    t1.start();
    // t1.join(); 
    // 这里如果不加t1.join(), 此时主线程不会等待t1线程给r赋值, 主线程直接就输出r=0结束了
    // 如果加上t1.join(), 此时主线程会等待到t1线程执行完才会继续执行.(同步), 此时r=10;
    log.debug("结果为:{}", r);
    log.debug("结束");
}

图示如下:

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第9张图片

思考:等待多个结果时的运行时间’

static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
 test2();
}
private static void test2() throws InterruptedException {
 //线程t1
 Thread t1 = new Thread(() -> {
 sleep(1);
 r1 = 10;
 });
 //线程t2   
 Thread t2 = new Thread(() -> {
 sleep(2);
 r2 = 20;
  });
    
 long start = System.currentTimeMillis();
 t1.start();
 t2.start();
 t1.join();
 t2.join();
 long end = System.currentTimeMillis();
 log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}

因为线程t1等待时间示1ms,线t2等待时间为2ms,线程t1运行结束后还要等待1ms,线程t2才结束。总共运行2ms,输出。

如图:

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第10张图片

而调换之后:

static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
 test2();
}
private static void test2() throws InterruptedException {
 //线程t1
 Thread t1 = new Thread(() -> {
 sleep(1);
 r1 = 10;
 });
 //线程t2   
 Thread t2 = new Thread(() -> {
 sleep(2);
 r2 = 20;
  });
    
 long start = System.currentTimeMillis();
 t1.start();
 t2.start();
 t2.join();
 t1.join();
 long end = System.currentTimeMillis();
 log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}

线程t2join结束后,能立马输出,总时间还是两秒。

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第11张图片

join()函数里面的参数

同时,join()里还可以写入参数,

  • 当参数小于实际运行时间,主线程等待参数时间,再开始行动

    static int r1 = 0;
    static int r2 = 0;
    public static void main(String[] args) throws InterruptedException {
     test3();
    }
    public static void test3() throws InterruptedException {
     Thread t1 = new Thread(() -> {
     sleep(2);
     r1 = 10;
     });
     long start = System.currentTimeMillis();
     t1.start();
     // join结束,等待结束
     t1.join(1500);
     long end = System.currentTimeMillis();
     log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
    
  • 当参数大于实际运行时间,主线程会依照实际运行时间。

static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
 test3();
}
public static void test3() throws InterruptedException {
 Thread t1 = new Thread(() -> {
 sleep(2);
 r1 = 10;
 });
 long start = System.currentTimeMillis();
 t1.start();
 //运行结束,等待结束
 t1.join(3000);
 long end = System.currentTimeMillis();
 log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}

3.3.5. interrupt

interrupt是特有的打断方法,本身并不会终止线程运行,常常和打断标记配合使用,使得线程终止更加“优雅”。

  • interrupt打断正在运行中的线程时,会让打断标记变为true
  • 打断sleep状态的线程,会报出对应的异常,不会把打断标记变为false

打断正在sleep的线程

private static void test1() throws InterruptedException {
 Thread t1 = new Thread(()->{
 sleep(1);
 }, "t1");
 t1.start();
 sleep(0.5);
 t1.interrupt();
 log.debug(" 打断状态: {}", t1.isInterrupted());
}

输出

java.lang.InterruptedException: sleep interrupted
 at java.lang.Thread.sleep(Native Method)
 at java.lang.Thread.sleep(Thread.java:340)
 at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
 at cn.itcast.n2.util.Sleeper.sleep(Sleeper.java:8)
 at cn.itcast.n4.TestInterrupt.lambda$test1$3(TestInterrupt.java:59)
 at java.lang.Thread.run(Thread.java:745)
21:18:10.374 [main] c.TestInterrupt - 打断状态: false

打断正在运行的线程

private static void test2() throws InterruptedException {
 Thread t2 = new Thread(()->{
 while(true) {
 Thread current = Thread.currentThread();
 boolean interrupted = current.isInterrupted();
 if(interrupted) {
 log.debug(" 打断状态: {}", interrupted);
 break;
 }
 }
 }, "t2");
 t2.start();
 sleep(0.5);
 t2.interrupt();
}
20:57:37.964 [t2] c.TestInterrupt - 打断状态: true

注意:isInterrupted和interrupted的区别

前者将打断标记变为true之后不将它自动变为false 而后者自动将它变为false

3.4.终止模式之两阶段终止模式(一)用打断方式实现

两阶段终止模式就源于堆打断方法和打断标记的利用

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第12张图片

大致操作如下图:

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第13张图片

简单来说就是:用一个监控线程监控所有线程,运行过程中时刻检测当前线程的打断标志是否为true,当打断标志为true时,停止线程。

而我们可以通过此来停止该线程。

当前线程正常运行时被打断可以得到上面我们说的结果,而睡眠中被打断,需要我们手动将标志改为true.

实际代码如下:

public class Test7 {
	public static void main(String[] args) throws InterruptedException {
		Monitor monitor = new Monitor();
		monitor.start();
		Thread.sleep(3500);
		monitor.stop();
	}
}

class Monitor {

	Thread monitor;

	/**
	 * 启动监控器线程
	 */
	public void start() {
		//设置线控器线程,用于监控线程状态
		monitor = new Thread() {
			@Override
			public void run() {
				//开始不停的监控
				while (true) {
                    //判断当前线程是否被打断了
					if(Thread.currentThread().isInterrupted()) {
						System.out.println("处理后续任务");
                        //终止线程执行
						break;
					}
					System.out.println("监控器运行中...");
					try {
						//线程休眠
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
						//如果是在休眠的时候被打断,不会将打断标记设置为true,这时要重新设置打断标记
						Thread.currentThread().interrupt();
					}
				}
			}
		};
		monitor.start();
	}

	/**
	 * 	用于停止监控器线程
	 */
	public void stop() {
		//打断线程
		monitor.interrupt();
	}
}

3.5打断 park 线程

打断 park 线程是一种将当前线程停止在park()那行的一种方法,可以由打断标志控制,当打断标志为truepark()失去它的作用,false时生效

private static void test3() throws InterruptedException {

Thread t1 = new Thread(() -> { log.debug("park..."); 

LockSupport.park(); log.debug("unpark..."); 

log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); }, "t1"); 

t1.start(); sleep(0.5); 

t1.interrupt();
}

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第14张图片

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第15张图片

如果不手动复原被打断的线程不会自己复原打断标志,isInterrupted()方法不会自动复原,所以这时我们可以使用 Thread.interrupted() 清除打断状态。

3.6.不推荐使用的方法

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第16张图片

3.7. 主线程与守护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

log.debug("开始运行...");

Thread t1 = new Thread(() -> {
 log.debug("开始运行...");
 sleep(2);
 log.debug("运行结束...");
}, "daemon");

// 设置该线程为守护线程
t1.setDaemon(true);

t1.start();

sleep(1);
log.debug("运行结束...");
//结果
08:26:38.123 [main] c.TestDaemon - 开始运行... 
08:26:38.213 [daemon] c.TestDaemon - 开始运行... 
08:26:39.215 [main] c.TestDaemon - 运行结束...

注意

垃圾回收器线程就是一种守护线程

Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

3.8 五种状态

操作系统的层面上,我们可以把线程的运行分为五种状态:

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第17张图片

3.9.六种状态

按照线程state()里的划分,我们可以把线程分成六种状态

黑马程序员并发编程笔记(二)--java线程基本操作和理解_第18张图片

对六种状态的测试:

@Slf4j(topic = "c.TestState")
public class TestState {
    public static void main(String[] args) throws IOException {
        Thread t1 = new Thread("t1") {	// new 状态
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while(true) { // runnable 状态

                }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        t3.start();

        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                synchronized (TestState.class) {
                    try {
                        Thread.sleep(1000000); // timed_waiting 显示阻塞状态
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                try {
                    t2.join(); // waiting 状态
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (TestState.class) { // blocked 状态
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState());
        log.debug("t2 state {}", t2.getState());
        log.debug("t3 state {}", t3.getState());
        log.debug("t4 state {}", t4.getState());
        log.debug("t5 state {}", t5.getState());
        log.debug("t6 state {}", t6.getState());
    }
}

并发编程核心

你可能感兴趣的:(java底层相关,java,intellij-idea,开发语言)