java多线程

java多线程

1. 线程的创建方式

1.1. 继承Thread类

Thread类的本质是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()方法。start()方法是一个native方法,它将启动一个线程并执行run方法。

//创建一个类继承Thread类
public class MyThread extends Thread { 
 public void run() { 
 System.out.println("MyThread.run()"); 
 } 
} 
//创建MyThread类并启动线程
MyThread myThread1 = new MyThread(); 
myThread1.start();

1.2. 实现Runnable接口

如果自己的类已经继承了其他的类,这时就无法通过继承Thread来创建线程,此时就可以实现一个Runnable接口。

public class MyThread extends OtherClass implements Runnable { 
 	public void run() { 
 		System.out.println("MyThread.run()"); 
 	} 
}
//启动 MyThread,需要首先实例化一个 Thread,并传入自己的 MyThread 实例:
MyThread myThread = new MyThread(); 
Thread thread = new Thread(myThread); 
thread.start(); 
//事实上,当传入一个 Runnable target 参数给 Thread 后,Thread 的 run()方法就会调用target.run()
public void run() { 
 	if (target != null) { 
 		target.run(); 
 	} 
}

1.3. 实现Callable接口

有返回值的任务必须实现Callable接口,类似的,无返回值的任务则可以实现Runable接口或者继承Thread。执行Callable任务后,可以获取一个Future对象,在该对象上调用get就可以获取到Callable任务返回的结果。

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        //求1 -100 之间的和
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

//创建对象
MyCallable myCallable = new MyCallable();
//创建FutureTask对象(用于管理多线程运行结果)
FutureTask<Integer> futureTask = new FutureTask<>(myCallable)
//创建线程对象
Thread t1 = new Thread(futureTask);
//启动线程
t1.start();
Integer result = futureTask.get();
System.out.println(result);

用返回值的方式,再结合ExecutorService就可以实现有返回结构的多线程

//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List<Future> list = new ArrayList<Future>(); 
for (int i = 0; i < taskSize; i++) { 
	Callable c = new MyCallable(i + " "); 
	// 执行任务并获取 Future 对象
	Future f = pool.submit(c); 
	list.add(f); 
} 
// 关闭线程池
pool.shutdown(); 
// 获取所有并发任务的运行结果
for (Future f : list) { 
	// 从 Future 对象上获取任务的返回值,并输出到控制台
	System.out.println("res:" + f.get().toString()); 
}

1.4. 基于线程池的方式

线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候就销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。使用线程池就不需要我们去手动创建线程了,需要使用线程的时候就到线程池里面去拿,用完再放回到线程池中,线程池中的核心线程是长期存在的,即使我们没有使用线程。在线程池创建的时候就已经创建了线程。

 // 创建线程池
 ExecutorService threadPool = Executors.newFixedThreadPool(10);
 while(true) {
 	threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
 		@Override
 		public void run() {
 			System.out.println(Thread.currentThread().getName() + " is running ..");
 		try {
 			Thread.sleep(3000);
 		} catch (InterruptedException e) {
 			e.printStackTrace();
 		}
 	  }
   });
 } 

2. 4种线程池

java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池的接口是ExecutorService

在这里插入图片描述

2.1. newCachedThreadPool

创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可以提高程序的性能。调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有60秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。

 ExecutorService executorService = Executors.newCachedThreadPool();

2.2. newFixedThreadPool

创建一个可重用的固定线程数的线程,以共享的无界队列方式来运行这些线程。在任意点,大多数线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。

 ExecutorService executorService = Executors.newFixedThreadPool();

2.3. newScheduledThreadPool

创建一个线程,它可以安排在给定延迟后运行命令或定期地执行。

ScheduledExecutorService scheduledThreadPool=Executors.newScheduledThreadPool(3); 
 	scheduledThreadPool.schedule(newRunnable(){ 
 		@Override 
 		public void run() {
 			System.out.println("延迟三秒");
 		}
 	}, 3, TimeUnit.SECONDS);
 scheduledThreadPool.scheduleAtFixedRate(newRunnable(){ 
	@Override 
 	public void run() {
 		System.out.println("延迟 1 秒后每三秒执行一次");
 	}
 },1,3,TimeUnit.SECONDS);

2.4. newSingleThreadExecutor

创建一个只有一个线程的线程池,在个线程池在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去!

 ExecutorService executorService = Executors.newSingleThreadExecutor();

3. 线程的生命周期

当线程被创建兵启动以后,它既不是已启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中他要经过:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直’霸占’CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行和阻塞之间切换。

3.1. 新建状态(New)

当程序使用一个new关键字创建一了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值

3.2. 就绪状态(Runable)

当线程对象调用了start()方法之后,该线程就处于就绪的状态。java虚拟机会为其创建方法调用栈和

程序计数器,等待调度运行。

3.3. 运行状态(Running)

如果处于就绪状态的线程获得了CPU的执行权,就开始执行run()方法的线程执行体,则该线程处于运行状态。

3.4. 阻塞状态(Blocked)

阻塞状态是指线程因为某种原因放弃了CPU的使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行(runable)状态,才有机会再次获得 cpu timeslice ,状态转为运行状态。

阻塞的情况分三种:

等待阻塞(o.wait -> 等待队列

​ 运行(Running)的线程执行o.wait()方法,JVM会把该线程放入等待队列中。

同步阻塞(lock -> 锁池)

​ 运行(Running)的线程在获取对象的同步锁时,若同步锁杯别的线程占用,则JVM会把线程放入锁池(lock pool)中。

其他阻塞(sleep / join)

​ 运行(Running)的线程执行Thread.sleep() 或 t.join()方法,或者出现 I/O 请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(Running)状态。

在这里插入图片描述

4. sleep 和 wait 的区别

1.对于sleep()方法,它是属于Thread类中的,而wait()方法,则是属于Object类中的

2.sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是它的监控状态依然保持着,当指定的时间到了有会自动恢复运行状态。

3.在调用sleep() 方法的过程中,线程不会释放对象锁。

4.在调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

5. startrun 区别

1.**start()**方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕, 可以直接继续执行下面的代码。

2.通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。

3.方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后CPU 再调度其它线程。

  • 26
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值