创建线程的方法
- 需要从 Java.lang.Thread 类派生一个新的线程类,重载它的 run()方法;
public class ThreadTest extends Thread{
public void run(){
for(int i=0;i<20;i++){
System.out.println("this is run way"+i);
}
}
public static void main(String[] args) {
ThreadTest threadTest=new ThreadTest();
threadTest.start();
}
}
- 实现 Runnalbe 接口,重载 Runnalbe 接口中的 run()方法。
public class RunnableTest implements Runnable {
public void run(){
for(int i=0;i<20;i++){
System.out.println("this is run way"+i);
}
}
public static void main(String[] args) {
RunnableTest runnableTest=new RunnableTest();
new Thread(runnableTest).start();
}
}
- 通过 Callable 和 Future 创建线程
有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务返回的 Object 了,再结合线程池接口 ExecutorService 就可以实现传说中有返回结果的多线程了。
import java.util.concurrent.*;
public class CallableTest implements Callable<Boolean> {
public Boolean call(){
for(int i=0;i<20;i++){
System.out.println("this is run way"+i);
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableTest ct1=new CallableTest();
CallableTest ct2=new CallableTest();
//创建执行服务
ExecutorService ser= Executors.newFixedThreadPool(2);
//提交执行
Future<Boolean> r1=ser.submit(ct1);
Future<Boolean> r2=ser.submit(ct2);
//获取结果
boolean res1=r1.get();
boolean res2=r2.get();
//关闭结果
ser.shutdown();
}
}
- 应用程序可以使用 Executor 框架来创建线程池
线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService service= Executors.newFixedThreadPool(10);
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.shutdown();
}
}
class MyThread extends Thread{
public void run(){
System.out.println(Thread.currentThread().getName());
}
}
线程的六种状态
- NEW: 新建状态,线程对象已经创建,但尚未启动
- RUNNABLE:就绪状态,可运行状态,调用了线程的start方法,已经在java虚拟机中执行,等待获取操作系统资源如CPU,操作系统调度运行。
- BLOCKED:堵塞状态。线程等待锁的状态,等待获取锁进入同步块/方法或调用wait后重新进入需要竞争锁
- WAITING:等待状态。等待另一个线程以执行特定的操作。调用以下方法进入等待状态。 Object.wait(), Thread.join(),LockSupport.park
- TIMED_WAITING: 线程等待一段时间。调用带参数的Thread.sleep, objct.wait,Thread.join,LockSupport.parkNanos,LockSupport.parkUntil
- TERMINATED:进程结束状态。
锁池和等待池
所有需要竞争同步锁的线程都会放在锁池中,当前面的线程释放同步锁后锁池中的线程去竞争同步锁
当调用wait方法时,线程会放到等待池中,等待池中的线程不回去竞争同步锁,只有调用了notify或者notifyall后等待池中的线程才会进入到锁池中
sleep() 和 wait()
-
sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,到时后会自动恢复。调用 sleep 不会释放对象锁。
-
wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待池,只有针对此对象发出 notify方法(或 notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
sleep()和yield()有什么区别?
- sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
- 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
- sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
stop()和 suspend()方法不推荐使用
-
stop这个方法将终止所有未结束的方法,包括run方法。如果在同步块执行一半时,stop来了,后面还没执行完呢,锁没了,线程退出了,别的线程又可以操作你的数据了,所以就是线程不安全了
-
suspend会导致死锁,因为挂起后,是不释放锁的,别人也就阻塞着,如果没人唤醒,那就一直死锁
ThreadLocal
- ThreadLocal是Java中所提供的线程本地存储机制,可以利⽤该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意⽅法中获取缓存的数据
- ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在⼀个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
- 如果在线程池中使⽤ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使⽤完之后,应该要把设置的key,value,也就是Entry对象进⾏回收,但线程池中的线程不会回收,⽽线程对象是通过强引⽤指向ThreadLocalMap,ThreadLocalMap也是通过强引⽤指向Entry对象,线程不被回收,Entry对象也就不会被回收,从⽽出现内存泄漏,解决办法是,在使⽤了ThreadLocal对象之后,⼿动调⽤ThreadLocal的remove⽅法,⼿动清除Entry对象
- ThreadLocal经典的应⽤场景就是连接管理(⼀个线程持有⼀个连接,该连接对象可以在不同的⽅法之间进⾏传递,线程之间不共享同⼀个连接)
https://www.cnblogs.com/cqqfboy/p/14484263.html
JVM中线程的实现方式
线程的实现其实是有三种方式的:
-
使用内核线程实现(1:1实现);
-
使用用户线程实现(1:N)实现;
-
使用用户线程加轻量级进程混合实现;
线程用户态和内核态概念
为什么会有用户态和内核态
由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 :用户态 和 内核态
用户态和内核态的区别
内核态:CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
用户态:只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取
本质区别就是访问内存的限制
内核线程实现
使用内核线程实现的方式被称为1:1实现。内核线程(Kernel Levvel Thread,KLT)就是直接由操作系统内核支持的线程,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。
其实程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常所讲的线程。这种轻量级进程与内存线程之间1:1的关系称为一对一的线程模型。
轻量级进程也具有它的局限性:首先,由于是基于内核线程实现的,所以各种线程操作(创建、析构及同步),都需要进行系统调用。系统调用就要在用户态和内核态中来回切换。其次,每个轻量级进程都需要一个内核线程的支持,因此需要消耗一定的内核资源,所以一个系统支持轻量级进程的数量是有限的。
用户线程实现
使用用户线程实现的方式被称为1:N实现。广义上来讲,一个线程只要不是内核线程,都可以任务是用户线程(User Threa,UT)的一种。从定义上来看轻量级进程不是内核线程也就是属于用户线程,但是它始终是建立在内核之上的,所以效率会受到限制,并不具备用户线程的优点。
用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核帮助。如果程序实现得当,不需要切换内核态,因此操作可以是非常快且低消耗的,也能够支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。
这种进程与用户线程之间1:N的关系称为一对多的线程模型。
用户线程的速度快低消耗等优势在于不需要系统内核支援,但是劣势也在于没有内核的支援,所有的线程操作都需要由用户程序自己去处理。这样就会导致线程的一些问题处理起来就很困难,甚至有些是不可能实现的。
混合实现
线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式,被称为N:M实现。
用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来完成,大大降低了整个进程被完全阻塞的风险。
如何停止一个正在运行的线程
1、使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
2、使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的
方法。
3、使用interrupt方法中断线程。
如何使线程优雅的退出
停止一个线程的最佳方法是让它执行完毕,没有办法立即停止一个线程,但你可以控制何时或什么条件下让他执行完毕。通过条件变量控制线程的执行,线程内部检查变量状态,外部改变变量值可控制停止执行。为保证线程间的即时通信,需要使用使用volatile关键字或锁,确保读线程与写线程间变量状态一致。