一.程序,进程,线程之间的关系
程序、进程和线程是计算机中重要的概念,它们描述了不同层次上的执行和管理方式。
程序是指由一系列指令组成的代码文件,它定义了算法和逻辑,用来解决特定的问题。程序本身是静态的,只有在被加载到内存并被操作系统调度执行时才会动起来。
进程是程序在执行时的一个实例,它是操作系统进行资源管理和调度的基本单位。一个进程拥有独立的内存空间和系统资源,包括打开的文件、网络连接等。进程之间相互独立,一个进程的崩溃不会影响其他进程的运行。
线程是进程内的独立执行单元,一个进程可以有多个线程。线程共享相同的内存空间和系统资源,可以与其他线程共享数据。相对于进程来说,线程的创建和销毁更加轻量级,切换和通信的开销也较小。
三者关系:一个程序在执行时被操作系统加载成一个进程,进程中可以有多个线程并行执行。程序是静态的,进程和线程是动态的执行状态。进程和线程通常是并发执行的,它们的执行顺序和并发性由操作系统的调度算法决定。不同的操作系统可能有不同的调度策略。同时,进程和线程是操作系统资源的消耗者。每个进程都有自己独立的内存空间和系统资源,而线程则共享进程的资源。因此,正确的管理进程和线程,合理分配资源,能够提高系统的性能和稳定性。
二.线程
2.1如何创建线程
在Java中,你可以通过两种方式创建线程:
继承Thread类:创建一个继承自Thread类的子类,重写run()方法,并在run()方法中定义线程的任务逻辑。然后实例化子类对象,并调用start()方法来启动线程。
以下是一个使用继承Thread类创建线程的示例代码:
public class MyThread extends Thread {
@Override
public void run() {
// 线程任务逻辑
System.out.println("线程任务逻辑");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
实现Runnable接口:创建一个实现了Runnable接口的类,实现run()方法,并在run()方法中定义线程的任务逻辑。然后实例化该类对象,并将其作为参数传递给Thread类的构造方法。再调用start()方法来启动线程。
以下是一个使用实现Runnable接口创建线程的示例代码:
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程任务逻辑
System.out.println("线程任务逻辑");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start(); // 启动线程
}
}
无论使用哪种方式创建线程,都需要调用start()方法来启动线程。线程启动后,会自动调用run()方法来执行线程任务。
三.线程中的常用方法
线程中有一些常用的方法,以下是其中一些常见的方法:
(1)start()方法:用于启动线程。调用该方法后,线程会被放入就绪队列,等待获取CPU的执行权。
(2)run()方法:线程的主体代码逻辑通常在run()方法中实现。该方法定义了线程的任务。
(3)sleep(long millis)方法:让当前线程暂停执行一段时间,单位为毫秒。在暂停期间,线程状态不会变为阻塞或就绪状态,而是保持运行状态。
(4)join()方法:等待调用该方法的线程执行完毕后再继续执行其他线程。可以通过传入参数指定最长等待时间。
(5)yield()方法:让出当前线程的CPU执行权,使得其他具有相同优先级的线程有机会执行。yield方法只是让当前线程重新回到可运行状态,而不是阻塞状态。
(6)setName():为线程设置名字。
(7)currentThread():获取当前线程。
(8)getName():获取当前线程的名字。
(10)setPriority():设置线程的优先级(从一到十,默认是5)。
MAX_PRIORITY:取值为10,表示最高优先级。
MIN_PRIORITY:取值为1,表示最底优先级。
NORM_PRIORITY:取值为5,表示默认的优先级
(11)getPriority():获取当前线程的优先级。
(12)setDaemon():设置守护线程。
线程中常用方法的具体实现:
public class Mythread1 extends Thread{
//创建线程首先要继承Thread
//重写run方法
@Override
public void run() {
//将需要执行的代码放在run方法当中
for (int i = 0; i < 1000; i++) {
if (i%2==0){//获取0-1000之间的偶数
System.out.println("MyThread:"+i);
}
}
}
}
public static void main(String[] args) {
//main函数相当于是主线程
//创建子线程对象
Mythread1 mythread1 = new Mythread1();
//切记不能直接调用子线程中的run方法,那就相当于是直接调用方法,不是启动子线程
//mythread1.run();
mythread1.start();//启动子线程
for (int i = 0; i < 1000; i++) {
if(i%2==1){//main函数让其输出奇数,区分
System.out.println("main:"+i);
}
}
}
在这个代码中,在main函数中创建了一个线程,一个线程执行的是输出偶数,一个是偶数。这段代码运用的方法有start()。
public class MyRunnnable1 implements Runnable{
//Runnable接口,要重写run方法
//run方法中写的是要执行的逻辑(代码)
@Override
public void run() {
for (int i = 0; i <1000; i++) {
System.out.println(Thread.currentThread().getName()+i);
//Thread.currentThread().getName()调用此线程的名字
}
}
}
public static void main(String[] args) {
//创建Runnable接口的对象
MyRunnnable1 myRunnnable = new MyRunnnable1();//创建了一个线程中执行的任务
Thread thread = new Thread(myRunnnable);//此处才是创建了一个线程
thread.setName("Run线程");
System.out.println(thread.getName());//获取线程的名字
System.out.println(thread.getId());//获取线程的id
thread.setPriority(10);//设置线程优先级,默认为 5,优先级的范围是(1-10)
thread.start();//启动线程
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
System.out.println(thread.getPriority());//获取线程优先级
System.out.println(Thread.currentThread().getPriority());//获取当前线程的优先级
}
这里的两段代码是创建线程的另外一种方式。实现的是Runnable接口。
public class MyThreadDemo extends Thread{
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
if (i%50==0){
MyThreadDemo.yield();//让出自己所占用的内存
}
System.out.println("MyThread:"+i);
}
}
}
public static void main(String[] args) {
MyThreadDemo myThreadDemo = new MyThreadDemo();
myThreadDemo.start();
for (int i = 0; i < 4000; i++) {
System.out.println("main:"+i);
}
}
在这两段代码中用到了yeild()方法,这个方法的作用是让出自己所占用的线程。
public class MyThread extends Thread{
//测试join
@Override
public void run() {
for (int i = 0; i < 101; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.setName("xx");
thread.join();//让其他线程等待此线程结束
thread.start();
MyThread thread1 = new MyThread();
thread.setName("dd");
thread1.start();
}
这两段代码测试的是join的功能,等待调用该方法的线程执行完毕后再继续执行其他线程。
public class DeamonThread extends Thread {
//测试守护线程
@Override
public void run() {
int n = 0;
while (true){
System.out.println(Thread.currentThread().getName()+":"+n++);
}
}
}
public static void main(String[] args) {
DeamonThread deamonThread = new DeamonThread();
deamonThread.setDaemon(true);//设置此线程为守护线程
deamonThread.start();
for (int i = 0; i <1000 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
这两段代码测试的是设置守护线程,守护线程就是只要当前JVM实例中尚存在 任何一个非守护线程没有结束,守护线程就全部工作。
以下两段是为了展示同步锁(sychronized)的功能:
public class PiaoThread extends Thread{
static int num = 10;
@Override
public void run() {
while(true){
this.Buy();
}
}
/*synchronized 修饰方法有两种情况:
1.当修饰的是非静态方法时候,锁的对象是this
2.当修饰的是静态的方法时,锁的对象是该类的class对象
*/
public synchronized static void Buy(){
if(num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}
}
}
public static void main(String[] args) {
PiaoThread piaoThread = new PiaoThread();
piaoThread.setName("窗口一");
piaoThread.start();
PiaoThread piaoThread1 = new PiaoThread();
piaoThread1.setName("窗口二");
piaoThread1.start();
}
设置同步锁之后,同步锁中的代码在同一时间只有一个线程可以进来。
如上两段代码的结果如下:
以下两段代码是上两段代码的Runnable接口的实现方式:
public class PiaoRun implements Runnable{
int num = 10;
@Override
public void run() {
while(true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.buy();
}
}
public synchronized void buy(){
if(num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}
}
}
public static void main(String[] args) {
PiaoRun piaoRun = new PiaoRun();
Thread t1 = new Thread(piaoRun,"窗口一");
Thread t2 = new Thread(piaoRun,"窗口二");
t1.start();
t2.start();
}
这两段代码就是Runnable接口的实现方式。
public class LockThread extends Thread{
static int num = 10;
static Lock lock = new ReentrantLock();
/*ReentranLock 和 synchronized的区别
*1.Lock 是一种Java代码底层的控制实现 ,synchronized是关键字,不依赖与Java代码,依赖与底层指令
*2.ReentranLock只能对一段代码进行加锁,而synchronized既可以修饰方法,又可以修饰属性
*3.Lock要自己主动加锁和释放锁,synchronized会自动加锁和释放锁
* */
@Override
public void run() {
while(true){
//启动锁
lock.lock();
try {
Thread.sleep(20);
if(num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();//关闭锁
}
}
}
}
public static void main(String[] args) {
LockThread lockThread = new LockThread();
Thread t1 = new Thread(lockThread,"窗口一");
Thread t2 = new Thread(lockThread,"窗口二");
t1.start();
t2.start();
}
以上两段代码是另外一种给代码加锁的方式。
public class SiThread extends Thread{
Boolean b = null;
static Object objA = new Object();
static Object objB = new Object();
public SiThread(Boolean b) {
this.b = b;
}
@Override
public void run() {
if(b){
synchronized(objA){
System.out.println("if-objA");
synchronized(objB){
System.out.println("if-objB");
}
}
}else{
synchronized (objB){
System.out.println("else-objB");
synchronized (objA){
System.out.println("else-objA");
}
}
}
}
}
public static void main(String[] args) {
SiThread siThread1 = new SiThread(true);
SiThread siThread2 = new SiThread(false);
siThread1.start();
siThread2.start();
}
以上的两段代码就是所谓的死锁,两段代码都在等待对方正在运行的线程,但是都不放手,就会造成死锁,同时程序也不会停止,结果如下:
public class WaitThread extends Thread{
static Object object = new Object();
static int num = 1;
@Override
public void run() {
while (num<=100){
synchronized (object){
object.notify();//唤醒被wait的线程,优先优先级高的线程
System.out.println(Thread.currentThread().getName()+":"+num);
num++;
try {
object.wait();//调用此方法会让当前正在执行的线程进入阻塞状态,并释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
WaitThread waitThread1 = new WaitThread();
WaitThread waitThread2 = new WaitThread();
waitThread1.start();
waitThread2.start();
}
以上这两段代码就是Object类中的notify和wait方法,一个是让线程处于阻塞状态,释放当前锁,一个是唤醒被wait的线程,如果有多个被wait的线程,首先唤醒优先级最高的一个。notifyall()此方法式用于唤醒全部被wait的方法。
注意:这三个方法必须处在同步代码块中。
四.多线程
4.1多线程的概念
多线程是指在一个程序中同时运行多个线程,每个线程都可以独立执行不同的任务或代码片段。与传统的单线程程序相比,多线程能够充分利用计算机的多核处理器和资源,并提高程序的并发性和响应性。在多线程环境下,每个线程都有自己的执行路径和执行状态,并且可以独立地进行操作和执行代码块。不同线程之间可以并行执行,从而实现同时处理多个任务、提高系统吞吐量和资源利用率。
4.2多线程的优缺点
多线程编程有以下优点和缺点:
优点:
- 提高程序的响应性:多线程可以将耗时的操作放在后台线程中执行,使得主线程可以及时响应用户的输入和请求,提升程序的交互性和用户体验。
- 提高系统资源利用率:通过并行执行多个任务,充分利用多核处理器或多CPU的计算能力,加快任务处理速度,提高系统的吞吐量和资源利用效率。
- 改善程序设计结构:使用多线程可以将复杂的任务拆分成多个独立的子任务,并发地进行处理,使程序的结构更清晰、模块化,便于维护和扩展。
缺点:
- 多线程编程复杂性高:线程之间的并发操作存在数据共享和同步等问题,需要合理地设计和管理线程之间的协作和数据同步,对开发人员要求较高,容易出现各种并发问题,如死锁、竞态条件等。
- 资源占用和调度开销:每个线程都会占用一定的内存空间和CPU调度开销,当线程数量过多时,会增加系统的负担,甚至导致系统资源的不足和性能下降。
- 可能引发安全问题:多线程程序在处理共享数据时需要正确地进行同步操作,否则可能会引发数据不一致、并发访问冲突等线程安全问题,增加了程序设计和调试的复杂性。
因此,在使用多线程编程时,需要仔细权衡其优点和缺点,并合理设计和管理多线程的使用,以避免潜在的问题和风险。
4.3线程同步
线程同步是指多个线程之间协调和同步彼此的行为,以保证它们按照预期的顺序执行和访问共享资源,避免出现并发操作导致的数据不一致、竞争条件等问题。
并发与并行:
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:在一个时间段内一次执行操作.例如卖票,抢购,秒杀看似同时进行, 实际是一个一个执行。
多线程同步:
多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制, 即各线程间要有先来后到;
同步就是排队+锁:
几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作;为了保证数据在方法中被访问时的正确性,在访问时加入锁机制。
4.4线程通信
线程通信是指多个线程之间进行信息交换、数据传递或协调操作的过程。在多线程编程中,不同的线程可能需要共享数据、协调执行顺序或进行协作任务,因此需要一种机制来实现线程之间的交互和通信。
常见的线程通信方式包括:
-
共享内存:多个线程通过访问共享内存区域来进行数据共享。线程可以读取和修改共享内存中的数据,但需要注意线程安全性和同步问题,以避免竞态条件或数据不一致的情况发生。
-
互斥锁(Mutex):通过互斥锁来保护共享资源的访问,确保同一时间只有一个线程能够操作共享资源。当一个线程获取到互斥锁时,其他线程需要等待,直到该线程释放锁。
-
条件变量(Condition Variable):线程可以使用条件变量进行等待和唤醒的操作。一个线程可以在满足特定条件之前等待,并在其他线程满足条件后发出信号通知等待线程继续执行。
-
信号量(Semaphore):通过信号量来控制对资源的访问数量。信号量可以用于限制同时访问某个资源的线程数量,或者在某个事件满足时发出通知。
-
消息队列(Message Queue):线程之间可以通过消息队列发送和接收消息。一个线程可以将消息放入队列中,另一个线程则从队列中获取消息进行处理。
-
管道(Pipe):管道是一种单向的通信机制,可用于实现两个线程之间的通信。一个线程将数据写入管道的写端口,另一个线程则从管道的读端口读取数据。
这些线程通信方式可以根据具体需求和场景进行选择和组合使用,以实现线程之间的协作和信息交换。在实际应用中,需要注意线程安全性、同步问题和避免死锁等相关技术细节。