Java —— 多线程
- 1. 线程与进程
- 1.1 线程生命周期
- 1.2 线程优先级
- 2. 多线程
- 2.1 守护线程
- 2.2 多线程高并发
- 2.3 synchronized同步锁
- 2.4 synchronized互斥锁
1. 线程与进程
进程(Process) 操作系统进行资源分配和调度的基本单位:系统中正在运行的程序实例,一个程序可以同时启动多个进程 线程(Thread) CPU调度的基本单位:操作系统能够进行运算调度的最小单位 |
进程:进程是指正在运行的程序的实例
- 特点:1. 每个进程都有独立的内存空间(程序代码、执行上下文、数据和资源)
2. 一个程序可以同时启动多个进程(每个进程之间相互独立,彼此不会直接共享内存)
3. 进程拥有自己的地址空间和系统资源(操作系统进行资源分配和调度的基本单位)
4. 进程之间通过进程间通信(IPC)机制来交换数据和信息线程:线程是进程中的执行单元
- 特点:1. 一个进程可以包含多个线程,它们可以共享进程的上下文和资源
2. 线程拥有自己的栈空间,但共享进程的代码段、数据段和堆空间
3. 线程之间的通信和同步比进程之间更加高效
4. 线程在执行过程中可以独立地完成一系列任务
5. 多个线程可以并发执行,提高程序的吞吐量和响应速度1. 进程之间通信开销较大,线程之间通信开销较小
2. 使用多线程可以充分利用多核处理器的性能
线程的3种创建方式
- 继承Thread类
- 实现Runnable接口
- 使用Callable和future创建
- 线程的创建(2种演示)
package thread;public class ThreadDemo {public static void main(String[] args) {// 实例化线程Thread t1 = new Thread1();Thread t2 = new Thread2();// 启动线程t1.start();t2.start();// 创建线程执行任务MyRunnable r1 = new MyRunnable();MyRunnables r2 = new MyRunnables();// 创建线程Thread t3 = new Thread(r1);Thread t4 = new Thread(r2);// 启动线程t3.start();t4.start();// ==============匿名内部类====================// 实例化(匿名内部类)Thread tl = new Thread(){@Overridepublic void run() {super.run();}};// 实例化(lambda表达式)Runnable rl = () -> System.out.println("lambda表达式");// 创建线程Thread t = new Thread(rl);// 启动线程tl.start();t.start();}
}// ====================第一种创建线程的方法==================
// 线程1
class Thread1 extends Thread {public void run() {for (int i = 0; i < 10000; i++) {System.out.println("你好");}}
}// 线程2
class Thread2 extends Thread {public void run() {for (int i = 0; i < 10000; i++) {System.out.println("hello world");}}
}// ====================第二种创建线程的方法==================
// 实现runnable接口
class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println("你好");}}
}class MyRunnables implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println("hello world");}}
}
1.1 线程生命周期
名称 | 状态 | 说明 |
---|---|---|
新建(New) | 当线程对象被创建但还未开始执行时,处于新建状态 | 此时线程没有被分配CPU资源 |
就绪(Runnable) | 在调用线程的start()方法后,线程进入就绪状态 | 此时线程已经被分配CPU资源,但还未开始执行,等待调度器进行调度 |
运行(Running) | 就绪状态的线程被调度器选中后,线程进入运行状态 | 开始执行run()方法中的任务 |
阻塞(Blocked) | 线程在运行过程中,可能因为某些原因而暂停执行,称为阻塞状态 | 常见的情况包括等待IO操作、等待获取锁、等待其他线程的通知等(释放CPU资源) |
等待(Waiting) | 线程执行wait()方法或类似方法后,线程进入等待状态 | 此时线程会释放CPU资源并等待其他线程的通知,直到被唤醒 |
超时等待(Timed Waiting) | 线程执行sleep()、join()、LockSupport.parkNanos()等方法时,线程进入超时等待状态 | 与等待状态类似,线程也会释放CPU资源,但在一定时间后会自动唤醒 |
结束(Terminated) | 线程执行完run()方法中的任务或者出现异常导致线程终止时,线程进入结束状态 | 已经结束的线程不能再重新启动 |
1.2 线程优先级
优先级的设置和获取:
Thread
类中的setPriority()
和getPriority()
方法
优先级范围:1到10(1为最低优先级,10为最高优先级),默认情况下,线程的优先级与创建它的父线程的优先级相同
使用
setPriority()
方法设置线程的优先级:
Thread thread = new Thread();thread.setPriority(Thread.MAX_PRIORITY); // 设置线程的优先级为最高优先级
thread.setPriority(Thread.MIN_PRIORITY);
thread.setPriority(Thread.NORM_PRIORITY); // 默认优先级
使用
getPriority()
方法获取线程的优先级:
int priority = thread.getPriority(); // 获取线程的优先级
注意:
1. 线程优先级仅提供一个指示给调度器的建议(不是高优先级的线程一定会先执行)
2. 具体的线程调度由操作系统和JVM决定(可能会受到系统负载、线程状态等因素的影)
- 线程优先级
package thread;public class PriorityThread {public static void main(String[] args) {// 最大优先级Thread max = new Thread(){@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("max");}}};// 最小优先级Thread min = new Thread(){@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("min");}}};// 默认优先级Thread norm = new Thread(){@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("nor");}}};// 设置优先级max.setPriority(Thread.MAX_PRIORITY);min.setPriority(Thread.MIN_PRIORITY);max.start();norm.start();min.start();}
}
start()和run()方法
start()方法:
start()方法是Thread类中的一个方法,用于启动一个新的线程并执行其代码
调用start()方法会创建一个新的线程,并在新线程中调用run()方法
在新线程中执行的代码由run()方法定义
run()方法:
run()方法也是Thread类中的一个方法,通常用于定义线程的任务或逻辑
直接调用run()方法不会创建一个新的线程,而是在当前线程中以普通方法的方式执行run()方法的代码
区别
start()方法会创建一个新的线程来执行任务,而run()方法在当前线程中执行
调用start()方法后,线程会被添加到线程调度器的线程队列中,由线程调度器决定何时开始执行,而直接调用run()方法会立即开始执行
使用start()方法可以充分利用多核处理器的并行能力,而直接调用run()方法则只能在单个线程中顺序执行
2. 多线程
线程控制
sleep( )
: 线程休眠
join( )
: 线程加入
yield( )
: 线程礼让
setDaemon( )
: 线程守护
中断线程
stop( )
: 线程停止
interrupt( )
:线程中断 (首先选用)
2.1 守护线程
守护线程 :不会阻止程序的终止,当所有非守护线程被终止时,守护线程也会被自动终止 临界资源:临界资源(Critical Resource)是指在多线程环境下被多个线程访问和操作的共享资源 常见临界资源 多线程共享实例变量 多线程共享静态公共变量 |
2.2 多线程高并发
高并发问题 :当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致执行顺序出现混乱产生不良后果 临界资源: 操作该资源的完整过程同一时刻只能被单个线程进行(被多个线程共享的资源) 常见临界资源 多线程共享实例变量 多线程共享静态公共变量 每个线程中对全局变量量、静态变量量只有读操作,⽽而⽆无写操作,一般来说,这个全局变量是线程安全的; 若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全 |
- 取款问题 利用多线程的并发间隙,在柜台和ATM机同时取款,寻找取款金额大于实际余额的情况
package thread;public class Bank {private int account = 20000;public synchronized boolean getMoney(int money) {int account = getAccount();if (account >= money) {account -= money;Thread.yield();saveAccount(account);return true;}return false;}public void saveAccount(int account) {this.account = account;}public int getAccount() {return account;}
}
package thread;public class BankTest {static boolean s1;static boolean s2;static int sum = 0;// 测试public static void main(String[] args) {Bank bank = new Bank();while (true) {Thread t1 = new Thread() {@Overridepublic void run() {s1 = bank.getMoney(20000);}};Thread t2 = new Thread() {@Overridepublic void run() {s1 = bank.getMoney(20000);}};t1.start();t2.start();try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}if (s1 && s2) {System.out.println("产生了漏洞,尝试了:" + sum + "次");break;} else {sum++;bank.saveAccount(20000);s1 = false;s2 = false;}}}
}
2.3 synchronized同步锁
- 解决线程安全 —— 将异步操作转为同步操作
- 异步操作:多线程并发,线程的执行互不干扰
- 同步操作:线程有先后执行顺序,线程执行受到限制,排队等待执行
使用
synchronized
关键字实现线程之间的同步,同步锁
- 使用语法:
synchronized(同步监视器对象(必须是引用数据类型,否则会失效)){
需要多个线程同步执行的代码片段;
}同步监视器对象的选取
1. 所有需要同步执行同步块代码的线程看到的同步监视器对象必须是同一个
2. 同步监视器对象只能是引用类型
合适的锁对象在满足锁基本条件:
1. 必须是引用类型
2. 多个需要同步执行该代码块线程看到的必须是同一个对象
适当的同步有效性:
1. 存在并发安全时可以限制多个线程同步执行(发生并发安全时,多线程看到的该对象是同一个)
2. 不存在不发安全时允许多个线程异步执行(不存在并发安全时,多线程看到的不应是同一个)
对成员方法(方法)同步时,同步监视器对象不可选,只能是this(当前对象)
- synchronized同步锁
package thread;public class Synchronize {public static void main(String[] args) {Shop shop = new Shop();Shop shops = new Shop();Thread t1 = new Thread(shop::buy, "王文");Thread t2 = new Thread("李雯") {@Overridepublic void run() {shop.buy();}};Thread ts = new Thread(shops::buy, "王菲");t1.start();t2.start();// ts与t1和t2不是同一个店(不是同一个对象),不需要和t1t2排队(不需要同步)ts.start();}
}class Shop {// 同步方法
// public synchronized void buy(){public void buy() {Thread t = Thread.currentThread();try {System.out.println(t.getName() + "正在挑衣服...");Thread.sleep(5000);// 添加所进行阻塞等待释放(缩小同步范围),同步代码块,this是合适的锁对象,不同的对象互不干扰synchronized (this) {// 引用数据类型(字面量,不适合使用)
// synchronized ("上锁对象") {System.out.println(t.getName() + "正在试衣服");Thread.sleep(2000);}System.out.println(t.getName() + "结账离开!");} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
静态方法使用了synchronized修饰时,一定具有同步效果
- 静态方法的调用:类名.方法名(),此时相当于同步锁锁的内容是相同的,执行同步操作
- 不同对象:对象1.方法名()
对象2.方法名(),此时属于不同对象(常规多线程看到的不应是同一个,但是执行同步操作 —— 当做相同对象处理)原因:
在synchronized修饰方法时,默认的同步锁对象为this,但是静态方法中没有this关键字,只能是类对象(class实例)
即静态方法的同步监视器对象:synchronized(类名.class) {}
- 静态方法的synchronized同步锁
package thread;public class SynchronizeStatic {public static void main(String[] args) {Boo boo1 = new Boo();Boo boo2 = new Boo();Thread t1 = new Thread(Boo::doSome, "线程1 ");Thread t2 = new Thread(() -> Boo.doSome(), "线程2 ");Thread t3 = new Thread(() -> boo1.doSome(), "线程3 ");Thread t4 = new Thread(() -> boo2.doSome(), "线程4 ");t1.start();t2.start();}
}class Boo {// public synchronized static void doSome(){public static void doSome() {synchronized (Boo.class) {try {Thread t = Thread.currentThread();System.out.println(t.getName() + "正在执行doSome方法");Thread.sleep(2000);System.out.println(t.getName() + "执行完毕");} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
- StringBuilder线程不安全,StringBuffer线程安全
package thread;public class BuiderAndBuffer {public static void main(String[] args) {// StringBuilder线程不安全
// StringBuilder builder = new StringBuilder();// StringBuffer线程安全StringBuffer builder = new StringBuffer();Thread t1 = new Thread() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {builder.append("aaa");}}};Thread t2 = new Thread() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {builder.append("bbb");}}};// 启动线程t1.start();t2.start();try {Thread.sleep(50);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("数组长度:" + builder.length());}
}
- 集合ArrayList,LinkedList,HashSet等线程不安全,并发安全转换
package thread;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;public class SyncSet {public static void main(String[] args) {// 集合ArrayList,HashSet等线程不安全
// List<Integer> list = new ArrayList<>();// 将集合转为并发安全的(此时list不可改变,不可二次赋值)List<Integer> list = Collections.synchronizedList(new ArrayList<>());Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {list.add(i);}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {list.add(i);}});t1.start();t2.start();try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(list.size());}
}
2.4 synchronized互斥锁
互斥性:指多个线程执行不同的代码片段,但是这些代码片段不能同一时刻被执行
互斥性使用synchronized实现:
1. 使用synchronized锁定多个需要互斥的代码片段
2. 这些synchronized指定的同步监视器必须同一个对象
例:用synchronized分别对两个不同的方法进行上锁,然后使用同一个对象分别调用两个方法
- 互斥锁的实现
package thread;public class Mutex {public static void main(String[] args) {Foo f = new Foo();Thread t1 = new Thread(f::methodA);Thread t2 = new Thread(f::methodB);t1.start();t2.start();}
}class Foo{public synchronized void methodA(){try {Thread t = Thread.currentThread();System.out.println(t.getName() + ":正在执行A");Thread.sleep(5000);System.out.println(t.getName() + ":A执行完毕");} catch (InterruptedException e) {throw new RuntimeException(e);}}public synchronized void methodB(){try {Thread t = Thread.currentThread();System.out.println(t.getName() + ":正在执行B");Thread.sleep(3000);System.out.println(t.getName() + ":B执行完毕");} catch (InterruptedException e) {throw new RuntimeException(e);}}
}