多线程
进程和线程
- 资源分配与管理:
- 进程是系统进行资源分配和调度的基本单位,每个进程都拥有独立的地址空间,即拥有自己的内存、数据栈、全局变量等,以及系统分配的其他资源(如文件描述符)。这意味着进程之间是相互隔离的,一个进程的崩溃通常不会直接影响到其他进程。
- 线程是进程内的执行单元,多个线程共享所属进程的地址空间和资源。线程拥有自己的程序计数器、栈空间和局部变量,但不拥有单独的地址空间,这使得线程之间的通信和数据共享更为直接,同时也减少了创建和切换线程的开销。
- 调度与执行:
- 进程的创建、切换和销毁通常由操作系统内核完成,开销较大。进程之间的切换涉及到整个地址空间的切换,因此速度较慢。
- 线程则属于轻量级实体,其创建、切换和销毁通常在用户态下完成(尽管某些操作系统或特定情况下可能涉及内核),开销较小,可以实现更高的并发性。
- 通信与同步:
- 进程之间的通信需要借助于进程间通信(IPC)机制,如管道、消息队列、信号量、套接字等,这些机制通常由操作系统提供,实现较为复杂。
- 线程之间可以直接读写同一进程内的数据,通信更为简便高效,但也容易引发数据竞争和同步问题。为了协调线程间的执行顺序,可以使用互斥锁、信号量、条件变量等同步原语。
- 健壮性与稳定性:
- 一个进程的错误通常不会直接破坏其他进程的地址空间,因此系统的整体稳定性较好。
- 如果一个线程发生错误,由于线程间共享资源,可能导致整个进程崩溃,因此需要仔细管理资源共享和异常处理以维护进程的稳定性。
- 性能与效率:
- 进程提供了更好的隔离性和保护,但创建和切换成本较高,不适合需要频繁切换的场景。
- 线程由于共享资源和较低的上下文切换开销,适合于要求高并发和快速响应的场景,但在资源管理和保护方面不如进程严格。
创建多线程的方式
1、继承Thread类
package day01;
/**
* 创建线程的方式:
* 1、继承Thread类
* 2、重写其中的run方法 run方法中编写的就是让线程做的事
* 3、创建线程对象
* 4、启动线程不是调用run方法,调用start方法
* 调用start方法启动线程 然后会调用线程对象中的run方法
*/
public class MyThread {
public static void main(String[] args){
//创建线程对象
M1 m1 = new M1();
m1.start();
M2 m2 = new M2();
m2.start();
}
}
package day01;
//m1这个类就是线程类 Thread 本身就是线程类 子类也是一个线程类
public class M1 extends Thread{
// run方法中编写的就是线程做的事
@Override
public void run() {
for(int i=0;i<=100;i++){
System.out.println("第一个线程打印的数字是:"+i);
}
}
}
package day01;
//m2这个类就是线程类
public class M2 extends Thread{
// run方法中编写的就是线程做的事
@Override
public void run() {
for(int i=0;i<=100;i++){
System.out.println("第二个线程打印的数字是:"+i);
}
}
}
缺点:一旦一个类继承了Thread类后 就不能在继承其他的类了(Java单继承)
2、实现Runnable接口
package day01;
//实现Runnable接口 实现多线程 重写其中的run方法
public class M3 implements Runnable{
@Override
public void run() {
for(int i=0;i<=100;i++){
System.out.println("实现Runnable接口实现多线程:"+i);
}
}
}
package day01;
/**
* 二、实现Runnable接日
* 1、实现Runnable接
* 2、实现其中的run方法 run方法中编写的是让线程的做的事情
* 3、创建线程对象 Thread类创建线程对象
* 4、创建Runnable类型的对象 将创建Runnable类型的对象作为Thead类构造方法的实参对象传入到线程对象中
* 5、使用start启动线程 线程启动后 会调用Runnable对象中的run方法
*
*/
public class MyThread {
public static void main(String[] args){
//创建线程对象
M1 m1 = new M1();
m1.start();
M2 m2 = new M2();
m2.start();
M3 m3 = new M3();
Thread thread = new Thread(m3);
thread.start();
}
}
缺点:以上两种方法都没办法获取到run运行的结果的返回值 即使加上等待时间 也没办法确定
3、实现Callable接口
package day01;
import java.util.concurrent.Callable;
public class M4 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum = sum+i;
}
return sum;
}
}
package day01;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 三、实现Callable接口
* 1、实现Callable接口
* 2、创建FutureTask对象 并将Callable对象传入进来FutureTask<Integer> futureTask = new FutureTask<>(m4)
* 3、创建线程对象 将futureTask对象作为构造方法的实参传入
* 4、使用start方法启动线程
* 5、调用futureTask方法获取线程运行的结果
*/
public class MyThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程对象
M4 m4 = new M4();
FutureTask<Integer> futureTask = new FutureTask<>(m4);
Thread thread = new Thread(futureTask);
thread.start();
Integer sum = futureTask.get();
System.out.println(sum);
}
}
常用的方法:
1、Thread.sleep 使当前正在执行的停留[暂停执行]多少亳秒
sleep在哪个线程中 就让哪个线程暂停执行
2、setName 给线程设置名字
3、Thread.currentThread().getName() 获取当前正在执行的线程的名字
package day01;
/**
* 常用的方法:
* 1、sleep 使当前正在执行的停留[暂停执行]多少亳秒
* sleep在哪个线程中 就让哪个线程暂停执行
* 2、setName 给线程设置名字
* 3、Thread.currentThread().getName() 获取当前正在执行的线程的名字
*/
public class MyThread {
//main方法也是一个线程 主线程
public static void main(String[] args) throws InterruptedException {
M1 m1 =new M1();
m1.setName("线程一");
m1.start();
Thread.sleep(5000);
System.out.println("main方法执行完毕");
}
}
package day01;
//m1这个类就是线程类
public class M1 extends Thread{
// run方法中编写的就是线程做的事
@Override
public void run() {
for(int i=0;i<=100;i++){
System.out.println(Thread.currentThread().getName()+"打印的数字是:"+i);
}
}
}
总结:
创建线程的方式 3种 继承Thread 实现Callable接口 实现Runnable接口’
1、Thread本身就是线程 需要继承 重写run方法 run方法没有返回值 run方法不能抛异常
只能自己try catch 自己处理异常 外部不能获取到异常信息
2、实现Runnable接口 需要实现 实现其中的run方法 run方法没有返回值 run方法不能抛异常
只能自己try catch 自己处理异常 外部不能获取到异常信息
3、实现Callable接口 实现call方法 call方法中写的是让线程做的事情 call方法有返回值
get方法获取到返回值 call方法可以抛出异常 外部就能知道线程中发生了什么异常
优先级
线程存在着优先级:
线程如何调度:线程都会交给cpu调度
1、分时调度模型:所有的线程轮流使用cpu 平均分配每个线程占用的cpu的时间
2、抢占式调度模型:哪个线程抢到cpu,哪个线程执行 优先让优先级高的线程使用cpu(有很高的概率,不一定能拿到)
优先级都一样 随机选择一个线程执行,优先级高的线程使用cpu时间会更长一点
java中使用抢古式调度模型 多线程执行时 就有随机性!只有你抢到cpu时你才有执行权,抢不到就等着
创建的线程 我们使用start启动后 不一定立刻执行 只有你抢到cpu时你才能执行
优先级:1、getPriority()获取某个线程的优先级默认值是5
2、setPriority()设置线程的优先级 取值方位是1-10
public class MyThread {
//main方法也是一个线程 主线程
public static void main(String[] args) throws InterruptedException {
M1 m1 =new M1();
m1.setName("线程一");
m1.setPriority(10);
m1.start();
M1 m2 = new M1();
m2.setPriority(1);
m2.setName("线程二");
m2.start();
}
}
线程安全问题
线程安全问题:当多个线程操作同一份资源的时候会出现线程安全问题
package day01;
public class M5 extends Thread{
private String name;
public M5(String name) {
this.name = name;
}
private static int num = 100;
@Override
public void run() {
while (true){
if(num>0){
System.out.println(name+"卖出了第"+num+"号票");
num--;
}else {
break;
}
}
}
}
public class MyThread {
//main方法也是一个线程 主线程
public static void main(String[] args) throws InterruptedException {
M5 m51 = new M5("窗口1");
M5 m52 = new M5("窗口2");
// M5 m53 = new M5("窗口3");
m51.start();
m52.start();
// m53.start();
}
}
案例看到了重票 0号票 负票······等问题
解决方案:一个一个线程去操作共享资源 排队操作
Synchronized
线程同步:加锁 同步锁
线程同步之Synchronized
1、修饰方法
当前力法只能被一个线程访问,其他线程拿不到锁 只能等待 上一个线程释放锁资源
1、继承Thread
修饰的是非静态方法:谁调用这个非静态方法 谁就是锁对象不能用
修饰的是静态方法: 默认情况下以当前的类,class[类 模版]作为同步监视器 整个内存中是唯一的
2、实现Runnable
修饰的是非静态方法:谁调用这个非静态方法 谁就是锁对象
修饰的是静态方法: 默认情况下以当前的类,class[类 模版]作为同步监视器 整个内存中是唯一的
3、实现Callable
//修饰的是非静态方法,不能用
public class M5 extends Thread{
private String name;
private static Object o = new Object();
public M5(String name) {
this.name = name;
}
private static int num = 100;
@Override
public void run() {
while (num>0) {
sale();
}
}
public synchronized void sale(){
if (num > 0) {
System.out.println(name + "卖出了第" + num + "号票");
num--;
}
}
}
修饰的是静态方法
public class M5 extends Thread{
private static Object o = new Object();
private static int num = 100;
@Override
public void run() {
while (num>0) {
sale();
}
}
public synchronized static void sale(){
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + num + "号票");
num--;
}
}
}
实现Runnable
public class M6 implements Runnable{
private int num = 100;
@Override
public void run() {
while (num>0){
sale();
}
}
public synchronized void sale(){
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + num + "号票");
num--;
}
}
}
public class MyThread {
//main方法也是一个线程 主线程
public static void main(String[] args) throws InterruptedException {
M6 m6 = new M6();
Thread thread1 = new Thread(m6);
Thread thread2 = new Thread(m6);
thread1.start();
thread2.start();
}
}
2、修饰代码块
当前这个代码块中的代码只能有一个线程执行其他线程需要等待 上一个线程释放锁资源
synchronized(锁对象/同步监视器){
}
要求多个线程监控同一个对象
要求锁对象对所有操作此同步代码块的线程来说是唯一的,此时只能有一个线程来操作对应的代码块了
只有当前线程释放锁了,下一个线程才能进来执行相应的代码块中的代码
好处:解决了多线程情况下 线程安全问题
弊端:当线程很多时,需要每个线程去判断这把锁是否被其他线程持有?判断过程消耗资源
由原来的并行变成了串行,效率变低了
package day01;
public class M5 extends Thread{
private String name;
private static Object o = new Object();
public M5(String name) {
this.name = name;
}
private static int num = 100;
@Override
public void run() {
while (true) {
synchronized (o) {
if (num > 0) {
System.out.println(name + "卖出了第" + num + "号票");
num--;
} else {
break;
}
}
}
}
}
public class MyThread {
//main方法也是一个线程 主线程
public static void main(String[] args) throws InterruptedException {
M5 m51 = new M5("窗口1");
M5 m52 = new M5("窗口2");
m51.start();
m52.start();
}
}
synchronized存在的问题
1、具体加锁和解锁的过程我们看不见
2、对于多个线程来说,获取到锁是不公平的
3、拿不到锁,就死等
扩充:发生异常是否会释放锁?
会释放锁资源
Lock
线程同步之Lock 锁 接口 多线程监视同一把锁 共有同一个对象
实现类:ReentrantLock 默认非公平锁,传入true,是公平锁
Lock lock = new ReentrantLock()
try{
lock.lock()
业务逻辑
}catch(Exception e){
处理异常
}finally{
Lock.unLock();
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class M6 implements Runnable{
private int num = 100;
//默认非公平锁,传入true,是公平锁
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (num>0){
try {
lock.lock();
sale();
}catch (Exception e){
System.out.println("发生异常");
}finally {
lock.unlock();
}
}
}
public void sale(){
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + num + "号票");
num--;
}
}
}
public class MyThread {
//main方法也是一个线程 主线程
public static void main(String[] args) throws InterruptedException {
M6 m6 = new M6();
Thread thread1 = new Thread(m6);
Thread thread2 = new Thread(m6);
thread1.start();
thread2.start();
}
}
注意:
tryLock() 尝试获取锁,获取到返回true,获取不到返回false (立刻返回)
boolean tryLock(long time,TimeUnit unit)在指定的时间后获取不到锁 去做其他事情(指定时间内)
获取到锁,才能解锁,加几次锁,就解几次锁
默认非公平锁,传入true,是公平锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class M6 implements Runnable{
private int num = 100;
//默认非公平锁,传入true,是公平锁
private Lock lock = new ReentrantLock(true);
@Override
public void run() {
while (num>0){
//tryLock()尝试获取锁,获取到返回true,获取不到返回false
if(lock.tryLock()){
try {
sale();
Thread.sleep(1);
}catch (Exception e){
System.out.println("发生异常");
}finally {
//获取到锁,才能解锁,加几次锁,就解几次锁
lock.unlock();
}
}else {
System.out.println("没有获取到锁");
}
}
}
public void sale(){
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + num + "号票");
num--;
}
}
}
过程:
1、创建一个Lock类型的对象
2、使用Lock对象的lock方法加锁
3、使用Lock对象的unLock方法解锁
使用Lock对象作为锁对象时,需要将其放入try…catch…finally代码块中进行加锁和解锁
ReadWriteLock
ReadWriteLock读写锁 读锁写锁
排他锁|互斥锁:当前线程获取锁对象后,其他线程不能在获取该锁对象
只有当前线程释放锁其他线程才能拿到锁 (写锁 ReentrantLock synchronized lock)
共享锁:多个线程可以同时享有这把锁不会造成线程的等待 (读锁)
总结:
当一个线程获取到写锁, 其他线程既不能获取到写锁也不能获取到读锁
当一个线程获取到读锁,其他线程可以获取到读锁 获取不到写锁
只有同一个锁对象的写锁和读锁是互斥的并且写锁之间也是互斥的
不同锁对象之间没有影响
读读不互斥
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class M7 extends Thread{
private static ReadWriteLock lock = new ReentrantReadWriteLock();
//获取锁
private static Lock readLock = lock.readLock();
@Override
public void run() {
try{
readLock.lock();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName());
}catch (Exception e){
}finally {
readLock.unlock();
}
}
}
读写互斥
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
//写锁
public class M8 extends Thread{
private Lock writelock;
public M8(ReadWriteLock readWriteLock) {
this.writelock = readWriteLock.writeLock();
}
@Override
public void run() {
try{
writelock.lock();
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName());
}catch (Exception e){
}finally {
writelock.unlock();
}
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
public class M9 extends Thread{
//读锁
private Lock readLock;
public M9(ReadWriteLock readWriteLock) {
this.readLock = readWriteLock.readLock();
}
@Override
public void run() {
try{
readLock.lock();
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName());
}catch (Exception e){
}finally {
readLock.unlock();
}
}
}
public class MyThread {
//main方法也是一个线程 主线程
public static void main(String[] args) throws InterruptedException {
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
M8 m8 = new M8(readWriteLock);
M9 m9 = new M9(readWriteLock);
m8.start();
m9.start();
}
}
写写互斥
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class M7 extends Thread{
private static ReadWriteLock lock = new ReentrantReadWriteLock();
//获取锁
private static Lock writeLock = lock.writeLock();
@Override
public void run() {
try{
writeLock.lock();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName());
}catch (Exception e){
}finally {
writeLock.unlock();
}
}
}
ReentrantReadWriteLock的相关特性:
1、公平性
默认非公平锁 读线程之间没有锁操作 所以读操作没公平性和非公平性
写操作时 由于写操作可能立即获取到锁【写锁】 导致其他线程获取不到读锁或者写锁
此时涉及到公平性和非公平性
2、重入性
读锁或者写锁按照锁的请求顺序可以再次获取读锁或者写锁
只有写线程释放了写锁,读线程才能获取重入锁
【同一个线程】写线程获取写锁后,可以在次获取读锁和写锁,但是读线程获取读锁后不能获取写锁
读锁可以嵌套读锁和写锁,一旦嵌套了写锁,对应的写锁代码进不来
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class M10 extends Thread{
private static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock writeLock = readWriteLock.writeLock();
Lock readLock = readWriteLock.readLock();
@Override
public void run() {
try{
readLock.lock();
test();
}catch (Exception e){
}finally {
readLock.unlock();
}
}
public void test(){
try {
writeLock.lock();
System.out.println("我进来了");
}catch (Exception e){
}finally {
writeLock.unlock();
}
}
}
3、锁升级
读锁是不能直接升级为写锁 想要获取一个写锁 先释放读锁 才能获取写锁
4、锁降级
写线程获取写锁后也可以直接获取读锁,然后释放写锁,这样便从写锁到了读锁
从而实现锁降级,此时线程持有的锁是读锁
5、锁中断
读锁和写锁都支持获取锁期间被巾断 都支持tryLock5、
6、获取条件变量
写锁提供了条件发量[Condition对象]的支持 但是读锁不支持获取条件变量
7、读写锁的数量
读锁和写锁的数量最大的值只能是65535的数量
线程的生命周期
1、新建状态 new Thread这个过程新建一个线程
2、就绪状态(线程是可以运行的) 创建完线程对象后,调用start方法,处于就绪状态
3、运行状态(线程正在运行) 处于就绪状态的线程获得CPU后,开始运行,此时处于运行状态
在运行状态过程中,如果时间片到了,当前线程没有再次抢到CPU执行权,回到就绪状态
调用yield方法也能让线程回到就绪状态
4、阻塞状态 调用相关的方法,让线程暂时停止运行,并且释放CPU资源,不在竞争CPU
sleep方法会释放cpu资源,不会释放锁
wait方法会释放cpu资源,会释放锁
当阻塞完毕后,从阻塞状态回到了就绪状态,得到CPU后继续运行
5、消亡: 线程中的run执行完毕或者调用stop方法(),处于死亡状态
守护线程与非守护线程
守护线程:主线程执行的时候 对应的守护线程也会执行当主线程执行完毕后,无论守护线程的业务是否执行完毕,都会停止运行
非守护线程:主线程执行的时候对应的非守护线程也会执行当主线程执行完毕后 非守护线程继续执行 直至run方法执行完毕才会停止 setDaemon(true)当前线程是守护线程
public class MyThread {
//main方法也是一个线程 主线程
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run() {
for(int i=0;i<=100;i++){
try{
Thread.sleep(1000);
}catch (Exception e){
}finally {
System.out.println("线程一=======》"+i);
}
}
}
};
thread.setDaemon(true);
thread.start();
for (int i = 0; i <=100;i++) {
try{
Thread.sleep(100);
}catch (Exception e){
}finally {
System.out.println("主线程=====》"+i);
}
}
}
}
un方法执行完毕才会停止 setDaemon(true)当前线程是守护线程
public class MyThread {
//main方法也是一个线程 主线程
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run() {
for(int i=0;i<=100;i++){
try{
Thread.sleep(1000);
}catch (Exception e){
}finally {
System.out.println("线程一=======》"+i);
}
}
}
};
thread.setDaemon(true);
thread.start();
for (int i = 0; i <=100;i++) {
try{
Thread.sleep(100);
}catch (Exception e){
}finally {
System.out.println("主线程=====》"+i);
}
}
}
}