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. start 与 run 区别
1.**start()**方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕, 可以直接继续执行下面的代码。
2.通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。
3.方法 run()称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后CPU 再调度其它线程。