线程的创建方式:
- 继承Thread类:
- 最直接的方式,重写Thread类的run()方法
- run()方法中不定义任何逻辑,需要重写来进行定义具体的业务逻辑
- 实例化该类后,通过start()启动线程【start()方法内部会自动执行run()方法】
- 缺点:会导致不能继承其他的类,限制性较大。
- 最直接的方式,重写Thread类的run()方法
class MyThread extends Thread{
@Override
public void run(){
具体逻辑.........
}
}
public static void main(){
MyThread myThread = new MyThread();
myThread.start();
}
- 实现Runnable接口:
- 大体与Thread类的功能差不多,但是是通过接口实现的方式。
class MyThread implements Runnable{
@Overrid
public void run(){
具体逻辑......
}
}
public static void main(){
Thread t=new Thread(new MyRunnable);
t.start();
}
- 实现Callable接口
- 可以有返回值,并且可以多线程处理一份资源
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 线程执行的代码, 这里返回一个整型结果
return 1;
}
}
public static void main(String[] args) {
MyCallable task = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(task);//将callable类包装进FutureTask
Thread t = new Thread(futureTask);
t.start();
try {
Integer result = futureTask.get(); // 获取线程执行结果
System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
- 使用线程池(Excutors):
- 一种规范管理多线程的方式,避免了线程创建和销毁的开销
Class Task implement Runnable{
@Override
public void run(){
具体逻辑.............
}
}
public static void main(){
ExecutorService executor= new Executors.newFixedThreadPool(10)//设置线程池大小:10
for(int i;i<10;i++){
executor.submit(new Task());
}
executor.shutdown;
}
如何终止线程:
- 使用sleep()休眠线程,然后调用interrupt方法使线程标记为中断状态,在线程提交完此次任务后自我了断
- 使用stop()暴力停止:但是会有很大不好的后果,已被废弃
- 使用interrupt方法标记为中断状态,在run方法中判断线程状态,如果是中断则return或手动抛出异常
线程的状态:
- New:刚刚被创建,还未start(),初始状态
- Runnable:调用了start(),正在运行状态
- Blocked:正在等待锁,阻塞状态
- Waitting:正在等待其他线程完成指定动作,等待状态
- Timed_waitting:人为设置了等待时间的等待状态W
- Terminated:线程完成任务,终止状态
Sleep和Wait的区别:
- 锁处理:Sleep不释放锁,抱着锁睡觉(但会释放CPU)。而wait方法会释放锁
- 使用位置:sleep可以在任何位置使用,而wait必须在synchronzed代码块/方法中使用
- 唤醒条件:sleep睡醒后就苏醒,而wait必须要等待notify()来唤醒
- 用途:sleep用于暂停线程,而wait用于线程间的通信与协作
- Wait和Blocked的区别:
- Wait是主动暂停,用于与其他线程的写作。而Blocked是抢锁失败,进入阻塞
- Wait需要显式的唤醒,而Blocked会主动去抢锁
线程间的通信方式:
- wait()、notify()、notifyAll():
- 最基础的通信方式
- Lock()和Condition接口:
- 提供了一种比synchronzed更轻量灵活的锁机制
- volatile关键字:
- 用volatile关键字修饰的变量再被更改后会立即通知其他线程,保证变量的可见性
- Semaphore信号量:一个计数的信号量,可以控制特定资源的访问
优雅的停止线程的方式:
不应该暴力的停止(如stop)。而是应该通过逻辑控制停止
- 通过共享标志变量来控制:
- 用volatile关键字修饰一个变量,当工作线程检测到为false时停止(设置中断标志)
- 直接使用线程中断机制:
- 通过Tread.interrupt()来标识中断标志,当线程执行完当前任务则会自动中断。
如何保证线程安全:
- synchronized关键字:同步代码块或方法,确保同一时间只能有一个线程进入代码块
- volatile关键字:作用于变量,确保所有线程都能及时看到该变量的值
- 使用ReentrantLock可重入锁:实现Lock接口。比synchronized更加强大的锁机制,就是那种需要fianlly解锁的那种
- 使用JUC提供的原子类:如AtomicInterger
- 使用并发安全的集合:如ConcurrentHashMapper;CopyOnWriteArryayList;ConcurrentLinkedQueue等
Java中常用的锁:
- Synchronized内置锁:
- 是一种可重入锁,公平锁
- 当没有其他线程竞争时,会使用无锁、偏向锁、轻量级锁、重量级锁
- ReentrantLock可重入锁:
- 默认非公平锁
- 比起Synchronized更加灵活功能高级,如定时锁等待,是否公平
- ReadWriteLock读写锁:
- 允许多个读取者但是只允许一个写入者,用于读远多于写的情况
- 自旋锁(思想):线程在等待锁时会持续循环检查锁是否可用,通常用于锁等待时间很短的情况,通常用CAS替代
- 锁的公平性:
- 公平:按先后顺序来
- 不公平:按线程获取锁的CAS来
synchronized的四种量级:
- 无锁:没有任何线程持有锁
- 偏向锁:在锁内部维护一个线程ID,如果该线程再次获取锁则直接进入。当有线程竞争锁时升级为轻量级锁
- 轻量级锁:当线程轻微竞争时,其他线程需要自旋来等待锁的释放,自旋不断尝试获取锁,占用CPU
- 后期Java废弃了偏向锁,因为现代应用持续的并发都比较高,偏向锁的撤销反而浪费性能
- 重量级锁:当线程激烈竞争时,其他线程获取不到锁会直接进入阻塞状态。



