Java 线程的基本API使用和分析(二)

今天我们通过代码分析java线程:

一、java 天生支持多线程

1、main 方法模拟一个多线程:

一个 Java 程序从 main() 方法开始执行,然后按照既定的代码逻辑执行,看 似没有其他线程参与,但实际上 Java 程序天生就是多线程程序,因为执行 main() 方法的是一个名称为 main 的线程。
2、代码如下:
/**
 *类说明:只有一个main方法的程序
 */
public class OnlyMainTest {
    public static void main(String[] args) {
        //Java 虚拟机线程系统的管理接口
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 不需要获取同步的monitor和synchronizer信息,仅仅获取线程和线程堆栈信息
        ThreadInfo[] threadInfos =
                threadMXBean.dumpAllThreads(false, false);
        // 遍历线程信息,仅打印线程ID和线程名称信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] "
                    + threadInfo.getThreadName());
        }
    }
}

执行后:

[6] Monitor Ctrl-Break // 监控 Ctrl-Break 中断信号的
[5] Attach Listener // 内存 dump ,线程 dump ,类信息统计,获取系统属性等
[4] Signal Dispatcher // 分发处理发送给 JVM 信号的线程 [3] Finalizer // 调用对象 finalize 方法的线程
[2] Reference Handler// 清除 Reference 的线程
[1] main //main 线程,用户程序入口
 
六个线程其中只有man方法是用户线程,其他的都是守护线程。
二、 线程的启动与中止
启动
启动线程的方式有:
1 X extends Thread; ,然后 X.start
2 X implements Runnable ;然后交给 Thread 运行
 

/**
 *类说明:新启线程的方式
 */
public class NewThread {
	/*扩展自Thread 静态内部类*/
	private static class UseThread extends Thread{
		@Override
		public void run() {
			super.run();
			System.out.println("I am extendec Thread");
		}
	}

	
	/*实现Runnable接口的静态内部类*/
	private static class UseRunnable implements Runnable{

		@Override
		public void run() {
			System.out.println("I am implements Runnable");
		}
	}
	

	public static void main(String[] args) 
			throws InterruptedException, ExecutionException {
		UseThread useThread = new UseThread();
		useThread.start();
		useThread.start();//如果同时调用两次,则抛异常:java.lang.IllegalThreadStateException

		UseRunnable useRunnable = new UseRunnable();
		new Thread(useRunnable).start();

		
	}
}

Thread 和 Runnable 的区别

Thread 才是 Java 里对线程的唯一抽象, Runnable 只是对任务(业务逻辑) 的抽象。Thread 可以接受任意一个 Runnable 的实例并执行。
 
