Java中的进程、线程、并发、并行、多线程、线程同步、死锁、线程状态转换图、线程通信的概念实例及详解完整版。

1.什么是进程?什么是线程?

进程:一个正在运行的程序,它是资源分配和管理的基本单位。每个进程都拥有独立的地址空间和资源,因此进程之间相互隔离,不直接共享资源。

线程:线程是进程内的执行单元,一个进程可以包含多个线程,它们共享进程的资源,如内存空间。线程是操作系统调度的基本单位。

总的来说线程是操作系统调度的基本单位,用于实现多任务并发执行,而进程是程序的执行实例,它们之间有自己的资源和状态。多个线程可以属于同一个进程,共享进程的资源。

2.什么是并发?什么是并行?

1.并发是指多个任务在同一时间段内交替执行,(cpu在多任务间进行切换)
 2.并行是指多个任务在同一时刻同时执行,利用多个处理单元或计算资源并行处理多个任务(多个cpu同时处理多个任务)

注:这里的任务指线程

3.什么是单线程?

如果一个进程只有一个线程,这样的程序称为单线程程序。

优点:资源可以最大化,不会出现争夺资源的问题

4.什么是多线程?

如果一个进程,拥有不止一个线程,这样的程序称为多线程程序

优点:可以同时执行多个任务,提高运行的效率

5.什么时候用多线程?

1.多个任务互不影响任务之间没有交集,谁先执行完,谁后执行完无所谓。这种时候可以使用多线 程,让多个任务同时执行。

2. 当你有一个任务很耗时,可以把这个耗时的任务放到一个单独线程里执行,这样就不会阻塞程序的 执行, 例如:下载附件。

3. 你的需求只能靠多线程(多人聊天,英雄联盟各个角色的操作)完成的时候,要使用多线程。 4. 买票 12306 1亿人同时抢票等等

6.线程同步是什么?(解决多线程不安全的一种机制)

定义:线程同步又叫线程安全,是一种确保多个线程在访问共享资源时按照一定顺序进行操作的机制。注:(它可不是多个线程同步执行,名字起的很奇怪)

目的:线程同步的主要目的是确保多个线程在访问共享数据时能够正确地进行同步,为了保证数据的正确性,避免出现数据不一致的问题。通俗的来说,就是解决线程不安全的一种机制

实现线程同步的方式:(1)同步代码块(2)同步方法(3)Lock对象加锁和解锁

7.java死锁是什么?(多线程并发执行的不安全情况)

1.定义:

当多个线程在获取资源时出现循环依赖并被阻塞,无法执行的情况称为死锁。

我自己的理解(仅供参考):很多人说java中的多线程是并发执行的,就是一个cpu在多个时间片之间来回切换,还有的是说取决与电脑自身是不是多核还有什么操作系统什么宽七八糟的东西,我觉得就是纯扯淡,我这里理解的就是它是并发执行的。

2.举例

我这里举个例子,你们也思考一下:

有两个线程A和B,有我定义好的监视器(可以说成锁标记)a和b(唯一) 

1.第一种情况并没有发生死锁,因为t1先抢到了CPU时间片(很短),此时因为并发执行,t1占用着cpu资源,t2并没有执行,然后t1获得锁标记a,输出“我是线程1的锁”,然后时间片还未用完,t2还未不能执行,t1接着获得锁标记b然后输出“我是线程1的锁b”,此时t1结束释放锁标记b,然后释放锁标记a,t1线程结束。接着t2抢到了cpu也是同样执行,此时就是很正常的输出,程序也正常结束(看下图)所以我在这理解为并发执行没问题吧!!

