本帖最后由 小石姐姐 于 2018-8-9 11:30 编辑
数据结构、集合、异常、多线程
四 线程
1 多线程
并发:指两个或多个事件在同一时间段内发生(一个人吃两馒头);
并行 指两个或多个事件同时发生(两个人吃两个馒头);
1、线程与进程
内存 ram 程序进入到内存中的程程序叫进程;
进程进入cpu的通道叫线程
2、线程
线程: 是进程内的一个独立执行单元 (一条代码执行路径)
3、线程的调度方式
1. 分时调度: 所有线程轮流使用CPU, 平分占用CPU的时间
2. 抢占式调度: 优先让优先级高的线程使用CPU; 如果优先级相同, 则随机选择一个线程执行
Java使用的是"抢占式"调度
4、多线程的随机性
因为Java的线程调度室抢占式的,所以cpu想要执行哪一个线程是随机,不能人为干预;
5、多线程的内存
在多线程情况下,每个线程都有各自的栈内存,所以线程各自的方法调用,进的是各自线程的栈中,而堆是所有线程共用的;
2 创建多线程的方式
1、第一种方式: 继承Thread类
实现多线程的第一种方式:
1. 定义类, 继承Thread类
2. 重写run()方法, run方法内部是线程要执行的任务
3. 创建Thread子类的对象, 调用start()方法启动线程
java.lang.Thread类: 表示线程. 实现了Runnable接口
void start(): 启动线程, 即让线程开始执行run()方法中的代码
代码:
[Java] 纯文本查看 复制代码 public class HomeworkThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("CPU正在帮你写数学作业: 进度:" + i + "%");
}
}
}
public class Test {
public static void main(String[] args) {
HomeworkThread thread = new HomeworkThread();
thread.start();
注意:
必须调用 start() 方法来开启线程, 不能直接调用 run() 方法, 调用 run() 会变成单线程
同一个线程对象, 不能多次调用 start() 方法
Java是抢占式调度, 不同线程的代码, 执行顺序是随机的
Thread的常用方法:
[Java] 纯文本查看 复制代码 Java.lang.Thread类:表示线程,实现了Runnable接口
[font=微软雅黑][size=3]构造方法[/size][/font]
[mw_shl_code=java,true]Thread Thread(): 创建Thead对象
Thread Thread(String threadName): 创建Thead对象并指定线程名
Thread Thread(Runnable target): 通过Runnable对象创建Thread对象
Thread Thread(Runnable target, String threadName): 通过Runnable对象创建对象并指定线程名
成员方法
[Java] 纯文本查看 复制代码 oid run(): 用于让子类重写, 表示该线程要执行的任务.不能直接调用
void start(): 启动线程, 即让线程开始执行run()方法中的代码
String getName(): 获取线程的名称
void setName(String name): 设置线程名称
静态方法
[Java] 纯文本查看 复制代码 static Thread currentThread(): 返回对当前正在执行的线程对象的引用
static void sleep(long millis)// 让所在线程睡眠指定的"毫秒
注意:由于Thread.sleep(xxx)方法会抛出一个异常,所以这里我们要使用try{...}catch(){...}方法来接受异常
2、第二种方式:
1、 定义类, 实现Runnable接口 ;
2、重写 run() 方法 ;
3、创建Runnable实现类对象 ;创建Thread类对象, 在构造方法中传入Runnable实现类对象 ;
4、通过Thread对象调用start()方法启动线程;
Thread和Runnable的区别:
实现Runnable的好处:
①避免单继承的局限性
②增强了程序的扩展性, 降低了程序的耦合性(解耦) 线程是Thread, 任务是Runnable实现类对象. 相当于将线程和任务分离,使我们使用时更加灵活
3 匿名内部类方式创建线程:
格式:
new 父类/接口( ){
重复父类/接口中的方法
}.start( );
代码:
[Java] 纯文本查看 复制代码 public class Test {
public static void main(String[] args) {
new Thread("继承Thread类") {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}.start();
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
};
new Thread(r, "可以设置名字").start();
更简单的方式
[Java] 纯文本查看 复制代码 new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}, "实现Runnable接口").start();
}
}
4 线程安全问题
4.1 问题发生场景:
多个线程操作共享资源
4.2 问题发生原因:
JVM是抢占式调度, CPU在每个线程之间切换是随机的, 代码执行到什么位置是不确定的 在操作共享资源时, 由于一个线程还没有执行完, 另一个线程就来操作, 就会出现问题
4.3 如何解决:
在操作共享资源时, 让线程一个一个来执行, 不要并发操作共享变量, 就可以解决问题
4.4 同步的原理:
线程进入同步代码块前, 会争夺锁对象, 只有一个线程会抢到锁对象,
进入同步代码块的线程, 会持有锁对象, 并执行同步代码块中的代码
此时同步代码块外的线程, 处于阻塞状态, 只能等待
当同步代码块内的线程执行完代码块, 会离开同步代码块, 并归还锁对象给同步代码块
等在同步代码块外的其他线程就可以继续争夺锁对象
解决多线程操作共享数据的安全问题的3种方式:
4.4.1 同步代码块:
而我们要做的就是使用synchronized ( 多个线程共享的同一个锁对象 ){
将操作共享数据的代码全部放在同步代码块中
}
4.4.2 同步方法:
格式:
同步方法: 使用 synchronized 关键字修饰的方法, 具有默认的锁对象
非静态同步方法的锁对象: this
// 非静态同步方法
public synchronized void method(){
// 可能会产生线程安全问题的代码
}
静态同步方法:
public static synchronized void method(){
// 可能会产生线程安全问题的代码
}
静态同步方法的锁对象: 当前类的字节码对象
获取一个类的字节码对象的3种方式:
对象.getClass()
类名.class
Class.forName("类的全路径");
4.4.3 Lock锁机制
同步代码块: 使用 synchronized 关键字修饰的代码块, 并传入一个当作锁的对象
格式: synchronized (锁对象) { // 操作共享数据的代码 }
注意: 锁对象可以是任意类型的一个对象 锁对象必须是被多个线程共享的唯一对象 锁对象的作用: 只让一个线程在同步代码块中执行
举例:
java.util.concurrent.locks.Lock接口: JDK 5 新增的Lock接口
// 成员方法
[Java] 纯文本查看 复制代码 void lock(): 获取锁
void unlock(): 释放锁
java.util.concurrent.locks.ReentrantLock类: Lock的实现类
使用方式:
[Java] 纯文本查看 复制代码 public class RunnableImpl implements Runnable {
Lock lock = new ReentrantLock();
@Override public void run() {
try {
} finally {
lock.unlock();
}
}
} 4.5 线程的状态概述
1、Object类中关于线程的方法:
java.lang.Object类:
// 成员方法 (只能通过"锁对象"调用)
[Java] 纯文本查看 复制代码 void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
void wait(): 让当前线程处于无限等待状态
void wait(long timeout): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态 void wait(long timeout, int nanos): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
2、线程的生命周期中的6种状态:
线程的生命周期中, 可以出现有6种状态:
①NEW 新建
线程被创建, 但没有调用 start() 启动
② RUNNABLE 可运行
调用 start()方法后已启动, 但可能正在执行 run() 方法的代码, 也可能正在等待CPU的调度
③ BLOCKED 阻塞
线程试图获取锁, 但此时锁被其他线程持有
④WAITING 无限等待
通过锁对象调用无参的 wait() 进入此状态. 等待其他线程通过锁对象执行 notify() 或 notifyAll() 才能结束这个状态
⑤TIMED_WAITING 计时等待
如通过锁对象调用有参的 wait(long millis) 或 sleep(long millis), 则进入此状态. 直到时间结束之前被其他线程通过锁对象执行notify()或notifyAll()唤醒, 或时间结束自动唤醒
⑥ TERMINATED 终止
run()方法结束(执行结束, 或内部出现异常), 则进入此状态
|
|