我是靠谱客的博主 孝顺日记本,最近开发中收集的这篇文章主要介绍JAVA并发编程(8)-线程基础看这一篇就够啦,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

为什么使用多线程?
1)多核心CPU时代单线程浪费cpu,多线程可以充分利用cpu计算能力
2)提高执行效率减少程序的响应时间
线程优先级
可以通过setPriority 来设置优先级 1-10 优先级一次递增。 一般高优先级的线程获得执行的几率比低优先级的线程大。但是线程并不是严格按照优先级来执行的。比如Mac OS X 10.10,Java版本为1.7.0_71 环境下,不管设置线程优先级为多少,运行时线程的优先级都是5(设置优先级不起作用).所以不能用优先级来保证程序的正确性。

线程的状态
线程的状态有 new 新建 、runnable 可执行、blocked 阻塞 、waiting 等待、timewaiting 限时等待、terminated 终结
值得指出的是只有在synchronize 关键字阻塞的线程才是 bolcked状态。被锁阻塞的线程是waiting或timewaiting 状态。因为并发包中lock 用的都是 LockSupport的相关方法(park unpark)。
线程在等待一个io时是什么状态呢? 一般会说线程会阻塞。这个阻塞其实是说在CPU层面上线程会进入休眠状态,但是在JVM 中 jvm会将这种情况看作和线程等待CPU时间片是同一种情况都是等待系统资源。而且jvm不用关心CPU中线程的状态。所以JVM 中这种情况下线程是 runnable状态.

Daemon线程
通过setDaemon(true)方法设置线程为 daemon线程。如果一个进程中所有非daemon线程都已执行结束,则进程结束(无论是否有daemon线程在执行)。
daemon线程主要用来完成辅助支持类的工作,java虚拟机退出时 daemon线程的 fianlly块未必会执行。所以daemon线程中不能依靠finally块来释放资源。

创建一个线程
继承Thread类或实现Runnable接口,重写run方法即可。
new Thread 可以传入的参数 ThreadGroup 线程组、Runnable 执行对象、线程名、期望分配的内存等。
Thread 中持有一个 Runnable 类型的对象,如果这个值不为空,Thread 的run 方法 会 执行runnable 对象的 run 方法。 所以重写 继承重写 Thread 的run 方法 和 传入 实现了Run方法的 Runnable对象都行。

   @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

启动一个线程
直接调用线程实例的 start() 方法。start() 方法 含义:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动被调用start()方法的线程

线程的中断
中断可以理解为线程的一个标识位属性,通过isInterrupted 可以获得这个值(默认为false),它表示一个运行中的线程是否被其他线程进行了中断操作。
1)中断操作: 线程实例的interrupt() 方法 调用完该方法 一般再调用 isInterrupted()返回true (默认是false) 。
2)中断标志的复位:Thread.interrupted() 即调用完该方法 会将 线程的中断标志位进行复位 (isInterrupted方法返回false)。
3)如果线程已经处于完结状态 isInterrupted 都返回false(即使被中断过).
4)对于可能抛出 InterruptedException 的方法 比如 Thread.sleep(long millis) ,执行该方法后,在其他线程中调用 线程的 interrupt 方法,会抛出 InterruptedException 异常,并且抛出这个异常前会将中断标识为复位成false.

过期的suspend resume stop 方法
suspend()、resume()和stop()方法可以完成线程的暂停、恢复和终止工作,而且非常“人性化”,但是API是过期的。原因是 suspend 方法 被调用后不会释放占用的资源 比如锁,容易引发死锁问题。stop方法一旦被执行,无论线程执行到什么位置,都会立刻退出,无法保证占用的资源正常释放。会造成不可预知的问题。
类似的这种 等待和运行状态切换可以通过 通知机制实现。

如何安全的终止线程
通过 interrupt 或者volatile 变量(如 private volatile boolean on = true )实现线程通信 .需要被控制的线程 通过while(!on ){} 来判断是否继续执行。这样线程就会根据条件判断是不是应该继续执行,如果不应该继续执行(while条件不满足)则线程自动结束。即实现了在安全点结束线程。避免了资源无法释放等未知问题。

线程间通信
1、volatile 通过内存可见性 保证线程之间可以通过修改 volatile类型变量的值来传递信息。 参见前边关于volatile语义相关内容
2、synchronized 在编译的java class文件中,对于同步块的实现使用了monitorenter和monitorexit指令,同步方法则依靠方法修饰符上增加 ACC_SYNCHRONIZE标识 来实现临界代码的 单线程访问。且每次访问临界代码对后续获得锁的线程可见。
3、juc包下的锁 原理同 volatile

