初识线程,多线程

本文介绍了进程与线程的概念,包括进程的独立内存空间和线程的堆空间共享。讨论了多线程提高CPU利用率的优势,并详细阐述了线程的生命周期、线程的创建与执行方法、线程间的交互如join、sleep等方法。还提到了并发控制的synchronized关键字和volatile变量的作用,以及线程状态和避免死锁的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                                          进程与线程

进程:

同一个操作系统中执行的一个子程序,包含了三部分,虚拟CPU,代码,数据.

多进程:

同一个操作系统中执行的多个并行的子程序,可以提高CPU的使用率.

线程:

同一个进程当中并发执行的多个子程序流.

多线程:

同一个进程中并发执行的多个子程序流,可以提高CPU的使用率.

进程与线程的区别:

进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的.

线程的堆空间时共享的,栈空间时独立的,线程消耗的资源也比进程小,相互之间是可以影响的.

注意:

只有运行状态的线程才有机会执行代码,主线程中止是不会影响到其他正在运行的线程,主线程中止也就是main()方法退出了.只有进程中的所有线程都中止是,进程(jvm进程)才会退出,只要有线程没有中止,进程就不会退出.

操作系统决定线程是否有优先级,独占是的操作系统中系统会有优先级的概念,共享式的操作系统则不会有优先级的,在线程内部,程序依然顺序执行.

线程的编写方法:

1,

Thread t = new Thread() {

public void run() { // 运行

// 线程需要执行的代码

    // 不能抛出检查异常,只能自己处理检查异常

}

};

2,

把线程和要执行的代码分开

Thread 代表线程

Runnable 可运行的(线程要执行的代码)
 

Runnable runnable = new Runnable() {

public void run(){

// 要执行的代码

}

};

Thread t = new Thread( runnable );

t.start();

例子:

/*Runnable r = new Runnable() {

            @Override

            public void run() {

                System.out.println("hello");

            }

        };*/



        Runnable r = () -> System.out.println("hello");



        // () -> System.out.println("hello");



        Thread t = new Thread( r );

        t.start();

线程中常见的方法:

Thread.sleep(long n); // 让当前线程休眠n毫秒

Thread.currentThread(); // 找到当前线程

main 方法实际是由主线程(main)来调用的

注意 可以使用jconsle 来查看某个java进程中线程的运行情况

start() 让线程启动, 只能调用一次,如果调用了多次会出现IllegalThreadStateException

直接调用run和使用start间接调用run的区别:

直接调用run是在主线程中执行了run,没有启动新的线程

使用start是启动新的线程,通过新的线程间接执行run

join() 等待某个线程执行结束

join(long n) 表示等待线程结束,但是最多等待n毫秒

其它方法:

getName()  得到线程的名称

yield()      谦让

不推荐使用的方法:

.stop() 让线程停止

.suspend() 让线程暂停

.resume() 让线程继续

默认情况下,java进程需要等待所有线程都运行结束,才会结束.

有一种特殊的线程叫做守护(守护的是主线程)线程,只要主线程运行结束,即使守护线程的代码没有执行完,也会跟着主线程一起结束.

interrupt() 可以打断正在等待的线程(包括sleep, join的等待).

 线程的并发(Concurrent),synchronized (同步关键字)

语法:
 

synchronized(对象) {

要作为原子操作代码

}

用synchronized 解决并发问题:

static int i = 0;

static Object obj = new Object(); // 房间,能容纳一个人

public static void main(String[] args) throws InterruptedException {

    Thread t1 = new Thread(() -> { // 甲

        for (int j = 0; j < 5000; j++) {

            synchronized (obj) { // 甲会锁住这个房间

                i++;

            } // 甲从房间出来解开了锁

        }

    });

    Thread t2 = new Thread(() -> { // 乙

        for (int j = 0; j < 5000; j++) {

            synchronized (obj) { // 乙   在门外等待

                i--;

            } //

        }

    });

    t1.start();

    t2.start();

    t1.join();

    t2.join();

    System.out.println(i);
}

 

