本文共 6696 字,大约阅读时间需要 22 分钟。
.exe
文件java.lang.Thread
代表线程,任何线程都是Thread类的子类自定义Thread类,重写run方法。然后创建该类对象,来调用start方法(不是直接调用run方法)
// subThread是 Thread的子类Thread t = new subThread();// JVM会自动调用run方法t.start();
自定义类实现Runnable
接口,并且重写run方法。再创建该类的对象作为实参来构造Thread类型对象,然后使用Thread类对象调用 run方法
虽然是Thread类对象调用run方法,其实调用的是Runnable接口实现类中重写的run方法
new Thread(new Runnable() { // 匿名内部类中,重写run方法 @Override public void run() { System.out.println("Runnable接口实现线程的创建"); }}).start();// 上次代码,使用了匿名对象和匿名内部内
使用 lambda
: (参数列表)->{方法体}
new Thread(()->System.out.println("Runnable接口实现线程的创建");).start();
方法声明 | 功能介绍 |
---|---|
Thread() | 无参构造 |
Thread(String name) | 根据参数指定线程名,来构造对象 |
Thread(Runnable target) | 实现Runnable接口,构造对象 |
Thread(Runnable target, String name) | 根据参数,指定引用和名称来构造对象 |
void run() | 1. 若使用了Runnable引用构造了线程对象,调用run方法时最终调用接口中的重写的run2. 若没有使用Runnable接口引用构造线程对象,调用该方法时,啥也不做 |
void start() | 用于另外启动一个线程,Java虚拟机会自动调用该线程的run方法 |
Thread类中的 run方法测试
Thread thread = new Thread();thread.run();
run方法源码分析:
@Overridepublic void run() { if (target != null) { target.run(); }}
从上面的Java源码源码中我们可以看到,如果target 成员变量=null时,将不执行任何方法。
那这个target?怎么判断呢?看构造方法
Thread(Runnable target)
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0);}
Thread()
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0);}
其实这个target 就是Runnable接口引用,底层这个Runnable接口引用,会赋值给 target变量。所以,在构造方法时,就会确定target是否为空
步骤 | 说明 |
---|---|
new Thread() | 创建线程(并没有执行) |
start() | 让JVM执行 run方法,激活线程,进入就绪状态 |
线程调度器 | 使用线程调度器调用该线程,线程进入运行状态。新线程开启 |
运行状态的3种情况:
方法声明 | 功能介绍 |
---|---|
long getId() | 获取当前线程的编号 |
String getName() | 获取当前线程的名称 |
void setName(String name) | 修改线程名 |
static Thread currentThread() | 获取当前正在执行线程的引用 |
方法声明 | 功能介绍 |
---|---|
static void yield() | 让当前线程让出CPU,进入就绪状态 |
static void sleep(times) | 让线程,进入阻塞状态,并且休眠 times毫秒 1. 其他线程可能会打断阻塞状态,则发生InterruptedException异常 2. 所以使用sleep方法,要使用try/catch 3.(因为,子类重写的方法不能抛出比父类更大的异常,只能使用try/catch,不能使用throws) |
int getPriority() | 获取当前线程的优先级1. 最大优先级:102. 最小优先级:1 |
void setPriority( int newPriority) | 修改线程的优先级优先级越高的线程不一定先执行,但该线程获取到时间片的机会会更多一些 |
void join() | 等待该线程终止 |
void join(long millis) | 参数指定等待的额毫秒数(等待结束后,直接和子线程抢夺时间片运行) |
boolean isDaemon() | 判断是否为守护线程 |
void setDaemon (boolean on ) | 设置线程为守护线程 |
注意,优先级越高的线程不一定先打印。只是所,优先级越高CPU分配给他的时间片几率就越高
守护线程:
必须在主线程启动前,将子线程设置为守护线程
// 创建线程Thread t = new Thread();// 必须在start前,设为守护线程t.setDaemon(true);t.start();
守护线程,在主线程结束后。不管守护线程是否完成执行任务,都要立马结束掉线程
小应用,主程序让新线程执行5s后,停掉子线程
// flag私有属性,和setterboolean flag = true;public void setFlag(boolean flag) { this.flag = flag;}public void method(){ new Thread(new Runnable() { @Override public void run() { while (flag){ System.out.println("子线程正在执行..."); try { // 让当前线程(main主线程)睡眠1秒, 效果就是1s打印一次 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start();}public static void main(String[] args) { Thread01 t = new Thread01(); t.method(); try { // 让当前线程(main主线程)睡眠5秒 Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } t.setFlag(false); System.out.println("子线程结束");}
IDEA快捷键:Ctrl + Alt + T
使用synchronized关键字
来实现线程的同步 (也叫对象锁机制),从而保证线程执行的原子性
synchronized(类类型的引用) { // TODO 编写所有需要锁定的代码;}
全部代码的锁定
synchronized(this) { // TODO 整个方法体的代码}
如果使用Runnable接口实现类做参数,实现的线程。那么,this就是Runnable接口的实现类引用
synchronized修饰类:
public synchronized StringBuffer append(char c)
注意:
如果synchronized的参数,不是同一个对象的引用,那么会锁不住。就可能出现线程安全问题
Collections.synchronizedList()
和Collections.synchronizedMap()
等方法实现安全。 线程-1,执行的代码:
public void run(){ synchronized(a){ //持有对象锁a,等待对象锁b synchronized(b){ // TODO 编写锁定的代码; } }}
线程-2,执行的代码:
public void run(){ synchronized(b){ //持有对象锁a,等待对象锁b synchronized(a){ // TODO 编写锁定的代码; } }}
两个线程竞争了不可剥夺的资源。比如:上面2个线程,同时执行,线程1需要锁b,才能释放锁a。而线程2需要锁a,才能释放锁b。这样相互就形成了死锁,永远都打不开
结论:
在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!
java.util.concurrent.locks.Lock
接口是控制多个线程,对共享资源进行访问的工具ReentrantLock
类,该类拥有和 synchronized 相同的并发性。在线程安全的控制中,经常使用 ReentrantLock类 显式示加锁和释放锁方法声明 | 功能介绍 |
---|---|
ReentrantLock() | 无参构造,构造对象 |
void lock() | 获取锁 |
void unlock() | 释放锁 |
举例:
ReentrantLock rl = new ReentrantLock();public void run(){ // 加锁 rl.lock(); // TODO 加锁的代码部分 // 释放锁 rl.unlock();}
方法声明 | 功能介绍 |
---|---|
void wait() | 将线程进入阻塞状态 |
void wait( long timeout ) | 指定参数的时间过去后,接触阻塞状态 |
void notify() | 将进入阻塞状态的线程唤醒 |
void notifyAll() | 用于唤醒所有进入阻塞的线程 |
一个客户端就要创建一个新的线程
当大量客户端连接服务器,就要不断地销毁和创建线程,这严重影响到了服务器的性能
所以引入,线程池这一概念。任务直接提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务
后,它就在内部找有无空闲的线程,再把任务交给内部某个空闲的线程。任务是提交给整个线程 池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。从Java5开始提供了线程池的相关类和接口:
Executors类
是个工具类和线程池的工厂类
常用方法如下
方法声明 | 功能介绍 |
---|---|
static ExecutorService newCachedThreadPool() | 创建一个可根据需要创建新线程的线程池 |
static ExecutorService newFixedThreadPool( int nThreads ) | 创建一个可重用固定线程数的线程池 |
static ExecutorService newSingleThreadExecutor() | 创建一个只有一个线程的线程池 |
) | 创建一个只有一个线程的线程池 |
ExecutorService接口
是真正的线程池接口,主要实现类是ThreadPoolExecutor
常用方法如下
方法声明 | 功能介绍 |
---|---|
void execute(Runnable command) | 执行任务和命令 |
Future submit(Callable task) | 执行任务和命令 |
void shutdown() | 启动有序关闭 |
转载地址:http://jhuen.baihongyu.com/