【Java高级特性】java学习之旅35-多线程

本文详细介绍了Java中的线程与进程概念,包括线程的创建、并发与并行的区别、线程的生命周期、调度策略、同步机制、线程锁及死锁。并展示了通过继承Thread、实现Runnable和Callable接口创建线程的方法,以及线程池的使用。此外,还探讨了线程通信中的等待和通知机制,以及避免死锁的策略。

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

什么是线程,什么是进程?

进程:进行中的程序,系统中的最小执行单元,有独立的运行空间和内存空间

线程:进程中的最小执行单元,每一个进程至少包含一个主线程。

什么是并发,什么是并行?

并发:多个线程争抢系统资源,交替执行,单核单线程cpu即可执行。

并行:多个线程同时执行,互不影响,必须要在多线程CPU上才能执行。

实现多线程的根本目的:

根本目的是压榨系统资源,可以一定程度上的提高任务执行效率。

常用方法:

  1. 设置线程的名字:setName(String name)
  2. 获取线程的名字:Thread类中的getName(); Thread.currentThread.getName()
  3. 启动线程:start()

如何创建一个线程(4种方式):

  1. 自定义线程类继承Thread类
/**
* 1. 创建一个自定义的类继承Thread
* 2. 重写run()方法,方法内编写线程任务
* 3. 创建自定义线程的对象,执行start()方法
*/
public class MyThread01 extends Thread {
    
    @Override
    public void run(){
        long startTime = System.out.currentTimeMillis();
        for(int i = 1; i <= 100; i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + "用时" + (endTime - startTime));
    }
}

public static void test1(){
    //使用对象去点run方法也可以实现,但是与线程无关了
    //public synchronized(同步)  void start() 使用线程的开始方法start()会默认去找run方法
    MyThread01 thread01 = new MyThread01();
    thread01.setName("线程1");
    MyThread01 thread02 = new MyThread01();
    thread02.setName("线程2");
    //这里如果统计时间,发现两个线程的时间比按次序打印的时间还要慢
    //这是因为打印方法的out是static修饰的全局共用这一个,还有println方法是加上了 synchronized同步锁使得一次只有
    //一个线程访问进来
    thread01.start();
    thread02.strat();
}
  1. 自定义线程类实现Runnable接口
/**
* 1. 创建一个自定义的类实现Runnable接口
* 2. 重写run()方法
* 3. 创建自定义线程类的对象
* 4. 创建Thread对象,使用自定义线程类的对象作为有参构造的参数
* 5. 执行Thread对象的start()方法
* 注意:Runnable是函数式的接口,可以使用lambda表达式做实现
*/

//第一种实现方式
public class MyThread02 implements Runnable{
    
    @Override
    public void run(){
        long startTime = System.currentTimeMillis();
        for(int i = 1; i <= 100; i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + "用时" + (endTime - startTime));
    }
}

public static void test2(){
    MyThread02 run = new MyThread02();
    
    Thread thread1 = new Thread(run);
    thread1.setName("线程1");
    thread1.start();
    
    Thrad thread2 = new Thread(run);
    thread2.setName("线程2");
    thread2.start();
}

//第二种实现方式
Runnable run = () -> {
    for (int i = 1; i <= 100; i++){
        System.out.println(Thread.currentThread().getName() + ":" + i);
    }
};

Thread thread = new Thread(run);
thread.start();

3.自定义线程类实现Callable接口(jdk1.5+,可以抛出异常,可以有返回值)

