1.相关概念准备
- 并发性和并行性
- 并发性:在同一时刻,多个指令在不同的处理器上执行
- 并行性:在同一时刻只能有一个指令执行,但多个进程指令被快速的轮换,由于执行速度非常快,宏观上具有多个进程同时执行的效果
- 进程和线程
- 概念:操作系统可以同时执行多个任务,每个任务就是进程,进程中也可以同时执行多个子任务,子任务就是线程
- 该如何理解这句话呢?可以这样理解:线程是进程的组成部分,一个进程至少拥有一个线程,不同的线程之间共享这个进程的全部资源,但是每个线程可以拥有自己独立的堆栈,局部变量.
2.基本使用
java中有三种基本的使用方式:
- 继承
Thread
,重写run()
方法
- 使用方式:
public class ExtendThread extends Thread {
@Override
public void run() {
for (int i = 0; i <50 ; i++) {
System.out.println(this.getName()+" i="+i);
}
}
}
//运行
@org.junit.Test
public void testDemo1(){
for (int i = 0; i <100 ; i++) {
if(i==20){
new ExtendThread().start();
new ExtendThread().start();
new ExtendThread().start();
new ExtendThread().start();
}
}
}
- 运行结果:
Thread-0 i=0
Thread-1 i=0
Thread-1 i=1
Thread-1 i=2
Thread-1 i=3
Thread-1 i=4
Thread-1 i=5
Thread-1 i=6
Thread-1 i=7
Thread-1 i=8
Thread-0 i=1
Thread-1 i=9
Thread-0 i=2
...
- 实现
Runable
接口,重写run()
方法:
- 使用方式:
public class ImplRunable implements Runnable {
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+" i="+i);
}
}
}
//运行
@org.junit.Test
public void testDemo1(){
for (int i = 0; i <100 ; i++) {
if(i==20){
new ExtendThread().start();
new ExtendThread().start();
new ExtendThread().start();
new ExtendThread().start();
}
}
}
- 运行结果:
“`
Thread-1 i=0
Thread-1 i=1
Thread-1 i=2
Thread-1 i=3
Thread-1 i=4
Thread-1 i=5
Thread-1 i=6
Thread-1 i=7
Thread-1 i=8
Thread-1 i=9
Thread-1 i=10
Thread-1 i=11
Thread-1 i=12
Thread-0 i=0
Thread-0 i=1
…
- 通过实现`Callable`接口,重写`call()`方法,最后用`Future`来包装成`Runable`:
- 使用方式:
```java
public class ImplCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i=0;
for (; i<50 ; i++) {
System.out.println(Thread.currentThread().getName()+" i="+i);
}
return i;
}
}
//运行
@org.junit.Test
public void testDemo3(){
for (int i = 0; i <100 ; i++) {
if(i==20){
new Thread(new FutureTask(new ImplCallable())).start();
new Thread(new FutureTask(new ImplCallable())).start();
}
}
}
<div class="se-preview-section-delimiter"></div>
- 运行结果:
Thread-0 i=0
Thread-1 i=0
Thread-0 i=1
Thread-1 i=1
Thread-1 i=2
Thread-1 i=3
Thread-1 i=4
Thread-1 i=5
Thread-1 i=6
Thread-1 i=7
Thread-0 i=2
Thread-1 i=8
Thread-0 i=3
Thread-0 i=4
//...
<div class="se-preview-section-delimiter"></div>
3.线程的生命周期
java中线程总共有五种状态,分别是新建
,就绪
,运行
,阻塞
,死亡
新建
:当程序中使用new
关键字创建线程之后,该线程就处于新建状态,jvm仅仅为它分配内存以及初始化变量.就绪
:当对线程对象调用start()
后,线程进入就绪状态,注意这里该线程并不是立即运行,何时运行取决于jvm线程调度器调度.运行
:jvm为处于就绪状态的线程分配到了cpu
资源,线程体开始执行,但是该线程并不会一直运行,而是会有一定的运行时间,因为调度器需要给其他线程cpu资源.阻塞
:比如对线程调用sleeep()
,调用了阻塞IO
等待该方法返回,请参照线程状态转换图.死亡
:当线程执行体run()
,call()
执行完,运行意见出现错误/异常,主动调用stop()
,都会使线程进入死亡状态.可以通过isAlive()
返回判断线程的状态情况,当线程处于就绪,运行,阻塞
时返回true
,处于新建,死亡
时返回false
@org.junit.Test
public void testDemo5() throws InterruptedException {
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+" i="+i);
if(i==5){
Thread thread=new Thread(new ExtendThread());//新建状态
thread.start();//就绪状态 ,在之后一定时间内处于运行状态
thread.sleep(100);//阻塞状态,执行完run()方法会进入死亡状态
System.out.println(thread.isAlive());
}
}
}
<div class="se-preview-section-delimiter"></div>
4.控制线程
join
:在某个程序执行流中调用其他线程的join()
方法,调用线程将被阻塞,直到被join
的线程执行完毕.
@org.junit.Test
public void testDemo5() throws InterruptedException {
new Thread(new ExtendThread(),"thread-normal").start();
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+" i="+i);
if(i==5){
Thread thread=new Thread(new ExtendThread(),"join-thread");
thread.start();
thread.join();
}
}
}
<div class="se-preview-section-delimiter"></div>
- 执行结果:
main i=0
main i=1
main i=2
main i=3
main i=4
thread-normal i=0
thread-normal i=1
thread-normal i=2
thread-normal i=3
thread-normal i=4
main i=5
join-thread i=0
join-thread i=1
join-thread i=2
join-thread i=3
join-thread i=4
main i=6
main i=7
main i=8
main i=9
<div class="se-preview-section-delimiter"></div>
- 后台线程:我们常听说的gc线程就是一个后台线程,它有个特征是当所有的前台线程死亡的时候,后台线程会自动死亡,通过调用
setDaemon(true)
可设置线程为守护线程 - 线程睡眠:如果我们需要让当前线程暂停一段时间,可以通过调用
sleep()
让线程进入阻塞状态,在睡眠的时间段内,该线程不会获得cpu资源及不会得到执行的机会. - 线程让步:调用
yield()
会让线程处于就绪状态,这个方法会让jvm调度器重新调度一次,所以可能存在的情况是线程调用yield()
方法又立即被jvm调度出来重新出来运行.需要注意的是:某个线程调用yield()
后,只有线程优先级比该线程高或者相同的处于就绪状态的线程才会获得执行的机会.
- 这里需要区别下
sleep()
,yield()
: - (1)sleep()是暂停线程,一定会给其他线程机会而不管线程的优先级.
- (2)调用后的状态不一样,sleep()是阻塞,yield()是就绪.
- 改变线程的优先级:每个线程在执行的时候具有一定的优先级,线程默认的优先级和创建他的父线程优先级相同.可通过调用
setPriority(int newPriority),getPriotity()
来设置/查询线程的优先级,参数可以是一个整数,通常使用Thread
类的三个静态产量,MAX_PRIORITY,MIN_PRIORITY,NORM_PRIORITY
值分别为:10,1,5 . 不同的操作系统线程的优先级并不相同,所以为了程序具有更好的移植性应该尽量使用Thread
类提供的静态常量.
- 这里需要区别下
5.线程同步
- 同步代码块
synchronized(obj)
{
... //此处的代码块就是同步代码块
}
<div class="se-preview-section-delimiter"></div>
上面的obj称为同步监视器,通常推荐将会被并发访问的资源作为同步监视器,整个流程可以概括为加锁->修改->释放锁
.
- 同步方法
public synchronized void setxxx(){
...
}
<div class="se-preview-section-delimiter"></div>
同步方法的监视器是this
,this
指的是调用该方法当前对象的引用
- 释放同步监视器的锁定
任何线程在进入同步代码块/同步方法之前都会获得对监视器对象的锁定,那么哪些情况下会释放对同步监视器的锁定呢?有以下几种情况:
- 同步代码块/同步方法执行完毕
- 同步代码块/同步方法中遇到break,return终止程序的执行
- 同步代码块/同步方法执行过程中遇到遇到未处理的error
或Exception
- 执行监视器对象的wait()
方法.
那么哪些情况对线程的操作不会释放锁呢?
- 调用sleep()
,yield()
暂停当前线程,不会释放锁
- 调用suspend()
挂起线程不会释放锁,但是应该尽量避免使用suspend()``resume()
等方法
- 同步锁
从jdk5
开始java开始提供同步锁机制,锁由Lock
来充当.
public class LockDemo {
private final Lock lock = new ReentrantLock();
public void currentMethod() {
lock.lock();
try {
//发生并发的执行代码
} finally {
lock.unlock();
}
}
}