每个对象都有一个自己的monitor(监视器),当一个线程调用synchronized(对象),就相当于进入了这个对象的监视器。要检查有没有owner,如果没有,此线程成为owner; 但如果已经有owner了,这个线程在entryset的区域等待owner的位置空出来。

成为owner可以理解为获得了对象的锁

在竞争的时候,是非公平的

synchronized必须是进入同一个对象的monitor 才有上述的效果

volatile 易变的

可以用来修饰成员变量和静态成员变量,他可以防止线程从自己的高速缓存中查找变量的值,必须到主存中获取它的值。

它保证的是变量在多个线程之间的可见性, 不能保证原子性

static boolean run = true;

public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{
        while(run){
            // ....
        }
    });
    t.start();

    Thread.sleep(1000);
    run = false;
}

一个线程对run变量的修改对于另一个线程不可见,导致了另一个线程无法停止

synchronized 语句块既可以保证代码块的原子性,也可以保证代码块内变量的可见性。但缺点是synchronized是属于重量级操作,性能会受到影响。

 synchronized的另外两种写法:

public synchronized void test() {

}
等价于
public void test() {
	synchronized(this) {
	
	}
}
class Test{
	public synchronized static void test() {

	}
}
等价于
public static void test() {
	synchronized(Test.class) {
		
	}
}

 线程死锁

a 线程  获得 A 对象 锁

 接下来获取B对象的锁

b 线程获得 B对象 锁

 接下来获取A对象的锁

例:

Object A = new Object();
Object B = new Object();


Thread a = new Thread(()->{
    synchronized (A) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (B) {
            System.out.println("操作...");
        }
    }
});

Thread b = new Thread(()->{
    synchronized (B) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (A) {
            System.out.println("操作...");
        }
    }
});
a.start();
b.start();

检测死锁可以使用 jconsole工具

wait() notify() notifyAll()都属于Object对象的方法

wait() 等待

notify() 唤醒

它们都是线程之间进行协作的手段

obj.wait(); 让object监视器的线程等待

obj.notify(); 让object上正在等待的线程中挑一个唤醒

obj.notifyAll(); 让object上正在等待的线程全部唤醒

必须获得此对象的锁,才能调用这几个方法

Object obj = new Object();

new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-0线程执行....");
        try {
            obj.wait(); // 让线程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-0其它代码....");
    }
}).start();

new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-1线程执行....");
        try {
            obj.wait(); // 让线程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-1其它代码....");
    }
}).start();

try {
    Thread.sleep(2000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("唤醒obj上其它线程");
synchronized (obj){
//            obj.notify();
    obj.notifyAll();
}

}

wait() 方法实际是会释放对象的锁,进入WaitSet等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify为止

wait(long n)  有时限的等待, 到n毫秒后结束等待,或是被notify

 

面试题:sleep(long n) 睡眠n毫秒, wait(long n) 等待n毫秒

1) sleep是Thread方法,而wait是Object的方法

2) sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用

3)   sleep 在睡眠的同时,不会释放对象锁的,但wait在等待的时候会释放对象锁。

NEW(新建)  线程刚被创建,但是还没有调用 start方法

RUNNABLE(可运行) 当调用了start() 方法之后

BLOCKED(阻塞) 当线程进入了monitor监视器区,处于entrySet里准备竞争锁的时候,处于阻塞状态

WAITING(等待) 当调用了对象的wait方法,或调用了线程对象的join方法,进入了WaitSet,处于等待状态

TIMED_WAITING 当调用wait(long n) join(long n) 进入了WaitSet,处于有限时的等待状态

当调用sleep(long n) 是让当前线程放弃cpu的时间片,睡眠一会

TERMINATED (终止)当线程代码运行结束

 

五种状态:

NEW(新建), RUNNABLE(可运行) , RUNNING(正在运行), 阻塞(BLOCKED,WAITING, TIMED_WAITING )TERMINATED(终止)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值