/**
* 1. 创建一个自定义的类实现callable接口
* 2. 重写call()方法
* 3. 创建自定义线程的对象
* 4. 创建FutureTask类的 对象,把自定义线程对象作为构造方法的参数。
* 5. 创建Thread类对象,把FutureTask类的对象作为构造方法的参数
* 6. 调用Thread对象的start()方法;
*/
public class MyThread03 implements Callable<Long> {
    @Override
    public Long call() throws Exception {
        long startTime = System.currentTimeMillis();
        for (int i = 1; i <= 100; i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }
}

private static void test4() {
    //三层包装,第一步创建实现了Callable接口的实现类对象
    MyThread03 myThread03 = new MyThread03();
    //创建FutureTask对象将实现类对象作为参数放入FutureTask对象中
    FutureTask<Long> futureTask = new FutureTask<>(myThread03);
    //创建线程对象,将FutureTask对象作为参数,FutureTask对象继承了Runnable所以多态可以放入其中
    Thread thread = new Thread(futureTask);
    
    thread.start();
    //使用该接口创建线程的好处
    // 1. 可以获得返回值
    // 2. 如果线程中有异常可以抛出
    try{
        //通过FutureTask对象可以获得call()方法的返回值
        Long result = futureTask.get();
        System.out.println(result);
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}
  1. 使用线程池创建线程
/**
* 1. 创建一个指定容量的线程池
* 2. 创建一个单例的线程池
* 3. 创建一个缓存型的线程池
*/

//使用固定大小的线程池
//可以理解为我们往线程池里面提交线程,但是线程池固定了大小,一次只会跑3个线程,当线程结束时,别的线程 才会进来
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//往线程池中放入6个线程
for(int i = 1; i <= 6; i++) {
    //将线程提交到线程池中
    fixedThreadPool.submit(new Runnable() {
        @Override
        public void run(){
            for (int i = 0; i <= 100; i++){
                 System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

//创建一个单例的线程池
//无论往线程池中丢入多少的数据,线程池一次只会执行一条线程
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for(int i = 1; i <= 3; i++){
    singleThreadExecutor.submit(() -> {
    for (int j = 1; j <= 100; j++){
        System.out.println(Thread.currentThread().getName() + ":" + j);
    });
}

//创建一个缓存型的线程池
//程序根据系统自身来定,能跑多少个线程就跑多少个线程
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for(int i = 1; i <= 10000; i++){
    cachedThreadPool.submit(() -> {
        for (int j = 1; j <= 100; j++){
            System.out.println(Thread.currentThread().getName() + ":" + j);
        }
    }
}

线程的分类:

  1. 主线程:一般是主方法的线程
  2. 子线程:一般情况下都是用户创建的线程
  3. 守护线程:给主线程和子线程提供底层的技术支持,如果主线程或者子线程死亡,守护线程也没有存活的价值

线程的声明周期:

  1. 创建状态:创建出Thread的实例
  2. 就绪状态:线程对象执行start()方法之后,运行状态释放系统资源也会进入
  3. 运行状态: 线程获取到系统资源
  4. 等待用户输入、线程休眠都会进入阻塞
  5. 死亡状态: 线程执行完毕、手动停止线程
//线程的生命周期
private static void test1(){
    Thread thread = new Thread(() -> {
        for (int i = 1; i <= 100; i++){
            System.out.println(i);
        }
    });
    //-------------到此是创建状态-----------------
    thread.start();
    //-------------线程进入就绪状态---------------
    //线程在系统内部获取cpu资源
    //-------------进入运行状态-------------------
    //任务执行完毕,或者手动停止任务
    //-------------进入死亡状态-------------------
}

线程的调度:

  1. 调度的策略
  1. 分时调度
  2. 抢占式调度
  3. java使用的是抢占式的方式
  1. 设置线程的优先级 setPriority(int level):
//MIN_PRIORITY = 1;最小是1
//NORM_PRIORITY = 5;默认是5
//MAX_PRIORITY = 10;最大是10

Runnable runnable = () -> {
    for(int i = 1; i <= 100; i++){
        System.out.println(Thread.currentThread().getName() + ":" + i);
    }
};

Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);

//设置线程1和线程2的优先级,线程2先跑完,1才会跑
thread1.setPriority(1);
thread2.setPriority(10);

thread1.start();
thread2.start();
  1. 线程的休眠 Thread.sleep(long time):
//这里注意的是sleep()方法是静态的方法,可以通过Thread类直接调用,不需要创建对象再调用
Runnbale runnable = () -> {
    for(int i = 1; i <= 100; i++){
        if(i == 50){
            try{
            //通过Thread类直接调用sleep方法,睡眠10秒
                Thread.sleep(10000);
            } catch(InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName + ":" + i);
    }
};

Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnbale);

thread1.start();
thread2.strat();
  1. 线程的礼让 Thread.yield():
//当前线程释放系统资源,进入就绪状态,和其他线程再次争抢资源,这里注意这次礼让只会释放cpu资源并不是直接让另一个线程进来,而是再次争抢。
Thread thread1 = new  Thread(() -> {
    for (int i = 1; i <= 100; i++) {
        if (i == 30) {
        //当线程1打印到30的时候会释放cpu资源,然后再次争抢
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + ":" + i);
    }
});

Thread thread2 = new Thread(() -> {
	for (int i = 1; i <= 100; i++) {
		System.out.println(Thread.currentThread().getName() + ":" + i);
	}
});

thread1.start();
thread2.start();
  1. 线程的加入 join():
//主线程可以实现子线程的加入(使用子线程的对象调用join()方法),子线程加入之后,不再和主线程交替执行,主线程会进入阻塞一直到子线程执行完毕

Thread thread1 = new Thread(() -> {
    for(int i = 1; i <= 100; i++){
        System.out.println(Thread.currentThread().getName() + ":" + i);
    }
});

thread1.start();

for(int i = 1; i <= 100; i ++){
    if (i == 30) {
    //当主线程执行到等于30时会让线程1加入进来,这里线程会执行完毕才会出来
        try{
            thread1.join();
        } catch (IntertupttedException e) {
            e.printStackTrace();
        }
    }
    System.out.println(Thread.currentThread().getName() + ":" + i);
}

线程锁的概念

  1. 对象互斥锁
    1. 当使用多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常
    2. 当一个线程拿到锁的时候,其他线程不能获得锁;获得锁的线程可以执行被锁住的代码;当这个线程 执行完毕这块代码后,释放锁,此时其他线程和他一起争抢锁
    3. 如何实现互斥锁:(在java中采用同步机制来实现互斥锁 )
      1. 同步方法
      2. 同步代码块
    4. 常用的锁:
      1. 对象锁:任意对象,如:Object lock = new Object();获得this
      2. 类锁:类的Class对象,如TicketsThread01.class

使用:

  1. 使用同步方法:
public class TicketsThread02 extends Thread {
	
	//线程操作的对象,一定要设置为静态的,共用的代码
	private static int ticketCount = 1000;
	
	private static int count = 0;
	
	//synchronized修饰的静态同步方法
	public static synchronized void buyTicket() {
		if (ticketCount > 0) {
			count++;
			System.out.println(Thread.currentThread().getName() + "抢了第"+ count +"张票!");
			ticketCount--;
			System.out.println("还剩" + ticketCount + "张票!");
		}
	}
	
	//run方法中调用方法
	@Override
	public void run() {
		while (true) {
			buyTicket();
		}
	}

}
  1. 同步代码块实现同步:
public class TicketsThread03 extends Thread {
	
	private static int ticketCount = 1000;
	
	private static int count = 0;
	
	//设置共用静态对象
	private static Object lock = new Object();
	
	@Override
	public void run() {
		while (true) {
		    //同步对象锁
		    //如果实现两个线程和其他的一个线程锁不同,只需要创建不同的对象放入其中
			synchronized(lock) {
				if (ticketCount > 0) {
					count++;
					System.out.println(Thread.currentThread().getName() + "抢了第"+ count +"张票!");
					ticketCount--;
					System.out.println("还剩" + ticketCount + "张票!");
				}
			}
		}
	}
}

这里注意一点:(如果我们使用Runnable这个借口就可以使用this关键词)

public class TicketsThread04 implements Runnable {
	
	private static int ticketCount = 1000;
	
	private static int count = 0;
	
	@Override
	public void run() {
		while (true) {
			synchronized(this) {
				if (ticketCount > 0) {
					count++;
					System.out.println(Thread.currentThread().getName() + "抢了第"+ count +"张票!");
					ticketCount--;
					System.out.println("还剩" + ticketCount + "张票!");
				}
			}
		}
	}

}

//在主线程中创建一个runnable的对象
public static void main(String[] args) {
    TicketsThread04 run = new TicketsThread04();
}
死锁:(由于两个线程同时需要对方的锁,导致无法释放自己的锁,所以导致程序一直这样不执行)
public static void main(String[] args) {
    Object lock1 = new Object();
    Object lock2 = new Object();
    
    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (lock1) {
                Sysyem.out.println("我是线程1,我拿到lock1");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                sychronized (lock2) {
                System.out.prinyln("我是线程1,我拿到了lock2");
                }
            }
        }
    }).start();
    
    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (lock2) {
                System.out.println("我是线程2,我拿到了lock2")try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.pirntln("我是线程2, 我拿到了lock1")}
            }
        }
    }).start();
}

线程通信:

线程的等待和通知机制

使用的方法:
1.wait():当前使用锁的线程进入阻塞状态

Thread thread1 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronized(lock) {
            for (int i = 0; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
                if (i == 50) {
                    try {
                        //使用wait()会使当前对象释放锁,并且进入阻塞状态
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

Thread thread1 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronized(lock) {
            for (int i = 0; i <= 50; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            //使用notify()会通知被该锁阻塞的可以执行了
            lock.notify();
            
            //使用notityAll()方法会让多个被阻塞的对象都可以回到就绪状态,但是第一个被锁住的对象会先执行,阻塞相当于是一个队列
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JeffHan^_^

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值