并发编程(六):Java并发编程基础


学习资料

《Java并发编程的艺术》第4章 4.1~4.3


1.线程简介

1.1 什么是线程

现代操作系统调度的最小单元,也叫轻量级进程

一个进程可以创建多个线程,线程有各自的计数器,堆栈和局部变量等属性

处理器在线程上高速切换(时间片调度),让使用者感觉到这些线程是同时执行的

1.2 为什么要使用多线程

使用多线程的原因:

  1. 更多的处理器核心:一个线程在同一时刻只能运行在一个CPU核上,有多个核的cpu就可以在同时刻运行多个线程,提高运行速度(并行)
  2. 更快的响应速度:将数据一致性不强的操作派发给其他线程处理(也可以是消息队列),能让请求更快处理完成,缩短响应时间
  3. 更好的编程模型:Java提供了良好且一致的多线程编程模型

1.3 线程优先级

thread.setPriority(n):设置线程优先级,1~10,默认为5

有些操作系统会忽略优先级的设置,设置优先级没有效果(类Unix操作系统)

在Wnidows下,优先级高的线程分配的时间片数量要多于优先级低的线程:

  • 频繁阻塞的线程(IO或休眠),应设置较高优先级
  • 偏重计算(CPU密集型)的线程应设置较低优先级,防止独占cpu

1.4 线程运行状态

线程六种状态:初始,运行,阻塞,等待,超时等待,终止

线程状态切换:

注意:

  • Java将操作系统线程状态的运行和就绪状态合并为运行态
  • synchronized对应的是阻塞状态,但是JUC的Lock接口对应的却是(超时)等待状态,因为JUC中该接口的实现都使用了LockSupport类的方法

1.5 Daemon线程

也叫守护线程,后台线程

JVM中没有非Daemon线程时,所有的Daemon线程都要立即终止,可能会导致Daemon线程中的代码未执行完毕,Daemon线程中的finally块也不一定会执行

thread.setDaemon(true);


2.启动和终止线程

2.1 构造线程

构造线程是通过父线程来构造的,在Thread类的init方法中进行构造

private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) { 
	//1.name参数有效性检查
	//2.设置传入参数的值
	//3.指定当前线程为父线程
	//4.deamon,priority属性设置为父线程对应的属性
	//5.将父线程的InheritableThreadLocal复制过来
	//6.分配一个线程ID标记该线程
}

2.2 启动线程

thread.start():当前线程(父线程)告知JVM,只要线程规划器空闲,立即启动thread线程

最好为线程设置名称thread.setName(name),方便使用jstack排查问题

2.3 理解中断

中断可以理解为线程的一个标识位属性

thread.interrupt():其他线程调用thread的该方法,将thread标记为中断

thread.isInterrupted():true表示thread有中断标记,false表示没有中断标记,对终止状态的线程调用该方法返回结果都是false

Thread.interrupted():清除该类对象的所有中断标记

2.4 过期的suspend()、resume()和stop()

thread.suspend()thread.resume()thread.stop()

过期API不建议使用,不会释放(suspend)或者不会正确释放(stop)占有资源,导致程序出现不确定的状态

suspend/resume暂停挂起可以使用等待通知机制来替代

2.5 安全地终止线程

通过对中断状态的交互控制来,还可以通过对Volatile类型的boolean变量来控制


3.线程间通信

3.1 volatile和synchronized关键字

volatile修饰字段,表示任何对该变量的访问都要从共享内存中获取,且对它的改变也必须刷新回共享内存,保证所有线程对变量访问的可见性

Synchronized可以修饰方法或者以同步块的形式来使用,确保多个线程在同一时刻只有一个线程处于方法或同步块中

  • 本质是对一个对象监视器(monitor)进行获取,排他,同一时刻只能有一个线程获取
  • 每个对象都有自己的监视器,只有获取到该监视器的线程才能进入到同步块,否则就会阻塞在同步队列SynchronizedQueue

Synchronized示意图:

3.2 等待/通知机制及范式

等待方:消费者,WaitThread

  • 等待方原则:

    1. 要获取对象的锁
    2. 条件不满足,调用对象的wait()方法,被通知后仍要检查条件
    3. 条件满足则执行对应的逻辑
  • 示例代码:

    synchronized(obj){//锁对象
    	while(条件不满足){
    		obj.wait();
    	}
    	//对应处理逻辑
    }
    

通知方:生产者,NotifyThread

  • 通知方原则:

    1. 要获取对象的锁
    2. 改变条件
    3. 通知所有等待在对象上的线程
  • 示例代码:

    synchronized(obj){
    	//修改条件
    	obj.notifyAll();
    }
    

示意图:

3.3 管道输入输出流

管道输入/输出流用于线程之间的数据传输,传输媒介为内存,有四种具体实现:

  • 字节:
    • PipedOutputStream
    • PipedInputStream
  • 字符:
    • PipedWriter
    • PipedReader

需要使用 connect() 将输入流和输出流绑定,否则会抛出IOException

示例代码:

...main(...){
	PipedWriter out=new PipedWriter();
	PipedReader in=new PipedReader();
	out.connect(in);    //绑定
	Thread inThread=new Thread(new InThread(in));	//读取数据的线程
	inThread.start();
    ...
	out.write("A"); //main线程写
    ...
}

public class InThread implements Runnable{	
    private PipedReader in;
    public InThread(PipedReader in){
        this.in;
    }
    public void run(){
        ...
        in.read();	//读取
        ...
    }
}

3.4 thread.join()的使用

thread.join():当前线程等待,thread线程执行终止后才继续执行当前线程

逻辑结构与等待/通知类似

超时重载类型:join(long millis)join(long millis,int nanos)

3.5 ThreadLocal的使用

ThreadLocal,线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构

一个线程可以通过ThreadLocal对象查询到绑定在这个线程上的一个值(线程独有的值)

示例代码:

ThreadLocal<LONG> tll=new ThreadLocal<>();
tll.set();//设置值
tll.get();//获取值

posted @ 2021-03-11 20:51  菜鸟kenshine  阅读(79)  评论(0编辑  收藏  举报