等待通知机制
简单理解就是 没有任务的时候线程处于等待状态,一旦有了任务则被唤醒。开始执行。
1、粗暴的实现方式 while() 判断 为true 就打印,否则就轮训判断。如下

 while (value != desire) {
Thread.sleep(1000);
}
doSomething();

显然这种不太好 每次睡的时间长了 执行任务不及时,睡的时间短了空转浪费CPU. 无法达到一种合适的状态。
2、wait notify实现方式。通过 wait wait(long) wait(long,int) notify() notifyall() 方法实现,经典实现范式如下
1)线程A获取对象锁
2)线程Awhile()循环中判断如果条件不满足 则调用锁对象的wait()方法,释放获得的锁对象。并进入wating状态
3)条件满足后 线程B 获得锁后调用 锁对象的 notify/notifyAll 唤醒正在等待的线程A,待线程B释放锁,线程A获得锁后开始执行。
wait 和 notify 方法被调用的时候必须持有锁,如下:
线程A

synchronized (lock) {
	while (flag) {
		try {
			System.out.println(“没有工作进入等待”);
			lock.wait(); // 当条件不满足时,一直wait,同时释放了lock的锁 线程进入 锁的等待队列 状态变为 waiting 
		} catch (InterruptedException e) {
		}
	}
	System.out.println(“执行工作”);
}

线程B

synchronized (lock) {
	// 获取lock的锁,然后进行通知,通知时不会释放lock的锁,
	// 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
	System.out.println(“唤醒线程A”);
	lock.notifyAll(); //唤醒锁对象等待队列中 的所有线程(包括线程A)。这些线程进入锁的阻塞队列(竞争锁)。状态由waiting 变为blocked 
	flag = false;
	SleepUtils.second(5);
	//线程B执行完 释放锁 然后 锁阻塞队列中的线程开始竞争锁 如果线程A竞争到 锁那么线程A 从wait 方法返回 并开始执行逻辑
}

还有一种延迟等待的写法,比如获取数据库连接最多等待1s, 写法如下 相比上边就是增加了超时时间的处理:

public Connection fetchConnection(long mills) throws InterruptedException {
	synchronized (pool) {
		// 完全超时
		if (mills <= 0) {
			while (pool.isEmpty()) {
				pool.wait();
			}
			return pool.removeFirst();
		} else {
			long future = System.currentTimeMillis() + mills;
			long remaining = mills;
			while (pool.isEmpty() && remaining > 0) {
				pool.wait(remaining);
				remaining = future - System.currentTimeMillis();
			}
			Connection result = null;
			if (!pool.isEmpty()) {
				result = pool.removeFirst();
			}
			return result;
		}
	}
}

管道输入输出流
管道输入输出流和普通的文件输入输出流或者网络io相似,只不过它使用在线程之间数据传输的。传出的媒介为内存。 主要类有PipedReader PipedOutputStream PipedWriter PipedIntputStream
用法就是创建对象进行连接,将引用分别传给不同的线程,线程就可以从中写入/读取数据了:

PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 将输出流和输入流进行连接,否则在使用时会抛出IOException
out.connect(in);
Thread printThread = new Thread(new Print(in), "PrintThread");
printThread.start();
int receive = 0;
try {
	while ((receive = System.in.read()) != -1) {
 		out.write(receive);
	 }
}

Thread.join()
ThreadA中如果调用了ThreadB.join(),意思就是等到ThreadB执行完了,Thread才会继续往下执行。查看源码会发现其实 就是调用了ThreadB 的 wait 方法,当前线程进入ThreadB的等待队列,等ThreadB执行完成,JVM会自动调用ThreadB.notifyAll 方法。来唤醒等待的线程。源码如下:

在这里插入图片描述

ThreadLocal
是一个以ThreadLocal对象为键、任意对象为值的存储结构.能够实现同一个属性为不同的线程保存不同的值。原理如下 每个Thread 对象都有一个ThreadLocal.ThreadLocalMap 对象属性如下:

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap set 方法 源码中,会取到当前线程的上述变量,然后以当前 ThreadLocal 对象为键,将值保存在这个map中。 get方法原理相同
在这里插入图片描述

最后

以上就是孝顺日记本为你收集整理的JAVA并发编程(8)-线程基础看这一篇就够啦的全部内容,希望文章能够帮你解决JAVA并发编程(8)-线程基础看这一篇就够啦所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(76)

评论列表共有 0 条评论

立即
投稿
返回
顶部