3、中止
线程自然终止
要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。 stop 暂停、恢复和停止操作对应在线程 Thread API 就是 suspend() resume() stop() 。但是这些 API 是过期的,也就是不建议使用的。不建议使用的原因主 要有:以 suspend() 方法为例,在调用后,线程不会释放已经占有的资源(比如 锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop() 方 法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资 源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为 suspend()、 resume()和 stop() 方法带来的副作用,这些方法才被标注为不建议使用的过期方 法。
4、中断:继承 therad  的线程中断方法: 
/**
 *类说明:如何安全中断线程
 */
public class EndThreadTest {
	//静态内部类
	private static class UseThread extends Thread{

		public UseThread(String name) {
			super(name);
		}

		@Override
		public void run() {
			String threadName = Thread.currentThread().getName();
			System.out.println(threadName+" interrrupt flag ="+isInterrupted());
			int i=1;
			while(!isInterrupted()){//判断是否已经通知停止的非静态方法,一般常用这个
				//while(!Thread.interrupted()){//判断是否已经通知停止的静态方法
				i++;
				System.out.println("第几次:"+i+";"+threadName+" is running");
				System.out.println("第几次:"+i+";"+threadName+"inner interrrupt flag ="
						+isInterrupted());
			}
			System.out.println("第几次:"+i+";"+threadName+" interrrupt flag ="+isInterrupted());
		}
	}

	public static void main(String[] args) throws InterruptedException {
		Thread endThread = new UseThread("endThread");
		endThread.start();
		Thread.sleep(20);
		endThread.interrupt();//中断线程,其实设置线程的标识位true,起到通知作用,线程一般不会立刻停止,而是执行完一定的任务后停止。
	}

5、两种情况的执行结果类似:静态方法:

非静态方法:

6、 实现runnable接口的线程的中断:

/**
 *类说明:实现接口Runnable的线程如何中断
 */
public class EndRunnableTest {
	
	private static class UseRunnable implements Runnable{
		
		@Override
		public void run() {
			while(!Thread.currentThread().isInterrupted()) {//静态方法判断
				System.out.println(Thread.currentThread().getName()
						+ " I am implements Runnable.");
			}
			System.out.println(Thread.currentThread().getName()
					+" interrupt flag is "+Thread.currentThread().isInterrupted());
		}
	}

	public static void main(String[] args) throws InterruptedException {
		UseRunnable useRunnable = new UseRunnable();
		Thread endThread = new Thread(useRunnable,"endThread");
	//	endThread.start();//执行的线程方法
		endThread.run();//执行的仅是业务方法
		Thread.sleep(20);
		endThread.interrupt();
	}

}

执行start方法时,执行的是线程方法:

执行run方法时,执行的是业务方法,而且是死循环执行:

7、由此可知:Thread类是 Java 里对线程概念的抽象,可以这样理解:我们通过 new Thread() 其实只是 new 出一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。 只有执行了 start() 方法后,才实现了真正意义上的启动线程。 start()方法让一个线程进入就绪队列等待分配 cpu ,分到 cpu 后才调用实现 的 run() 方法, start() 方法不能重复调用,如果重复调用会抛出异常。 而 run 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方 法并没有任何区别,可以重复执行,也可以被单独调用。阻塞方法中断线程,核心方法:
/**
 *说明:阻塞方法中抛出InterruptedException异常后,如果需要继续中断,需要手动再中断一次
 */
@Override
		public void run() {
			while(!isInterrupted()) {
				try {
					Thread.sleep(100);//休眠阻塞方法
				} catch (InterruptedException e) {
					System.out.println(Thread.currentThread().getName()
							+" in InterruptedException interrupt flag is "
							+isInterrupted());
					//手动资源释放
					interrupt();
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()
						+ " I am extends Thread.");
			}
			System.out.println(Thread.currentThread().getName()
					+" interrupt flag is "+isInterrupted());
		}
 
8、总之,安全的中止则是其他线程通过调用某个线程 A interrupt() 方法对其进行中 断操作, 中断好比其他线程对该线程打了个招呼,“ A ,你要中断了”,不代表
线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。 线程通过检查自身的中断标志位是否被置为 true 来进行响应, 线程通过方法 isInterrupted() 来进行判断是否被中断,也可以调用静态方法 Thread.interrupted() 来进行判断当前线程是否被中断,不过 Thread.interrupted() 会同时将中断标识位改写为 false 。 如果一个线程处于了阻塞状态(如线程调用了 thread.sleep thread.join 、 thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true ,则会在 这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即 将线程的中断标示位清除,即重新设置为 false
不建议自定义一个取消标志位来中止线程的运行 。因为 run 方法里有阻塞调 用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取 消标志。这种情况下,使用中断会更好,因为:
一、一般的阻塞方法,如 sleep 等本身就支持中断的检查,
二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可 以避免声明取消标志位,减少资源的消耗。
注意:处于死锁状态的线程无法被中断
yield() 方法:使当前线程让出 CPU 占有权,但让出的时间是不可设定的。也 不会释放锁资源。注意:并不是每个线程都需要这个锁的,而且执行 yield( ) 的线 程不一定就会持有锁,我们完全可以在释放锁后再调用 yield 方法。 所有执行 yield() 的线程有可能在进入到就绪状态后会被操作系统再次选中 马上又被执行。
 
9、join 方法:
把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。 比如在线程 B 中调用了线程 A Join() 方法,直到线程 A 执行完毕后,才会继续
执行线程 B

/**
 *类说明:演示Join方法的使用
 */
public class UseJoinTest {
	
    static class Goddess implements Runnable {
        private Thread thread;

        public Goddess(Thread thread) {
            this.thread = thread;
        }

        public void run() {
            System.out.println("Goddess开始排队打饭.....");
            try {
                if(thread!=null) thread.join();
            } catch (InterruptedException e) {
            }
            SleepTools.second(2);//休眠2秒
            System.out.println(Thread.currentThread().getName()
                    + " Goddess打饭完成.");
        }
    }

    static class GoddessBoyfriend implements Runnable {

        public void run() {
            SleepTools.second(2);//休眠2秒
            System.out.println("GoddessBoyfriend开始排队打饭.....");
            System.out.println(Thread.currentThread().getName()
                    + " GoddessBoyfriend打饭完成.");
        }
    }

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

        GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend();
        Thread gbf = new Thread(goddessBoyfriend);
        Goddess goddess = new Goddess(gbf);
        Thread g = new Thread(goddess);
        g.start();
        gbf.start();
        System.out.println("nan开始排队打饭.....");
        g.join();//插队。。。
        SleepTools.second(2);//让主线程休眠2秒
        System.out.println(Thread.currentThread().getName() + " nan打饭完成.");
    }
}

执行结果:

10、线程的优先级
Java 线程中,通过一个整型成员变量 priority 来控制优先级,优先级的范 围从 1~10 ,在线程构建的时候可以通过 setPriority(int) 方法来修改优先级,默认 优先级是 5 ,优先级高的线程分配时间片的数量要多于优先级低的线程。 设置线程优先级时,针对频繁阻塞(休眠或者 I/O 操作)的线程需要设置较 高优先级,而偏重计算(需要较多 CPU 时间或者偏运算)的线程则设置较低的 优先级,确保处理器不会被独占。在不同的 JVM 以及操作系统上,线程规划会 存在差异,有些操作系统甚至会忽略对线程优先级的设定。
11、线程的调度
线程调度是指系统为线程分配 CPU 使用权的过程,主要调度方式有两种: 协同式线程调度(Cooperative Threads-Scheduling) 抢占式线程调度(Preemptive Threads-Scheduling) 使用协同式线程调度的多线程系统,线程执行的时间由线程本身来控制,线 程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。使用协同 式线程调度的最大好处是实现简单,由于线程要把自己的事情做完后才会通知系 统进行线程切换,所以没有线程同步的问题,但是坏处也很明显,如果一个线程 出了问题,则程序就会一直阻塞。 使用抢占式线程调度的多线程系统,每个线程执行的时间以及是否切换都由 系统决定。在这种情况下,线程的执行时间不可控,所以不会有「一个线程导致 整个进程阻塞」的问题出现。 在 Java 中, Thread.yield() 可以让出 CPU 执行时间,但是对于获取,线程本身 是没有办法的。对于获取 CPU 执行时间,线程唯一可以使用的手段是设置线程 优先级,Java 设置了 10 个级别的程序优先级,当两个线程同时处于 Ready 状态 时,优先级越高的线程越容易被系统选择执行。 Java 中的线程优先级是通过映射到操作系统的原生线程上实现的,所以线程 的调度最终取决于操作系统,操作系统中线程的优先级有时并不能和 Java 中的 一一对应,所以 Java 优先级并不是特别靠谱。 所以在面试中如果遇到相关的问题,可以这样回答:Java 中的线程是通过映 射到操作系统的原生线程上实现的,所以线程的调度最终取决于操作系统,而操 作系统级别,OS 是以抢占式调度线程,我们可以认为线程是抢占式的。 Java 虚 拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用 CPU ,如 果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用 CPU 。处 于运行状态的线程会一直运行,直至它不得不放弃 CPU 。而且操作系统中线程的 优先级有时并不能和 Java 中的一一对应,所以 Java 优先级并不是特别靠谱。但 是在 Java 中,因为 Java 没有提供安全的抢占式方法来停止线程,要安全的停止 线程只能以协作式的方式。
12、守护线程
Daemon (守护)线程是一种支持型线程,因为它主要被用作程序中后台调 度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在 Daemon 线程的 时候,Java 虚拟机将会退出。可以通过调用 Thread.setDaemon(true) 将线程设置 为 Daemon 线程。我们一般用不上,比如垃圾回收线程就是 Daemon 线程。 Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线 程中的 finally 块并不一定会执行。在构建 Daemon 线程时,不能依靠 finally 块中 的内容来确保执行关闭或清理资源的逻辑。
 
总结如图:
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅灯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值