目录
基本概念
进程是操作系统中进行保护和资源分配的基本单位,操作系统分配资源以进程为基本单位。而线程是进程的组成部分,它代表了 一条顺序的执行流。
线程(Thread) 进程可以进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行的执行多个线程,就是支持多线程的,线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程的切换开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间->他们从同一堆中分配对象,可是访问相同的变量和对象,这就使得线程建通信更简便、高效,但是多个线程操作共享的资源可能会带来安全隐患。
可以共享的内容可以参照JVM内存模型 如下图中的 虚拟机栈和程序计数器就是每一个线程独有的
方法区和堆是一个进程一份,进程中的所有线程共享方法区和堆中的内容
1 Class Loader 类加载器
2 Execution Engine 执行引擎负责解释命令,提交操作系统执行
3 Native Interface 本地接口
4.Runtime Data Area 运行数据区
线程的创建和使用
1、通过继承Thread类的方式创建线程
package test;
/**
* 多线程的方式一:继承于Thread类
* 1、创建一个继承于Thread类
* 2、重写run方法 --> 将此线程执行的操作声明在run()中
* 3、创建Thread类的子类对象
* 4、通过此对象调用 start方法
*
* @author zjt
*/
// 1、创建一个继承于Thread类
class MyThread extends Thread {
@Override
// 2、重写run方法
public void run() {
for (int i = 0; i < 10000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// 3、创建Thread类的子类对象 主线程帮我们构造对象
MyThread t1 = new MyThread();
// 4、通过此对象调用 start方法 t1独立去执行
// t1.start()两个作用 1、启动当前线程 2、调用当前线程的run() 方法
t1.start();
// 下面的操作仍然在主线程中执行
for (int i = 0; i < 1000; i++) {
System.out.println("hello");
}
}
}
如果在main方法中不调用start() 方法直接调用run方法是没有启动新的线程去执行的
package test;
/**
*
* @author zjt
*/
// 1、创建一个继承于Thread类
class MyThread extends Thread {
@Override
// 2、重写run方法
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
// 获取当前线程的名称
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// 3、创建Thread类的子类对象 主线程帮我们构造对象
MyThread t1 = new MyThread();
// 4、通过此对象调用 start方法 t1独立去执行
// t1.start();
// 调用run方法
t1.run();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + "hello");
}
}
}
idea 也会有有提示
若不需要重复调用仅仅使用一次,也可以使用匿名内部类调用Thread
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
// 获取当前线程的名称
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}).start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + "hello");
}
}
Thread中常用的方法简单介绍
1、start() 启动当前线程,调用当前线程的run()方法
2、run() 通常情况下需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3、currentThread() 返回执行当前代码的线程
4、getName() 获取当前线程的名字
5、setName() 设置当前线程的名字
6、yield() 释放当前CPU的执行权,不代表不再抢占CPU,所以可能还是会抢到资源继续执行
7、join() 在线程A的执行过程中调用线程B 的jion() ,此时线程A进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞状态继续执行
8、stop() 已过时,执行此方法强制结束当前线程
9、sleep() 让当前线程进入“睡眠”状态,在指定的milltime毫秒时间内,当前线程是阻塞状态
10、isAlive() 判断当前线程是否存活
线程的调度
调度策略
时间片
抢占式:高优先级的线程抢占CPU
Java的调度方法
同优先级的线程组成先进先出队列,使用时间片策略(先到先得)
对于高优先级,使用优先调度的抢占式策略
线程的优先级
MAX_PRIORITY: 10 最大优先级
MIN_PRIORITY :1 最小优先级
NORM_PRIORITY: 5 默认优先级
涉及到的方法
// 返回线程的优先级值
int priority = thread.getPriority();
// 设置线程的优先级
thread.setPriority(5);
// 注意设置值 需要在 MIN_PRIORITY 到 MAX_PRIORITY 否则会抛出错误 设值的源码如下
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
线程创建时,继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
2、方式二 实现 Runnable 接口创建线程
Java线程的分类
Java的线程分为两类,一类是守护线程,一类是用户线程
他们在每个方面几乎都是相同的,唯一的区别是判断何时离开
守护线程是用来服务用户线程的,通过start() 方法前调用,thread.setDaemon(true) 可以把一个用户线程变成一个守护线程
Java垃圾回收就是一个典型的守护线程
若JVM中都是守护线程,当前JVM会退出
线程的生命周期
JDK中使用Thread.State类定义了线程的几种状态
要想实现多线程必须在主线程中创建新的线程对象,Java语言使用Thread类及其子类对象来表示线程,在它的一个完整的生命周期中,通常要经历如下的五种状态
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已经具备了运行的条件,只是还没有分配到 CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊的情况下,被人为挂起获取执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作,或者线程被提前强制的中止,或者出现异常导致结束
线程的同步
class MyRunnable implements Runnable {
// 共享数据
private int ticket = 100;
// 2、实现的类去重写Runnable接口中抽象方法
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
// 强制线程 停止 10 微秒
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票:票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
安全问题如上代码的卖票会有重复或者其他异常
问题出现的原因:当某个线程操作 ticket 的过程中,尚未操作完成时,其他线程进入方法 操作 ticket-- 导致
解决方式思路:当一个线程A在操作ticket的时候,其他线程不能参与进来,直到当前线程A操作完成后,其他线程才可以开始操作ticket ,即使线程A出现阻塞,其他线程也需要等待
在Java中通过同步机制来解决线程安全问题
方式一 :同步代码块
synchronized(同步监视器){
// 操作共享数据的代码,即为需要被同步的代码
// 共享数据,多个线程共同操作的变量,比如上述代码中的 ticket
// 同步监视器 :也就是锁 任何一个类的对象都可以成为锁 要求 多个线程必须共用同一把锁
}
class MyRunnable implements Runnable {
private int ticket = 100;
// 共用此对象 但是还是比较麻烦 可以使用当前对象this
private final Object obj = new Object();
@Override
public void run() {
while (true) { //注意不要把它包含进去,包含进去后 只有一个线程操作 ticket
// synchronized (this) { // 可以考虑唯一的当前对象 注意此方法在 继承thread类实现的时候不可用
//synchronized (MyRunnable.class) { // 当前类的对象也是唯一的 继承thrad类实现多线程 可以使用 当前类作为同步监视器
synchronized (obj) {
if (ticket > 0) {
try {
// 强制线程 停止 10 微妙
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票:票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
同步的方式,解决了线程安全的问题
操作同步代码时,只能有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,可以将此方法声明同步的
package test;
/**
* 使用同步方法 解决实现runnable接口的线程安全问题
*
* @author zjt
*/
class MyRunnableT implements Runnable {
private int ticket = 100;
private boolean flag = true;
@Override
public synchronized void run() {
while (flag) {
this.sale();
}
}
private synchronized void sale() { // 同步方法当中 同步监视器 是this
if (ticket > 0) {
try {
// 强制线程 停止 10 微妙
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票:票号为:" + ticket);
ticket--;
} else {
this.flag = false;
}
}
}
public class ThreadTest3 {
public static void main(String[] args) {
// 3、创建实现类的对象
MyRunnableT myRunnable = new MyRunnableT();
// 4、将此对象作为参数传递到Thread类的构造器中,创建Thead类的对象
Thread thread = new Thread(myRunnable);
// 5、通过Thread类的对象去调用start() 方法
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
thread.setName("窗口1");
thread1.setName("窗口2");
thread2.setName("窗口3");
thread.start();
thread1.start();
thread2.start();
}
}
package test;
/**
* 使用同步方法处理继承 Thread类的方式 中的线程安全问题
*
* @author zjt
*/
class MyThread1 extends Thread {
private static int ticket = 100;
private static boolean flag = true;
private static synchronized void sale() { // 同步监视器 MyThread1.class
//private synchronized void sale() { // 同步监视器 为m1,m2,m3 所以这种解觉方式是有误的 需要加上 static
if (ticket > 0) {
try {
// 强制线程 停止 10 微妙
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票:票号为:" + ticket);
ticket--;
} else {
flag = false;
}
}
@Overrid
public void run() {
while (flag) {
sale();
}
}
}
public class ThreadTest4 {
public static void main(String[] args) {
MyThread1 m1 = new MyThread1();
MyThread1 m2 = new MyThread1();
MyThread1 m3 = new MyThread1();
m1.setName("窗口1");
m2.setName("窗口2");
m3.setName("窗口3");
m1.start();
m2.start();
m3.start();
}
}
关于同步方法的总结
1、同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
2、非静态的同步方法 同步监视器是this 静态方法的同步监视器是 当前类本身
方式3使用Lock锁
package test1;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* 解决线程安全问题方式三 lock锁
*
* @author zjt
* @date 2020-08-24
*/
class Account {
// 实例化 ReentrantLock
private final static ReentrantLock lock = new ReentrantLock(true);
private double balance;
public Account(double balance) {
this.balance = balance;
}
public void deposit(double amt) {
try {
// 调用 锁定lock 方法
lock.lock();
if (amt > 0) {
balance += amt;
TimeUnit.MILLISECONDS.sleep(10);
System.out.println(Thread.currentThread().getName() + ":存钱成功,余额为:" + balance);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 手动调用解锁方法 否则线程获取锁一直阻塞
lock.unlock();
}
}
}
class Customer extends Thread {
private final Account account;
public Customer(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
public class TestAccount {
public static void main(String[] args) {
Account account = new Account(0);
Customer c1 = new Customer(account);
Customer c2 = new Customer(account);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
线程的死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有的进程都处于阻塞状态,无法继续。
解决思路,专门的算法、原则,尽量减少同步资源的定义,尽量避免嵌套同步
package test1;
/**
* 演示死锁的线程问题
*
* @author zjt
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer sb1 = new StringBuffer();
StringBuffer sb2 = new StringBuffer();
new Thread(() -> {
synchronized (sb1) {
sb1.append("a");
sb2.append("1");
try {
// 此时 该线程 持有 sb1 想获取 sb2 资源 导致死锁
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (sb2) {
sb1.append("b");
sb2.append("2");
}
}
System.out.println(sb1);
System.out.println(sb2);
}).start();
new Thread(() -> {
synchronized (sb2) {
sb1.append("c");
sb2.append("3");
try {
// 此时 该线程 持有 sb2 想获取 sb1 资源 导致死锁
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (sb1) {
sb1.append("d");
sb2.append("4");
}
}
System.out.println(sb1);
System.out.println(sb2);
}).start();
}
}
线程的通信
多个线程的操作共同的共享数据,可以看作它们之间有某种“交流”,可以看作是线程间的通信
package test3;
/**
* @author zjt
* 线程的通信
* 涉及到的3个方法
* wait() 一旦执行此方法 ,当前线程就会进入阻塞状态,并释放同步监视器
* notify() 一旦执行此方法 ,就会唤醒被 wait() 的一个线程 如果有多个线程被wait 就会唤醒优先级高的那个线程
* notifyAll() 一旦执行此方法 ,就会唤醒被 wait() 的所有线程
* 说明
* 1、使用前提,wait() notify() notifyAll() 必须使用在同步代码块,或者同步方法中
* 2、wait() notify() notifyAll() 调用没有加对象,默认是使用 this 调用 调用者必须是同步方法或者同步代码块中同步监视器 否则会抛出 IllegalMonitorStateException 异常
* 3、wait() notify() notifyAll() 是定义在 Object 当中 因为 同步监视器 是任何一个唯一的对象都可以调用 所以 放在了
*
* sleep() 和 wait() 方法的异同
* 相同点 一旦执行方法 都可以是的当前线程进去阻塞状态
* 不同点 1、两个方法声明的位置不同,Thread 类中声明 sleep() Object() 类中声明 wait()
* 2、调用的要求不同,sleep 的调用可以在任何需要的场景下调用 wait() 必须使用在同步代码块,或者同步方法中
* 3、关于是否释放同步监视器的问题 ,如果两个方法都使用在 同步代码块或者同步方法中,sleep() 不会释放同步监视器,wait() 会释放 同步监视器
*/
class Number implements Runnable {
private int num = 1;
private Object obj = new Object();
@Override
public void run() {
while (true) {
//synchronized (this) {
synchronized (obj) {
obj.notify();
//this.notify();
if (num <= 100) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
// 使得调用 wait(); 方法的线程进入阻塞状态 wait() 会释放锁
//this.wait();
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("甲");
t2.setName("乙");
t1.start();
t2.start();
}
}
JDK5.0新增线程的创建方式
新增方式一 :实现Callable接口
与Runnable相比 Callable功能更强大一些 1、与run() 方法相比 可以有返回值 2、方法可以抛出异常 3、支持泛型返回值4、需要借助于FutureTask类,比如获取返回值
package test3;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建方式三 实现Callable 接口创建线程
*
* 1、相比run()方法,可以有返回值
* 2、方法可以抛出异常
* 3、支持泛型的返回值
*
* @author zjt
*/
// 1、创建一个实现Callable<V> 的实现类
class NumThread implements Callable<Integer> {
@Override
//2、 实现 call() 方法,将此线程需要执行的操作声明在call() 方法当中
public Integer call() {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
sum += i;
}
}
return sum;
}
}
public class CallThread {
public static void main(String[] args) {
// 3、创建Callable<V> 接口的实现类的对象
NumThread t1 = new NumThread();
// 4、将此Callable<V> 接口的实现类的对象 作为参数 传递到FutureTask<V> 中,创建FutureTask<V> 的对象
FutureTask<Integer> futureTask = new FutureTask<>(t1);
// 将FutureTask<V> 的对象作为参数传递到 Thread 类的构造器中,创建 Thread对象 并且调用 start 方法
new Thread(futureTask).start();
try {
// 6、获取Callable<V> 方法中 call 方法的返回值
// .get 方法的返回值 即为 FutureTask 构造器参数的Callable 实现类重写的call 方法的返回值
Integer sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
新增方式二 :使用线程池
背景:经常创建和销毁、使用量特别大资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入池中,使用时直接获取,使用完成后放回池中,可以避免频繁的销毁、实现重复利用。
好处:1、提高响应速度(减少了创建新线程的时间) 2、降低资源消耗(重复利用线程池中的线程,不需要每次创建)3、便于线程管理比如可以设置 线程池核心池的大小(corePoolSize)、最大线程数(maximumPoolSize)、线程没有任务时最多保持多长时间后会终止(keepAliveTime)等等。
package test3;
import java.util.concurrent.*;
/**
* 创建线程的方式 四
*
* @author zjt
*/
public class ThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 提供指定数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//强制转换成子实现类 设置属性
//ThreadPoolExecutor executorService1 = (ThreadPoolExecutor) executorService;
// 设置 线程池属性 找接口实现类
// System.out.println(executorService.getClass());
//executorService1.setCorePoolSize(1);
// executorService1.setKeepAliveTime(10, TimeUnit.SECONDS);
// executorService.submit // 适合使用于 Callable
//executorService.execute(); // 适合使用于 Runnable
// 执行指定的线程的操作,需要提供 Callable 或者 Runnable 接口类实现类的对象
FutureTask<Integer> submit = (FutureTask<Integer>) executorService.submit(() -> {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
sum += i;
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
return sum;
});
executorService.execute(() -> {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
});
Integer q = submit.get();
System.out.println(q);
// 关闭线程连接池
executorService.shutdown();
}
}