多线程
概念
-
进程与线程的区别:
区别 进程 线程 根本区别 作为资源分配的单位 调度和执行的单位 开销 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销 线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器,线程切换开销较小 所处环境 在操作系统中可以同时运行多个程序 在同个程序中可以同时执行多个顺序流 分配内存 系统在运行时回为每个进程分配不同的内存空间 除CPU外,不会为线程分配内存,线程组只能共享资源 包含关系 没有线程的进程可以看做是单线程的 线程是进程的一部分 -
线程的优缺点:
- 优点:资源利用率高;程序设计在某些情况下更简单;程序响应更快
- 缺点:一般情况下多线程程序设计更复杂;上下文切换的的开销
创建线程的三种方式:
-
继承 Thread 类,重写 run() 方法 + start() 方法开启线程
-
实现 Runnable 接口,重写 run() 方法 + new Thread(Runnable target).strat() 方法开启线程
- 优点:1)避免单继承的局限性; 2)实现资源共享
-
实现 Callable 接口,重写 call() 方法
-
优点:1)可以有返回值; 2)可以抛出异常
-
缺点:使用复杂
//1.创建执行服务 创建一个线程池,用来管理线程的开启和结束 ExecutorService server = Executors.newFixedThreadPool(1); //2.提交执行 Future<E> result = server.submit(Callable target); //3.获取返回值 Object obj = result.get();
-
-
内部类与多线程:
public class ThreadDemo { //静态内部类 static class Inner implements Runnable { @Override public void run() { for (int i = 1; i <= 20; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("一遍玩手机"); } } } public static void main(String[] args) { //局部内部类 class LocalInner implements Runnable { @Override public void run() { for (int i = 1; i <= 20; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("一遍吃饭"); } } } new Thread(new Inner()).start(); new Thread(new LocalInner()).start(); //匿名内部类 new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 20; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("一遍学习"); } } }).start(); //Lambda 表达式 new Thread(() -> { for (int i = 1; i <= 20; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("一遍学习"); } }).start(); } }
用户线程与守护线程:
- 线程:用户线程User;守护线程 Deamon
- 如果程序中没有用户线程的存在了,所有的守护线程自动结束,只要还有一个用户线程,就不会强制结束守护线程
- 默认所有的线程为用户线程
- 经典的守护线程案例,垃圾回收机制
- 设置守护线程: thread.setDaemon(true);
- 要求: 在线程开启start()方法之前使用
线程的五种状态:
- 新生状态:创建线程 new Thread();
- 就绪状态:就绪队列中的线程,等待cpu的调度:调用start(); | yield(); | 线程切换 | 阻塞解除
- 运行状态:cpu把时间片分配给哪一个线程,这个线程才会运行
- 阻塞状态:sleep(); | join(); | wait(); | IO操作(Scanner); … …
- 结束状态:结束,死亡:正常执行完毕 | 调用destroy();stop(); | 通过添加标识判断
- sleep():注意不要在任何同步环境下使用,如果在同步环境下使用了,在休眠途中,是不会释放对象锁的
- yield():把cpu的资源让出,让cpu重新分配,给了其他线程能够获取资源的机会
- join():合并线程 ,插队线程;join(ms):等待指定毫秒数, 到点了,就阻塞等待了
- 线程的优先级:
- 1~10 ,默认一个线程的优先级: 5
- MAX_PRIORITY 最大优先级:10
- MIN_PRIORITY 最小优先级:1
- NORM_PRIORITY 默认:5
- setPriority():设置线程优先级
- getPriority():获取线程优先级
- 优先级只能控制概率问题,不能角色到底谁先执行
- 获取线程状态:
- Thread.State getState():返回该线程的状态(枚举类型)
- NEW:新生状态
- RUNNABLE:运行和就绪都是这个状态
- TIMED_WAITING:阻塞状态
- TERMINATED:终止
线程同步:
控制线程安全问题
- 前提:当多个线程同时操作同一份资源的时候,才有可能出现线程不安全问题
- 办法:使用同步锁 --> synchronized
- 使用方法:
- 同步方法:在方法上使用synchronized关键字(较简单,但是范围太大,效率低)
- 成员方法
- 静态方法
- 同步块:synchronized(this|类|资源){ }
- this: 锁对象
- 类名.calss:只有一个,不变的对象内容,在类第一次加载进内存就存在了
- 资源: 成员属性
- 注意:
- 锁谁:this|资源|类|方法?同步一定要锁同步不变的内容(自定义类的对象地址),变的内容锁不住
- 锁的多大的范围:的范围太大了,效率太低,锁的范围太小了,锁不住
- 同步方法:在方法上使用synchronized关键字(较简单,但是范围太大,效率低)