public static void main(String[] args) {
		String a= "a";
		String b= "b";
		//新建两个线程
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized(a) {
					System.out.println("我是线程1的锁a");
					synchronized(b) {
						System.out.println("我是线程1的锁b");
					}
				}
			}
		});
		t1.start();
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized(b) {
					System.out.println("我是线程2的锁b");
					synchronized(a) {
						System.out.println("我是线程2的锁a");
					}
				}
			}
		});
		t2.start();
	}

 2.这就是一个典型的死锁,我这里还是按照多线程并发来解释,还是跟上面一样,只是在”我是线程1的锁a“的后面加了个循环,就是一个耗时操作,再次分析,此时t1获得CPU时间片(很短),开始执行,先获得锁标记a,输出”我是线程1的锁“,再执行累加操作时(还未累加完)时间片刚好用完,释放CPU时间片,然后回到可执行状态继续去抢夺cpu,此时t2恰好抢到CPU时间片,开始执行,获得锁标记b,然后呢输出”我是线程2的锁b“,然后t2没有耗时操作,所以t2还有时间片对吧,所以继续获得锁a了,这时候问题就来了,锁a刚刚被t1标记了,t1并没有执行完,并没有释放锁标记,那t2此时即使有时间片也不能往下面执行了,只能在输出”我是线程2的锁b“以后等待时间片耗完,此时t1继续抢到CPU时间片,然后继续执行刚刚的累加操作,继续输出完,开始获得锁标记b,但是t2刚刚是不是没获取到锁标记a就卡住了,所以t2并没有释放锁标记b,那此时t1,只能在输出sum结果之后白白等待时间片耗尽,然后t2获得时间片继续卡在刚才的获取锁标记a的位置,然后耗完时间片,然后t1也是如此,他俩最终都释放不了,开始进入循环等待对方释放锁标记,这就导致死锁,程序永远终止不了。最终结果如下图所示(有红点程序未终止)

package com.lanou.file;

public class Test {
	public static void main(String[] args) {
		String a= "a";
		String b= "b";
		//新建两个线程
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized(a) {
					System.out.println("我是线程1的锁a");
					long sum=0;
					for(int i=0;i<10000000;i++) {
						sum+=i;
					}
					System.out.println("sum = "+sum);
					synchronized(b) {
						System.out.println("我是线程1的锁b");
					}
				}
			}
		});
		t1.start();
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized(b) {
					System.out.println("我是线程2的锁b");
					synchronized(a) {
						System.out.println("我是线程2的锁a");
					}
				}
			}
		});
		t2.start();
	}
}

 3.如何避免死锁问题

在 Java 中,采用了 wait 和 notify 这两个方法,来解决死锁机制。 首先,在 Java 中,每一个对象都有两个方法:wait 和 notify 方法。这两个方法是定义在 Object 类中的 方法。对某个对象调用 wait()方法,表明让线程暂时释放该对象的锁标记。

我这里简单分析一下关键步骤

首先,t1.wait()会立即释放锁对象,并且交出cpu时间片,进入等待队列状态(线程转换图后面有讲)此时t2会获得锁对象a,执行到notify以后,会随机唤醒一个线程,取决于线程调度器的策略和算法但此时并没有直接给它锁对象,只是告诉他“你有机会继续执行了,但是在你能够真正执行之前,需要先获取到对象的锁。”,此时该线程进入锁池状态,但是notify调用以后,当前线程不会立马结束,而是执行完synchronized代码块里面的所有内容以后才会释放锁对象,这时候线程池里面的t1才会有机会获得锁对象,为什么是有机会呢?因为锁池里面可能有很多个在等待获取的同一个锁对象的线程,但此时谁获得是由线程调度器的策略和算法决定,可以理解为操作系统决定的,此时获得锁对象以后进入可运行状态去竞争cpu时间片继续执行。

package com.lanou.file;

public class Test {
	public static void main(String[] args) {
		String a= "a";
		String b= "b";
		//新建两个线程_
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized(a) {
					System.out.println("我是线程1的锁a");
					long sum=0;
					for(int i=0;i<10000000;i++) {
						sum+=i;
					}
					System.out.println("sum = "+sum);
					try {
						a.wait();//释放锁对象标记,并且直接交出CPU时间片
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					synchronized(b) {
						System.out.println("我是线程1的锁b");
					}
				}
			}
		});
		t1.start();
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized(b) {
					System.out.println("我是线程2的锁b");
					synchronized(a) {
						System.out.println("我是线程2的锁a");
						a.notify();//唤醒一个正在等待同步对象(锁对象)上的线程
						System.out.println("我调用notify,但是我是它后面内容。");
					}
				}
			}
		});
		t2.start();
	}
}

8.线程状态转换图

我觉得这里需要解释的是等待队列状态和锁池状态

1.运行状态→等待队列状态:调用了wait方法,此时释放锁对象,释放cpu时间片进入等待队列状态

2.等待队列→锁池状态:比如上面的t2中的a调用了a.notify,此时唤醒(唤醒取决于线程调度器的策略和算法)等待队列中的一个线程进入锁池状态,注意,唤醒只是说它有机会获取锁了,并不是直接给他了,并且notify并不会终结当前线程,而是继续执行完当前的同步代码块内容。才能释放锁对象。

