一、基础概念
1.什么是进程和线程
进程是操作系统进行资源分配的最小单位,一个进程下是可以拥有多个线程的
线程是CPU调度的最小单位,不能独立于进程存在,可分享进程的资源
2.CPU核心数和线程数的关系
一般情况下——cpu核心数:线程数 = 1:1
intel引用超线程机制后 = 1:2
3.CPU时间轮转机制
并发编程之 CPU 时间片轮转机制 (RR 调度)_Java_谭青海-CSDN博客
4.并行与并发
并行:同时运行的任务数
eg:一台咖啡机能够给一人提供服务,那么并行数就为1,如果两台,那么并行数就为2
并发:一个时间段内能够执行的任务数
eg:一台咖啡机在10分钟内可以产出两杯咖啡,那么我们就可以认为并发数为10分钟内并发数为2
思考:JAVA为什么引入并发编程
1.充分利用CPU资源;
2.加快用户响应的时间;
二、认识JAVA里的线程
JAVA中的线程是协作的并不是抢占的
1.java里的程序天生就是多线程的,那么有几种新启线程的方式?
为什么说java天生就是多线程的,那么我们证明一下
public static void main(String[] args) {
//JAVA虚拟机线程系统管理接口
ThreadMXBeanthreadMXBean= ManagementFactory.getThreadMXBean();
//仅获得线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false,false);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() +"]" + threadInfo.getThreadName());
}
}
运行后我们会发现控制台上为我们打印了很多线程信息(线程信息仅供了解,不是重点)
[8]JDWP Command Reader
[7]JDWP Event Helper Thread
[6]JDWP Transport Listener: dt_socket
[5]Attach Listener --- dump线程信息线程
[4]Signal Dispatcher
[3]Finalizer --- 对象资源回收线程(守护线程)
[2]Reference Handler --- 回收引用处理线程
[1]main --- 主线程main
仅有一个main方法就为我们打印了很多线程,说明我们的JAVA天生就是多线程协作的
几种新启线程的方式
JDK Thread源码注释提供
JDK中告诉我们仅有两种方式
(1)extends Thread
(2)implements Runnable
(3)implements Callable 注:Callable 是有返回值的,可以认为和Runnable为一种
以下为代码实现
public class Theadextends Thread {
@Override
public void run() {
System.out.println("继承自Thread");
}
}
public class Thead1implements Runnable {
@Override
public void run() {
System.out.println("实现Runnable");
}
}
public class Thead2implements Callable {
@Override
public Object call()throws Exception {
return "实现Callable";
}
}
那么我们说Thread和Runnable 有什么区别呢?
1.Thead是对线程的抽象,而Runable是对任务的抽象
2.如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
3.main函数,实例化线程对象也有所不同,
extends Thread :t.start();
implements Runnable : new Thread(t).start();
4.使用Runnable,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
5.线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
2.有开始就有结束,怎样才能让JAVA里的线程安全的停止呢?
stop()还是interrupt()、isInterrupted()、static方法interrupted() =>我们需要深入了解这些方法
stop() --- 强制停止当前线程
我们通过Thread的源码中可看到stop方法被标记了废弃标签,这说明JDK告诉我们不推荐使用,因为它带有强制作用,并不能保证线程的安全与资源的回收
interrupt() --- 对线程进行通知中断(其实是设置线程的中断标识位)
我们可以认为是向我们的线程打个招呼,告诉当前线程你该停止了,但并非为强制中断,是否中断由线程自己决定
isInterrupted() --- 判断当前线程是否被中断
通常用来在线程run方法中进行判定业务是否进行
interrupted() --- 给线程一个中断标记位
也可以用来判断线程是否被中断,但同时将中断标识位由true改为false
*在Runnable 中使用时需执行Thread.currentThread()方法,调取当前线程,Runnable 中并未提供以上方法
*当阻塞方法抛出InterruptedException时,线程是不会被中断的,需我们手动在catch中重新调用interrupt()方法
*处于死锁状态的线程是不会理会中断的
三、对Java里的线程再多一点点认识
线程的生命周期
1.线程常用方法和线程的状态
深入理解run()和start()
run()方法为线程执行业务的方法,执行结束后死亡
start()线程进入一个就绪状态(可执行状态),每个线程仅可调用一次,等待时间片轮转,当cpu分配时间后可进入运行状态
wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)
notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程
wait(long timeout)让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)
Java线程中的Thread.yield( )方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。
yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行
yield() 的作用仅是让出cpu ,并不会让出锁
join()方法,获取执行权,能够使线程顺序执行,从并行变为串行
setPriority()方法 ,设置优先级,java中优先级分为1-10个级别,默认为5,但优先级并不能够保证线程执行顺序,真正的顺序由cpu决定
守护线程,与主线程共存亡的一个线程,服务于主线程,例如GC线程,new Thread都为用户线程(非守护线程)
setDaemon(boolean on) 设置守护线程,缺省为false,true为守护线程
*注:守护线程中finally不一定起作用,upu时间片分配到会执行,否则不会执行,完全由操作系统的调度决定
四、线程间的共享
1.synchronized 关键字(内置锁)
线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码 一步一步地执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立地运行, 那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作, 包括数据之间的共享,协同处理事情。这将会带来巨大的价值。 Java 支持多个线程同时访问一个对象或者对象的成员变量,关键字 synchronized 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线 程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量 访问的可见性和排他性,又称为内置锁机制。
对象锁和类锁: 对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态 方法或者一个类的 class 对象上的。我们知道,类的对象实例可以有很多个,但 是每个类只有一个 class 对象,所以不同对象实例的对象锁是互不干扰的,但是 每个类只有一个类锁。 但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存 在的,类锁其实锁的是每个类的对应的 class 对象。类锁和对象锁之间也是互不 干扰的
用处和用法
(1)方法加锁(同步方法)、同步块
public synchronized void incCount2() { count++;}
public void incCount3() {
synchronized (this) {
count++;
}
}
(2)对象加锁
private Objectobj =new Object();//作为一个锁
public void incCount() {
synchronized (obj) {
count++;
}
}
(3)类加锁
public synchronized static void incCount4() { count++;}
*注:synchronized 锁对象必须保证锁的对象不能够发生变化
2.volatile 最 轻 量 的 同 步 机 制
volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某 个变量的值,这新值对其他线程来说是立即可见的
volatile 不能保证数据在多个线程下同时写时的线程安全
volatile 最适用的场景:一个线程写,多个线程读
3.ThreadLocal辨析
(1)与 Synchonized的 比 较
ThreadLocal 和 Synchonized 都用于解决多线程并发访问。可是 ThreadLocal 与 synchronized 有本质的差别。synchronized 是利用锁的机制,使变量或代码块 在某一时该仅仅能被一个线程访问。而 ThreadLocal 为每个线程都提供了变量的 副本,使得每个线程在某一时间访问到的并非同一个对象,这样就隔离了多个线 程对数据的数据共享
Spring 的事务就借助了 ThreadLocal 类。 Spring 会从数据库连接池中获得一个 connection,然会把 connection 放进 ThreadLocal 中,也就和线程绑定了
只要从 ThreadLocal 中拿到 connection 进行操作。为何 Spring 的事务要借助 ThreadLocal 类? 以 JDBC 为例,正常的事务代码可能如下:
dbc=newDataBaseConnection();//第 1 行
Connectioncon=dbc.getConnection();//第 2 行
con.setAutoCommit(false);第 3 行
con.executeUpdate(...);//第 4 行
con.executeUpdate(...);//第 5 行
con.executeUpdate(...);//第 6 行
con.commit();第 7 行
上述代码,可以分成三个部分: 事务准备阶段:第 1~3 行 业务处理阶段:第 4~6 行 事务提交阶段:第 7 行 可以很明显的看到,不管我们开启事务还是执行具体的 sql 都需要一个具体 的数据库连接。 现在我们开发应用一般都采用三层结构,如果我们控制事务的代码都放在 DAO(DataAccessObject)对象中,在 DAO 对象的每个方法当中去打开事务和关闭 事务,当 Service 对象在调用 DAO 时,如果只调用一个 DAO,那我们这样实现则 效果不错,但往往我们的 Service 会调用一系列的 DAO 对数据库进行多次操作, 那么,这个时候我们就无法控制事务的边界了,因为实际应用当中,我们的Service 调用的 DAO 的个数是不确定的,可根据需求而变化,而且还可能出现 Service 调 用 Service 的情况
(2)ThreadLocal的 使 用
ThreadLocal 类接口很简单,只有 4 个方法,我们先来了解一下:
• voidset(Objectvalue) 设置当前线程的线程局部变量的值。
• publicObjectget() 该方法返回当前线程所对应的线程局部变量。
• publicvoidremove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK 5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动 被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它 可以加快内存回收的速度。
• protectedObjectinitialValue() 返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为 了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get() 或 set(Object)时才执行,并且仅执行 1 次。ThreadLocal 中的缺省实现直接返回一 个 null。
publicfinalstaticThreadLocal<String>RESOURCE=new ThreadLocal<String>();RESOURCE代表一个能够存放String类型的ThreadLocal对象。 此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是 线程安全的。
以上内容仅代表个人学习的见解,希望可以为大家提供一个学习参考