java线程认识

      进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
     线程是指进程内的一个执行单元,也是进程内的可调度实体. 线程是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

 

Java 程序天生就是多线程的
一个 Java 程序从 main() 方法开始执行,然后按照既定的代码逻辑执行,看
似没有其他线程参与,但实际上 Java 程序天生就是多线程程序,因为执行 main()
方法的是一个名称为 main 的线程。
[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 线程,用户程序入口
 

 

线程的启动与中止
启动
启动线程的方式有:
1 X extends Thread; ,然后 X.start
2 X implements Runnable ;然后交给 Thread 运行
参见代码: cn.enjoyedu.ch1.base.NewThread Thread Runnable 的区别
Thread 才是 Java 里对线程的唯一抽象, Runnable 只是对任务(业务逻辑)
的抽象。 Thread 可以接受任意一个 Runnable 的实例并执行。
 
中止
 
线程自然终止
要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
stop
暂停、恢复和停止操作对应在线程 Thread API 就是 suspend() resume()
stop() 。但是这些 API 是过期的,也就是不建议使用的。不建议使用的原因主
要有:以 suspend() 方法为例,在调用后,线程不会释放已经占有的资源(比如
锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样, stop()
法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资
源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为 suspend()
resume() stop() 方法带来的副作用,这些方法才被标注为不建议使用的过期方
法。
中断
安全的中止则是其他线程通过调用某个线程 A interrupt() 方法对其进行中
断操作 , 中断好比其他线程对该线程打了个招呼,“ A ,你要中断了”,不代表
线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。
因为 java 里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志
位是否被置为 true 来进行响应,
线程通过方法 isInterrupted() 来进行判断是否被中断,也可以调用静态方法
Thread.interrupted() 来进行判断当前线程是否被中断,不过 Thread.interrupted()
会同时将中断标识位改写为 false
如果一个线程处于了阻塞状态(如线程调用了 thread.sleep thread.join
thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true ,则会在
这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即
将线程的中断标示位清除,即重新设置为 false
不建议自定义一个取消标志位来中止线程的运行 。因为 run 方法里有阻塞调
用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取
消标志。这种情况下,使用中断会更好,因为,
一、一般的阻塞方法,如 sleep 等本身就支持中断的检查,
二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可
以避免声明取消标志位,减少资源的消耗。
 
注意:处于死锁状态的线程无法被中断
 
 

 

Java 里的线程再多一点点认识
深入理解 run() start()
 
Thread 类是 Java 里对线程概念的抽象,
可以这样理解:我们通过 new Thread()
其实只是 new 出一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。
只有执行了 start() 方法后,才实现了真正意义上的启动线程。
start() 方法让一个线程进入就绪队列等待分配 cpu ,分到 cpu 后才调用实现
run() 方法, start() 方法不能重复调用,如果重复调用会抛出异常。
run 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方
法并没有任何区别,可以重复执行,也可以被单独调用。
 
其他的线程相关方法
 
yield() 方法:使当前线程让出 CPU 占有权,但让出的时间是不可设定的。也
不会释放锁资源。注意:并不是每个线程都需要这个锁的,而且执行 yield( ) 的线
程不一定就会持有锁,我们完全可以在释放锁后再调用 yield 方法。
所有执行 yield() 的线程有可能在进入到就绪状态后会被操作系统再次选中
马上又被执行。
 
join 方法
 
把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。
比如在线程 B 中调用了线程 A Join() 方法,直到线程 A 执行完毕后,才会继续
执行线程 B ( 此处为常见面试考点 )
 
线程的优先级
 
Java 线程中,通过一个整型成员变量 priority 来控制优先级,优先级的范
围从 1~10 ,在线程构建的时候可以通过 setPriority(int) 方法来修改优先级,默认
优先级是 5 ,优先级高的线程分配时间片的数量要多于优先级低的线程。
设置线程优先级时,针对频繁阻塞(休眠或者 I/O 操作)的线程需要设置较
高优先级,而偏重计算(需要较多 CPU 时间或者偏运算)的线程则设置较低的
优先级,确保处理器不会被独占。在不同的 JVM 以及操作系统上,线程规划会
存在差异,有些操作系统甚至会忽略对线程优先级的设定。
 
守护线程
 
Daemon (守护)线程是一种支持型线程,因为它主要被用作程序中后台调
度以及支持性工作。这意味着,当一个 Java 虚拟机中不存在 Daemon 线程的
时候, Java 虚拟机将会退出。可以通过调用 Thread.setDaemon(true) 将线程设置
Daemon 线程。我们一般用不上,比如垃圾回收线程就是 Daemon 线程。 Daemon 线程被用作完成支持性工作,但是在 Java 虚拟机退出时 Daemon 线
程中的 finally 块并不一定会执行。在构建 Daemon 线程时,不能依靠 finally 块中
的内容来确保执行关闭或清理资源的逻辑。
 
线程间的共享和协作
 
线程间的共享
 
synchronized 内置锁
线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码
一步一步地执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立地运行,
那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作,
包括数据之间的共享,协同处理事情。这将会带来巨大的价值。
Java 支持多个线程同时访问一个对象或者对象的成员变量,关键字
synchronized 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线
程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量
访问的可见性和排他性,又称为内置锁机制。
对象锁和类锁:
对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态
方法或者一个类的 class 对象上的。我们知道,类的对象实例可以有很多个,但
是每个类只有一个 class 对象,所以不同对象实例的对象锁是互不干扰的,但是
每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存
在的,类锁其实锁的是每个类的对应的 class 对象。类锁和对象锁之间也是互不
干扰的。
 
错误的加锁和原因分析
参见代码 cn.enjoyedu.ch1. syn.TestIntegerSyn 原因:虽然我们对 i 进行了加锁,但是
但是当我们反编译这个类的 class 文件后,可以看到 i++ 实际是,
本质上是返回了一个新的 Integer 对象。也就是每个线程实际加锁的是不同
Integer 对象。
 
 
volatile ,最轻量的同步机制
 
volatile 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某
个变量的值,这新值对其他线程来说是立即可见的。参见代码:
cn.enjoyedu.ch1.vola. VolatileCase
不加 volatile 时,子线程无法感知主线程修改了 ready 的值,从而不会退出循环,
而加了 volatile 后,子线程可以感知主线程修改了 ready 的值,迅速退出循环。
但是 volatile 不能保证数据在多个线程下同时写时的线程安全,参见代码:
cn.enjoyedu.ch1.vola. NotSafe volatile 最适用的场景:一个线程写,多个线程读。
ThreadLocal 辨析
Synchonized 的比较
ThreadLocal Synchonized 都用于解决多线程并发訪问。可是 ThreadLocal
synchronized 有本质的差别。 synchronized 是利用锁的机制,使变量或代码块
在某一时该仅仅能被一个线程訪问。而 ThreadLocal 为每个线程都提供了变量的
副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线
程对数据的数据共享。
Spring 的事务就借助了 ThreadLocal 类。
Spring 会从数据库连接池中获得一个
connection ,然会把 connection 放进 ThreadLocal 中,也就和线程绑定了,事务需
要提交或者回滚,只要从 ThreadLocal 中拿到 connection 进行操作。为何 Spring
的事务要借助 ThreadLocal 类?
JDBC 为例,正常的事务代码可能如下:
dbc = new DataBaseConnection();// 1
Connection con = 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 的情况。
如果不使用 ThreadLocal ,代码大概就会是这个样子:
 
ThreadLocal 的使用
 
ThreadLocal 类接口很简单,只有 4 个方法,我们先来了解一下:
 
void set(Object value)
设置当前线程的线程局部变量的值。
 
public Object get()
该方法返回当前线程所对应的线程局部变量。
 
public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是 JDK
 
5.0 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动
被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它
可以加快内存回收的速度。
 
protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个 protected 的方法,显然是为
了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第 1 次调用 get()
set(Object) 时才执行,并且仅执行 1 次。 ThreadLocal 中的缺省实现直接返回一
null
 
上面先取到当前线程,然后调用 getMap 方法获取对应的 ThreadLocalMap
ThreadLocalMap ThreadLocal 的静态内部类,然后 Thread 类中有一个这样类型
成员,所以 getMap 是直接返回 Thread 的成员。
看下 ThreadLocal 的内部类 ThreadLocalMap 源码:
 
可以看到有个 Entry 内部静态类,它继承了 WeakReference ,总之它记录了
两个信息,一个是 ThreadLocal<?> 类型,一个是 Object 类型的值。 getEntry 方法
则是获取某个 ThreadLocal 对应的值,
set 方法就是更新或赋值相应的 ThreadLocal
对应的值。
 
 

 

         回顾我们的 get 方法,其实就是拿到 每个线程独有的 ThreadLocalMap
然后再用 ThreadLocal 的当前实例,拿到 Map 中的相应的 Entry ,然后就可
以拿到相应的值返回出去。当然,如果 Map 为空,还会先进行 map 的创建,初
始化等工作。

 

 

等待 / 通知机制
 
是指一个线程 A 调用了对象 O wait() 方法进入等待状态,而另一个线程 B
调用了对象 O notify() 或者 notifyAll() 方法,线程 A 收到通知后从对象 O wait()
方法返回,进而执行后续操作。上述两个线程通过对象 O 来完成交互,而对象
上的 wait() notify/notifyAll() 的关系就如同开关信号一样,用来完成等待方和通
知方之间的交互工作。
 
notify()
 
通知一个在对象上等待的线程 , 使其从 wait 方法返回 , 而返回的前提是该线程
获取到了对象的锁,没有获得锁的线程重新进入 WAITING 状态。
 
notifyAll()
 
通知所有等待在该对象上的线程
 
wait()
 
调用该方法的线程进入 WAITING 状态 , 只有等待另外线程的通知或被中断
才会返回 . 需要注意 , 调用 wait() 方法后 , 会释放对象的锁
 
wait(long)
 
超时等待一段时间 , 这里的参数时间是毫秒 , 也就是等待长达 n 毫秒 , 如果没有
通知就超时返回
 
wait (long,int)
 
对于超时时间更细粒度的控制 , 可以达到纳秒
等待和通知的标准范式
等待方遵循如下原则。
1 )获取对象的锁。
2 )如果条件不满足,那么调用对象的 wait() 方法,被通知后仍要检查条件。
 
 
 
                             在调用 wait ()、 notify() 系列方法之前,线程必须要获得该对象的对象级 别锁,即只能在同步方法或同步块中调用 wait ()方法、 notify() 系列方法 ,进 入 wait ()方法后,当前线程释放锁,在从 wait ()返回前,线程与其他线程竞 争重新获得锁,执行 notify() 系列方法的线程退出调用了 notifyAll synchronized 代码块的时候后,他们就会去竞争。如果其中一个线程获得了该对象锁,它就会 继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的 线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。
 
调用 yield() sleep() wait() notify() 等方法对锁有何影响?
    yield() sleep() 被调用后,都不会释放当前线程所持有的锁。
   调用 wait() 方法后,会释放当前线程持有的锁,而且当前被唤醒后,会重新
   去竞争锁,锁竞争到后才会执行 wait 方法后面的代码。
   调用 notify() 系列方法后,对锁无影响,线程只有在 syn 同步代码执行完后才
   会自然而然的释放锁,所以 notify() 系列方法一般都是 syn 同步代码的最后一行。
 
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值