3.锁池状态→可运行状态:当释放锁对象后,锁池里面由操作系统选中(取决于线程调度器的策略和算法)哪个线程获得锁对象然后进入可运行状态,就是还还给那个人。记住不是先处于运行状态再获取锁对象,而是先获取锁对象再去竞争cpu去获取时间片处于运行状态,你想t2线程执行完它才获得锁的,虽然t2释放了时间片,但是不一定是获得锁对象的直接抢到了cpu时间片,所以还是处于可运行状态再去抢夺cpu时间片去处于运行状态。再换一种说法,它调用wait之后就是直接交出cpu时间片了不可能处于运行状态。

4.运行状态→锁池状态:就是当线程里面有需要获得锁对象执行同步代码块的时候,也就是有被synchronized修饰的同步代码块。此时锁被其他线程占用着,就直接进入锁池状态。

9.线程通信

1.定义

不同线程之间可以相互的发信号。这就是线程通信。之所以需要进行线程通信,是因为有些时候,一个 线程的执行需要依赖另外一个线程的执行结果。在结果到来之前,让线程等待(wait),有了结果只之 后再进行后续的操作。对于另外一个线程而言,计算完结果,通知(notify)一下处于等待状态的线程.

  1. 协调: 线程通信可以用于协调不同线程的执行顺序,以实现特定的逻辑要求。

  2. 同步: 通过线程通信,你可以确保多个线程按照特定的顺序执行,避免并发执行引起的数据不一致性。

  3. 数据交换: 线程通信也用于在线程之间传递数据,允许一个线程生成数据,另一个线程处理数据。

  4. 等待/通知: 在 Java 中的 wait()notify() 方法就是用于线程通信,实现等待和通知机制,用于线程间的协调和同步。

最经典的例子就是生产者和消费者。我这里只给出关键代码。

2.例子:生产者和消费者

public class Saler {//售货员类
private int productCount = 10; //商品数量
public synchronized void stockGoods() {
if(productCount < 2000) {
productCount++;
System.out.println(Thread.currentThread().getName() + "生产了1件商品,
库存是:" + productCount);
this.notifyAll();
}else {
System.out.println("库存满了");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void sellGoods() {
if(productCount > 0) {
productCount--;
System.out.println(Thread.currentThread().getName() + "购买了1件商品,
库存剩余:" + productCount);
this.notifyAll();
}else {
System.out.println("库存不足");
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

 分析:假如我们有多个生产者生产商品,有多个消费者消费。

1.售货:

当我们没有商品(库存为0)的时候是不是应该让消费线程暂停购买啊,调用this.wait()。

当我们有商品(有库存)的时候是不是只要消费者消费了,生产者就可以继续生产了。

调用this.notify()去唤醒刚刚库存满时暂停的生产者继续生产。

2.进货:

当我们有商品(商品库存已满)的时候是不是应该暂停生产者生产,调用this.wait().

当我们有商品(未满)的时候是不是只要生产了一件商品,就可以唤醒刚刚库存为0时的

暂停的消费者了。调用this.notifyAll();

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在 .NET 实现多线程并发可以使用以下几种方法: 1. 使用 Thread 类:Thread 类是 .NET 提供的最基本的多线程编程类。你可以创建多个 Thread 实例,并在每个实例执行不同的任务。但是需要注意线程同步和协作问题,避免出现竞态条件和死锁等问题。 2. 使用 ThreadPool 类:ThreadPool 是一个已经初始化的线程池,可以通过 QueueUserWorkItem 方法将任务加入到线程线程池会自动管理线程的创建和回收,适用于大量短时间执行的任务。 3. 使用 Task Parallel Library(TPL):TPL 是 .NET Framework 4.0 引入的并行编程模型,它提供了更高级的抽象,通过 Task 类封装了多线程操作。你可以使用 Task.Factory.StartNew 方法创建和管理任务,并通过 Task.WaitAll 或 Task.WaitAny 等方法等待任务完成。 4. 使用 async/await 异步编程模型:.NET Framework 4.5 引入了异步编程模型,通过 async/await 关键字可以方便地进行异步操作。你可以使用 Task.Run 方法在后台线程执行耗时操作,同时使用 async/await 简化异步代码的编写。 5. 使用并发集合:.NET 提供了一些并发集合类,如 ConcurrentQueue、ConcurrentStack 和 ConcurrentDictionary 等,它们在多线程环境下提供了安全的并发访问。 在实现多线程并发时,需要注意线程安全、资源共享和线程同步等问题,避免出现数据竞争和死锁并发问题。可以使用锁、互斥量、信号量、事件等同步机制来保证线程安全和协作。同时,合理地使用线程池、任务调度和异步编程等技术可以提高并发性能和资源利用率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值