共计 13503 个字符,预计需要花费 34 分钟才能阅读完成。
线程基础
1. 线程的生命周期
1.1 新建状态:
- 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
1.2 就绪状态:
- 当线程对象调用了 start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待 JVM 里线程调度器的调度。
1.3 运行状态:
- 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
1.4 阻塞状态:
-
如果一个线程执行了 sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当 sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
1.5 死亡状态:
- 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
2. 线程的优先级和守护线程
2.1 线程的优先级
- 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
- Java 线程的优先级是一个整数,其取值范围是
1(Thread.MIN_PRIORITY)- 10(Thread.MAX_PRIORITY)
。 - 默认情况下,每一个线程都会分配一个优先级
NORM_PRIORITY(5)
。
2.2 守护线程
- Java 中有两种线程:用户线程和守护线程。可以通过 isDeamon()方法来区别它们:如果返回 false,则说明该线程是“用户线程”;否则就是“守护线程”。
- 用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。
- 需要注意的是:JVM 在“用户线程”都结束后会退出。
3. 创建线程
3.1 通过实现 Runnable 接口
-
步骤:
- 创建类实现 Runnable 接口
- 实现 run() 方法, 线程实际运行的方法
- 实现 start() 方法,里面实例化线程对象(new Thread(this, threadName)), 调用线程对象的 start() 方法
-
代码实现
package com.ljw.thread; public class RunnableDemo {public static void main(String[] args) { // 测试 RunnableDemo R = new RunnableDemo(); RunnableThread R1 = R.new RunnableThread("thread1"); R1.start(); RunnableThread R2 = R.new RunnableThread("thread2"); R2.start();} class RunnableThread implements Runnable{ private String threadName; private Thread t; public RunnableThread(String name) { // TODO Auto-generated constructor stub threadName = name; System.out.println("创建线程"+threadName); } @Override public void run() {System.out.println("正在运行线程:"+threadName); try {for(int i=10;i>0;i--) {System.out.println("线程:"+threadName+"正在打印:"+i); Thread.sleep(50); } }catch(Exception e) {e.printStackTrace(); } System.out.println("线程:"+threadName+"正在退出......"); } public void start() {System.out.println("开始线程"+threadName); if(t == null) {t = new Thread(this, threadName); t.start();} } } }
3.2 通过继承 Thread 类本身
-
步骤:
- 创建类继承 Thread 类
- 下面与用 Runnable 接口一样
3.3 通过 Callable 和 Future 创建线程
-
步骤:
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
- 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
- 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
- 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
-
Callable 接口与 Runnable 接口的区别:
- Callable 中 call 方法可以有返回值,而 Runnable 中的 run 方法没有返回值
-
代码实现
package com.ljw.thread; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class CallableThreadTest implements Callable<Integer> {public static void main(String[] args) {CallableThreadTest ctt = new CallableThreadTest(); FutureTask<Integer> ft = new FutureTask<>(ctt); for(int i = 0;i < 10;i++) {System.out.println(Thread.currentThread().getName()+"的循环变量 i 的值"+i); if(i%2==0) {new Thread(ft,"有返回值的线程").start();} } try {System.out.println("子线程的返回值:"+ft.get()); } catch (InterruptedException e) {e.printStackTrace(); } catch (Exception e) {e.printStackTrace(); } } @Override public Integer call() throws Exception { int i = 0; for(;i<10;i++) {System.out.println(Thread.currentThread().getName()+" "+i); } return i; } }
4. synchronized 关键字
4.1 概述
- synchronized 关键字是为了解决共享资源竞争的问题,共享资源一般是以对象形式存在的内存片段,但也可以是文件、输入 / 输出端口,或者是打印机。
- 要控制对共享资源的访问,得先把它包装进一个对象。然后把所有要访问的这个资源的方法标记为 synchronized。
- 如果某个任务处于一个对标记为 synchronized 的方法的调用中,那么在这个线程从该方法返回之前,其他所有要调用类中任何标记为 synchronized 方法的线程都会被阻塞。
4.2 基本原则
- 第一条:当一个线程访问某对象的
synchronized 方法
或者synchronized 代码块
时,其他线程对该对象的该synchronized 方法
或者synchronized 代码块
的访问将被阻塞。 - 第二条:当一个线程访问某对象的
synchronized 方法
或者synchronized 代码块
时,其他线程仍然可以访问该对象的非同步代码块。 - 第三条:当一个线程访问某对象的
synchronized 方法
或者synchronized 代码块
时,其他线程对该对象的其他的synchronized 方法
或者synchronized 代码块
的访问将被阻塞。
4.3 实例
-
两个相似的例子
- 实例 1:实现接口 Runnable
package com.ljw.thread; public class RunnableTest {public static void main(String[] args) { class MyRunnable implements Runnable{ @Override public void run() {synchronized (this) {for(int i=0;i<5;i++) { try {Thread.sleep(100); } catch (InterruptedException e) {e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在进行打印" +i); } } } } Runnable runnable = new MyRunnable(); Thread t1 = new Thread(runnable,"t1"); Thread t2 = new Thread(runnable,"t2"); t1.start(); t2.start();} }
运行结果:```
t1 正在进行打印 0
t1 正在进行打印 1
t1 正在进行打印 2
t1 正在进行打印 3
t1 正在进行打印 4
t2 正在进行打印 0
t2 正在进行打印 1
t2 正在进行打印 2
t2 正在进行打印 3
t2 正在进行打印 4
```
结果说明:run()方法中存在 synchronized(this)代码块, 而且 t1 和 t2 都是基于 MyRunnable 这个 Runnable 对象创建的线程。这就意味着,我们可以将 synchronized(this)中的 this 看做是 MyRunnable 这个 Runnable 对象;因此,线程 t1 和 t2 共享“MyRunable 对象的同步锁”。所以,当一个线程运行的时候,另外一个线程必须等待正在运行的线程释放 MyRunnable 的同步锁之后才能运行。- 实例 2:继承 Thread 类
```java
public class ThreadTest {public static void main(String[] args) {
class MyThread extends Thread{public MyThread(String name){super(name);
}
@Override
public void run() {synchronized(this){for(int i=0;i<10;i++){
try {Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"正在进行打印"+i);
} catch (InterruptedException e) {e.printStackTrace();
}
}
}
}
}
Thread t1 = new MyThread("t1");
Thread t2 = new MyThread("t2");
t1.start();
t2.start();}
}
```
运行结果:```
t2 正在进行打印 0
t1 正在进行打印 0
t2 正在进行打印 1
t1 正在进行打印 1
t1 正在进行打印 2
t2 正在进行打印 2
t2 正在进行打印 3
t1 正在进行打印 3
t1 正在进行打印 4
t2 正在进行打印 4
```
对比结果:发现实例 1 的两个线程是一个结束后,另一个才运行,实例 2 的是交叉运行,在 run()方法中都有 synchronized(this),为什么结果不一样?分析:**synchronized(this)** 中的 **this** 是指 ** 当前对象 **, 即 synchronized(this)所在类对应的当前对象。它的作用是获取获取当前对象的同步锁。对于 ** 实例 2 ** 中的 synchronized(this)中的 **this** 代表的是 **MyThread 对象 **,t1 和 t2 是两个 ** 不同的 MyThread 对象 **,因此 t1 和 t2 在执行 synchronized(this)时获取的是不同对象的同步锁。对于 ** 实例 1 ** 来说,synchronized(this)中的 **this** 代表的时候 **MyRunnable 对象 **,**t1** 和 **t2** 是 ** 共同 ** 一个 **MyRunnable 对象 **,因此,一个线程获取了对象的同步锁,会造成另一个线程的等待。
4.4 synchronized 方法和 synchronized 代码块
4.4.1 概述
-
synchronized 方法
是用 synchronized 修饰方法,这是一种粗粒度锁;这个同步方法(非 static 方法)无需显式指定同步监视器,同步方法的同步监视器是 this,也就是调用该方法的对象。 -
synchronized 代码块
是用 synchronized 修饰代码块,这是一种细粒度锁。线程开始执行同步代码块之前,必须先获得对同步监视器的锁定,任何时候只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对同步监视器的锁定。虽然 Java 允许使用任何对象作为同步监视器,但同步监视器的目的就是为了阻止两个线程对同一个共享资源进行并发访问,因此通常推荐使用可能被并发访问的共享资源充当同步监视器。
4.4.2 实例
public class SnchronizedTest {public static void main(String[] args) {
class Demo {
// synchronized 方法
public synchronized void synMethod() {for(int i=0; i<1000000; i++)
;
}
public void synBlock() {
// synchronized 代码块
synchronized(this) {for(int i=0; i<1000000; i++)
;
}
}
}
}
}
4.5 实例锁和全局锁
4.5.1 概述
- 实例锁:锁在某个实例对象上。如果该类是单例,那么该锁也是具有全局锁的概念。实例锁对应的就是 synchronized 关键字。
- 全局锁:该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是 static synchronized(或者是锁在该类的 class 或者 classloader 对象上)。
4.5.2 实例
pulbic class Something {public synchronized void isSyncA(){}
public synchronized void isSyncB(){}
public static synchronized void cSyncA(){}
public static synchronized void cSyncB(){}
}
假设,类 Something 有两个实例(对象)分别为 x 和 y。分析下面 4 组表达式获取锁的情况。
-
x.isSyncA()与 x.isSyncB()
- 不能同时访问,因为都是访问对象 x 的同步锁
-
x.isSyncA()与 y.isSyncA()
- 可以同时访问,因为是访问不同对象 (x 和 y) 的同步锁
-
x.cSyncA()与 y.cSyncB()
- 不能同时访问,因为两个方法是静态的,相当于用 Something.cSyncA()和 Something.cSyncB()访问, 是相同的对象
-
x.isSyncA()与 Something.cSyncA()
- 可以同时访问,因为访问不同对象
5. Volatile 关键字
5.1 Volatile 原理
- Java 语言提供了一种稍微同步机制,即 volatile 变量,用来确保将变量的更新操作通知其他线程
- 在访问 volatile 变量是不会执行加锁操作,因此也就不会重新执行线程阻塞,volatile 变量是一种比 synchronized 关键字轻量级的同步机制
- 当一个变量被 volatile 修饰后,不但具有可见性,而且还禁止指令重排。volatile 的读性能消耗与普通变量几乎相同,但是写操作就慢一些,因为它要保证本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行
6. 线程等待和唤醒
6.1 常用方法
- 在 Object.java 中,定义了 wait(),notify()和 notifyAll()等接口
- wait()方法的作用是让当前线程进入阻塞状态,同时会释放当前对象所持有的锁
- notify()唤醒当前对象上的等待线程,notifyAll()则是唤醒所有的线程
6.2 实例
package com.ljw.thread;
public class WaitDemo {public static void main(String[] args) {
class ThreadTest extends Thread{
@Override
public void run() {synchronized (this) {System.out.println("开始运行线程"+Thread.currentThread().getName());
System.out.println("唤醒线程 notify()");
notify();}
}
}
ThreadTest thread1 = new ThreadTest();
thread1.start();
synchronized (thread1) {
try {System.out.println("主线程进入阻塞,释放 thread 对象的同步锁,wait()");
thread1.wait(); // wait()是让当前线程进入阻塞状态,wait()是在主线程中执行,} catch (InterruptedException e) {e.printStackTrace();
}
}
System.out.println("主线程继续进行");
}
}
7. 线程让步和休眠
7.1 线程让步
7.1.1 概述
- 在 Java 线程中,yield()方法的作用是让步,它能让当前线程由“运行状态”进入到“就绪状态”,可能让其它同级别的线程获得执行权,但不一定,可能它自己再次由“就绪状态”进入到“运行状态”
7.1.2 实例
package com.ljw.thread;
public class YieldTest {public static void main(String[] args) {
class ThreadA extends Thread{public ThreadA(String name){super(name);
}
@Override
public synchronized void run() {for(int i=0;i<5;i++){System.out.println(""+this.getName()+" "+i);
if(i%2 == 0){Thread.yield();
}
}
}
}
ThreadA t1 = new ThreadA("t1");
ThreadA t2 = new ThreadA("t2");
t1.start();
t2.start();}
}
运行结果(不唯一):
t1 0
t2 0
t1 1
t1 2
t2 1
t1 3
t2 2
t1 4
t2 3
t2 4
结果说明:
线程 t1 在能被 2 整除的时候,并不一定切换到线程 2。这表明,yield()方法虽然可以让线程由“运行状态”进入到“就绪状态”;但是,它不一定会让其他线程获取 CPU 执行权(其他线程进入到“运行状态”)。即时这个“其他线程”与当前调用 yield()的线程具有相同的优先级。
7.1.3 yield()和 wait()比较
- wait()的作用是让当前线程由“运行状态”进入“阻塞状态”,而 yield()是让当前线程由“运行状态”进入“就绪状态”
- wait()是会让线程释放它所持有的对象的同步锁,而 yield()方法不会释放对象的同步锁。
7.2 线程休眠
7.2.1 概述
- sleep()方法定义在 Thread 类中,sleep()的作用是让当前线程休眠,即当前线程会从“远程状态”进入到“休眠(阻塞)状态”
- sleep()会指定休眠时间,线程休眠的时间会大于 / 等于该休眠时间
- 在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待 CPU 的调度执行。
7.2.2 sleep() 和 wait()的比较
- wait()的作用是让当前的线程由“运行状态”进入到“等待(阻塞)状态”的同时,也会释放同步锁 - 但是 sleep()的作用是让当前线程由“运行状态”进入到“休眠(阻塞)”状态,但不会释放锁。
8. 加入一个线程
8.1 概述
- 在一个线程 T 上调用另一个线程 t 的 join() 方法,相当于在 T 中加入线程 t,要等 t 结束后(即 t.isAlive 为假),join() 后面的代码块才会执行。
- 可以在调用 jion()时带上一个超时参数(单位可以是毫秒,或者纳秒),这样如果目标线程在这段时间到期时还没有结束的话,join()方法总能返回
9. 终止一个线程
9.1 概述
- interrupt()并不会终止处于“运行状态”的线程,它会将线程的中断标记设为 true。
- 综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式如下:
@Override
public void run() {
try {// 1. isInterrupted()保证,只要中断标记为 true 就终止线程。while (!isInterrupted()) {// 执行任务...}
} catch (InterruptedException ie) {// 2. InterruptedException 异常保证,当 InterruptedException 异常产生时,线程被终止。}
}
9.2 实例
public class InterruptBlock {
/**
* @param args
*/
public static void main(String[] args) {
class MyThread extends Thread{public MyThread(String name){super(name);
}
@Override
public void run() {
try {
int i=0;
while(!isInterrupted()){Thread.sleep(100);
i++;
System.out.println(Thread.currentThread().getName()+ "("+this.getState()+") loop"+i);
}
} catch (InterruptedException e) {e.printStackTrace();
System.out.println(Thread.currentThread().getName()+ "("+this.getState()+") catch InterruptedExecption");
}
}
}
try {
// 新建
Thread t1 = new MyThread("t1");
System.out.println(t1.getName()+"("+t1.getState()+") is new.");
System.out.println("luo1:"+t1.isInterrupted());
// 启动
t1.start();
System.out.println(t1.getName()+"("+t1.getState()+") is started.");
System.out.println("luo2:"+t1.isInterrupted());
// 主线程休眠 300ms,然后主线程给 t1 发“中断”指令
Thread.sleep(300);
t1.interrupt();
System.out.println("luo3:"+t1.isInterrupted());
System.out.println(t1.getName()+"("+t1.getState()+") is interrupted.");
// 主线程休眠 300ms,然后查看 t1 的状态
Thread.sleep(300);
System.out.println(t1.getName()+"("+t1.getState()+") is interrupted now .");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();}
}
}
运行结果:
t1 (NEW) is new.
luo1:false
t1 (RUNNABLE) is started.
luo2:false
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
luo3:true
t1 (RUNNABLE) loop 3
t1 (RUNNABLE) is interrupted.
t1 (TERMINATED) is interrupted now .
9.3 interrupt()和 isInterrupted()的区别
- interrupt()和 isInterrupted()都能够用于检测对象的“中断标记”。区别是:interrupt()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为 false);而 isInterrupted()仅仅返回中断标记。
线程进阶
1. 线程池
- 示例
package com.ljw.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {public static void main(String[] args) { // 主线程
// 线程池 ---> Executors 工具类(工厂类)/*
* newFixedThreadPool(int threadCount) 创建固定数量的线程池
* newCachedThreadPool() 创建动态数量的线程池
*/
ExecutorService es = Executors.newFixedThreadPool(3);
Runnable task = new MyTask();
// 提交任务
es.submit(task);
es.submit(task);
es.shutdown(); // 关闭线程池,则表示不在接收新任务,不代表正在线程池的任务会停掉}
}
class MyTask implements Runnable{
@Override
public void run() {for(int i=0;i<100;i++) {System.out.println(Thread.currentThread().getName()+"MyTask"+i);
}
}
}
2. 线程安全与锁
2.1 重入锁和读写锁
package com.ljw.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
/**
* ReentrantLock 类,重入锁:Lock 接口的实现类,与 synchronized 一样具有互斥锁功能 lock() 和 unlock()
* ReentrantReadWriteLock 类,读写锁:一种支持一写多读的同步锁,读写分离,分别分配读锁和写锁,在读操作远远高于写操作的环境中可以提高效率
* 互斥规则:* 写 -- 写:互斥,阻塞
* 读 -- 写:互斥,阻塞
* 读 -- 读:不互斥,不阻塞
*
*/
public class LockDemo {public static void main(String[] args) {ExecutorService es = Executors.newFixedThreadPool(20);
Student s = new Student();
// ReentrantLock rLock = new ReentrantLock(); // 用 ReenTrantLock 加锁运行时间 20008ms
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); // 用读写锁分别对读写任务加锁运行时间 3003ms
ReadLock rl = rwLock.readLock();
WriteLock wl = rwLock.writeLock();
// 写任务
Callable<Object> writeTask = new Callable<Object>() {
@Override
public Object call() throws Exception {// rLock.lock();
wl.lock();
try {Thread.sleep(1000);
s.setValue(100);
}finally {// rLock.unlock();
wl.unlock();}
return null;
}};
// 读任务
Callable<Object> readTask = new Callable<Object>() {
@Override
public Object call() throws Exception {// rLock.lock();
rl.lock();
try {Thread.sleep(1000);
s.getValue();}finally {// rLock.unlock();
rl.unlock();}
return null;
}};
// 开始时间
long start = System.currentTimeMillis();
for(int i=0;i<2;i++) { // 写任务执行 2 次
es.submit(writeTask);
}
for(int i=0;i<18;i++) { // 读任务执行 18 次
es.submit(readTask);
}
es.shutdown(); // 停止线程池,不在接受新的任务,将现有任务全部执行完毕
while(true) {if(es.isTerminated()) { // 当线程池中所有任务执行完毕,返回 true,否则返回 false
break;
}
}
// 执行到这里,说明线程池中所有任务都执行完毕,可以计算结束时间
System.out.println(System.currentTimeMillis()-start);
}
}
class Student {
private int value;
// 读
public int getValue() {return value;}
// 写
public void setValue(int value) {this.value = value;}
}
2.2 线程安全
2.2.1 Collections 工具类
- Collections 工具类中提供了多个可以获得线程安全集合的方法
static <T> Collection<T> synchronizedCollection(Collection<T> c)
// 返回由指定集合支持的同步(线程安全)集合。static <T> List<T> synchronizedList(List<T> list)
// 返回由指定列表支持的同步(线程安全)列表。static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
// 返回由指定地图支持的同步(线程安全)映射。static <K,V> NavigableMap<K,V> synchronizedNavigableMap(NavigableMap<K,V> m)
// 返回由指定的可导航地图支持的同步(线程安全)可导航地图。static <T> NavigableSet<T> synchronizedNavigableSet(NavigableSet<T> s)
// 返回由指定的可导航集支持的同步(线程安全)可导航集。static <T> Set<T> synchronizedSet(Set<T> s)
// 返回由指定集合支持的同步(线程安全)集。static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
// 返回由指定的排序映射支持的同步(线程安全)排序映射。static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
// 返回由指定的排序集支持的同步(线程安全)排序集。
2.2.2 CopyOnWriteArrayList
- 线程安全的 ArrayList
- 读写分离,写加锁,读没锁,读写之间不互斥
- 使用方法与 ArrayList 无异
2.2.3 CopyOnWriteArraySet
- 基于 CopyOnWriteArrayList
2.2.4 ConcurrentHashMap
- 初始容量默认为 16 段(Segment),采用分段锁设计
- 不对整个 Map 加锁,只对每个 Segment 加锁
- 当多个对象访问同个 Segment 才会互斥
正文完
发表至: java
2019-11-09