JAVA 线程实现/创建方式
继承 Thread 类
Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。 启动线程的唯一方法就是通过 Thread 类的 start()实例方法。 start()方法是一个 native 方法,它将启动一个新线程,并执行 run()方法。
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
myThread1.start();
实现 Runnable 接口
如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个Runnable 接口。
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
实现callbale
有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务返回的 Object 了。
public class callableTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(10);
//创建一个Callable,3秒后返回String类型
Callable myCallable = new Callable() {
@Override
public String call() throws Exception {
// Thread.sleep(3000);
System.out.println("calld方法执行了");
return "call方法返回值";
}
};
List list = new ArrayList();
for (int i = 0; i < 10; i++) {
Future future = executor.submit(myCallable);
list.add(future);
}
线程的状态
线程生命周期
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
新建状态(NEW)
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值。
就绪状态(RUNNABLE)
当线程对象调用了 start()方法之后,该线程处于就绪状态。 Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
运行状态(RUNNING)
如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。
阻塞状态(BLOCKED)
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。
直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
等待阻塞(o.wait->等待对列) :
运行(running)的线程执行 o.wait()方法, JVM 会把该线程放入等待队列(waitting queue)中。
同步阻塞(lock->锁池)
运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
其他阻塞(sleep/join)
运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、 join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。
线程死亡(DEAD)
线程会以下面三种方式结束,结束后就是死亡状态。
1.正常结束
run()或 call()方法执行完成,线程正常结束。
2.异常结束
线程抛出一个未捕获的 Exception 或 Error。
3.调用 stop
直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
锁的升级
如果有一个对象在被多个线程同时竞争,那么判断对象是否有锁,如果有锁,那么会先支持偏向锁,就是说当前已经获得锁的线程会优先拿到锁(markword区记录了偏向线程id)。那么拿不到锁的线程,就会升级锁,变成CAS synchronized乐观锁,会进行一段时间的循环自旋不断尝试获取锁,当自旋到一定次数后,会再次升级成synchronized重量级锁。
synchronized
锁方法会锁住this,锁静态方法会锁住class对象.锁代码块可以指定任意对象作为锁.
同步代码块可能会涉及到一个重入过程,synchronized不会说因为重入去不断重复获取锁释放锁的过程,而是用mointer每次重入去做一个计数器加一操作,在释放锁的过程中也会逐步将计算器清零。然后让其他线程从block阻塞状态变成runnable状态去竞争这个锁。
synchronized和reentranLock的区别
synchronized不用手动编程,他是一个jvm关键字,我也不用关心他锁释放的一个过程,直接用就行了,而reentrantlock他是一个类,需要手动lock,配合try catch finally中去做一个锁释放操作
线程池
线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 ExecutorService。
一个线程池建立后,如果没有预加载任务,他一开始的核心线程数为0,当一个新任务被提交时,会建立一个核心线程去执行任务,如果一直来任务,而先前建立的核心线程都在忙,那么就会一直建立核心线程直到到达最大核心线程数。
但核心线程数最大,而且都在执行任务时,后来的任务会被放到blockingqueue(阻塞队列里),如果阻塞队列也满了,就会去建立新线程,此时的线程叫非核心线程,当整个线程池的线程数达到最大,他也有一个max access时,会触发拒绝策略。
拒绝策略
AbortPolicy 中止策略
会直接抛出异常来执行中止任务执行拒绝
DiscardPolicy 抛弃策略
他会丢弃不执行多余的任务来执行拒绝
DIscardOldestPolicy
会丢弃最早你未执行的任务
callrunpolicy
公平锁和非公平锁的区别
在多线程环境下
- 公平锁一般指代 先到达临界区的线程一定比后到临界区的线程 优先拿到锁
- 非公平锁则指代 先到达临界区的线程也不一定比后到临界区的线程 优先拿到锁