黑马程序员技术交流社区
标题: [就业班 2018.11.19] 线程 同步 线程间通信 [打印本页]
作者: Java-yao 时间: 2018-11-20 15:26
标题: [就业班 2018.11.19] 线程 同步 线程间通信
本帖最后由 Java-yao 于 2018-11-26 15:09 编辑
[就业班 2018.11.19]
线程 同步 线程间通信
1. Java中多线程运行原理
随机性:抢占式
CPU执行哪个线程是随机的, 不能人为干预
Java线程调度是抢占式的, 多个线程互相抢夺CPU的执行权
内存:"栈"是每个线程各自的, "堆"是所有线程共用的
多线程情况下, 每个线程都有各自的栈内存
每个线程各自的方法调用, 进的是各自线程的栈
2. 创建多线程程序
java.lang.Thread类: 表示线程. 实现了Runnable接口
// 构造方法
Thread(): 创建Thead对象
Thread(String threadName): 创建Thead对象并指定线程名
Thread(Runnable target): 通过Runnable对象创建Thread对象
Thread(Runnable target, String threadName): 通过Runnable对象创建对象并指定线程名
// 成员方法
void run(): 用于让子类重写, 表示该线程要执行的任务.不能直接调用
void start(): 启动线程, 即让线程开始执行run()方法中的代码
String getName(): 获取线程的名称
void setName(String name): 设置线程名称
// 静态方法
static Thread currentThread(): 返回对当前正在执行的线程对象的引用
static void sleep(long millis): 让所在线程睡眠指定的毫秒
第一种方式: 继承Thread类
1. 定义类继承Thread
2. 重写run()方法, 要执行的任务
3. 创建子类的对象, 调用start()方法启动线程
[Java] 纯文本查看 复制代码
public class MyThread exctends Thread { // 定义MyThread类,并继承Thread类
@Override
public void run() { // 重写run方法
// 要执行的任务.不能直接调用
// 继承Thread类后,可以直接使用getName调用获取线程名称
System.out.println(getName()); // 方法一
// 没有继承Thread类 方法二
// Thread currentThread = Thread.currentThread(); // 获取当前执行的线程对象
// String name = currentThread.getName(); // 线程对象调用getname获取线程名称
// System.out.println(name); // 打印线程名称
// 简化合并的写法
System.out.println(Thread.currentThread().getName());
}
}
[Java] 纯文本查看 复制代码
public class Test { // 定义测试类
public static void main(String[] args) {
// 创建2个MyThread对象, 分别调用start()
// 每个线程对象, 就是一个线程. 2个线程对象就是2个线程
new MyThread().start();
new MyThread().start();
// 获取线程名称, 因为没有继承Thread类, 只能使用对象调用getName方法
System.out.println(Thread.currentThread().getName());
}
}
设置线程名称
1. Thread常用方法: setName(String name), Thread(String name)
// 构造方法
Thread(String threadName): 创建Thead对象并指定线程名
// 成员方法
voidsetName(String name): 设置线程名称
2. Thread常用方法: sleep()
// 静态方法
static void sleep(long millis): 让所在线程睡眠指定的"毫秒"(暂停指定的"毫秒")
[Java] 纯文本查看 复制代码
try {
Thread.sleep(1000); // 睡眠1000毫秒. 抛出编译时异常InterruptedException
} catch (InterruptedException e) {
e.printStackTrace();
}
第二种方式: 实现Runnable接口
1. 定义类, 实现Runnable接口
2. 重写 run() 方法, 要执行的代码(任务)
3. 创建Runnable实现类对象 (任务对象)
4. 创建Thread类对象, 在构造方法中传入Runnable实现类对象 (将任务和线程绑定)
5. 通过Thread对象调用 start() 方法启动线程
// 构造方法
Thread Thread(Runnable target): 通过Runnable对象创建Thread对象
Thread Thread(Runnable target, String threadName): 通过Runnable创建对象,指定线程名
// 定义类, 实现Runnable接口. 表示要执行的任务
[Java] 纯文本查看 复制代码
public class RunnableImpl implements Runnable { // 定义类, 实现Runnable接口
// 重写run方法
@Override
public void run() {
// 要执行的任务
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName + ":" + i);
}
}
}
// 测试类
[Java] 纯文本查看 复制代码
public class Test {
public static void main(String[] args) {
// 先创建任务对象
RunnableImpl run = new RunnableImpl();
// 然后创建Thread线程对象, 并传入线程要执行的任务
Thread t = new Thread(run);
// 调用Thread对象的start方法启动线程
t.start();
}
}
Thread和Runnable的区别
实现Runnable的好处:
1. 避免单继承的局限性
2. 增强了程序的扩展性, 降低了程序的耦合性(解耦)
线程是Thread, 任务是Runnable实现类对象. 相当于将线程和任务分离
我们追求 "低耦合"
匿名内部类方式创建线程
// 使用匿名内部类, 简化 继承Thread类方式 创建线程
[Java] 纯文本查看 复制代码
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}.start();
// 使用匿名内部类, 简化 实现Runnable接口方式 创建线程
[Java] 纯文本查看 复制代码
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
new Thread(r).start();
// 简化上面的代码, 直接在Thread的构造方法中, 传递匿名内部类
[Java] 纯文本查看 复制代码
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
线程安全问题的原因
多个线程操作共享资源时, 会产生线程安全问题;
问题发生原因:
JVM是抢占式调度, CPU在每个线程之间切换是随机的, 代码执行到什么位置是不确定的
在操作共享资源时, 由于一个线程还没有执行完, 另一个线程就来操作, 就会出现问题
如何解决:
在操作共享资源时, 让线程一个一个来执行, 不要并发操作共享变量, 就可以解决问题
解决多线程操作共享数据的安全问题的3种方式:
1. 同步代码块
2. 同步方法
3. Lock锁机制
解决线程安全问题方式1: 同步代码块
使用 synchronized 关键字修饰的代码块, 并传入一个当作锁的对象
格式:
// 定义一个共享的对象作为锁对象
Objectlock = new Object(); // 锁对象
synchronized (锁对象) {
// 操作共享数据的代码
}
注意:
锁对象可以是"任意类型的一个对象"
锁对象必须是"被多个线程共享的唯一的"对象
锁对象的作用: 只让一个线程在同步代码块中执行
锁对象, 也称为"同步锁", "对象锁", "对象监视器"
同步的原理:
线程进入同步代码块前, 会"争夺锁对象", "只有一个线程"会抢到锁对象
进入同步代码块的线程, 会"持有锁对象", 并执行同步代码块中的代码
此时同步代码块外的线程, 处于"阻塞"状态, 只能等待
当同步代码块内的线程执行完代码块, 会离开同步代码块, 并"归还锁对象"给同步代码块
等在同步代码块外的其他线程就可以继续争夺锁对象
解决线程安全问题方式2: 同步方法
使用 synchronized 关键字修饰的方法, 具有默认的锁对象
非静态同步方法的锁对象: this
// 非静态同步方法
publicsynchronizedvoid method(){
// 可能会产生线程安全问题的代码
}
静态同步方法的锁对象: 当前类的字节码对象 Class对象
RunnableImpl.class -> Class对象
获取一个类的字节码对象的3种方式:
1. 对象名.getClass() new RunnableImpl().getClass()
2. 类名.class RunnableImpl.class
3. Class.forName("类的全名"); Class.forName("com.itheima.test05.RunnableImpl");
字节码对象的特点: 同一个类, 他的字节码对象只有"唯一的一个"
锁对象必须是多个线程共享的同一个对象
// 静态同步方法
[Java] 纯文本查看 复制代码
public static synchronized void method(){
// 可能会产生线程安全问题的代码
}
解决线程安全问题方式3: Lock锁
java.util.concurrent.locks.Lock接口: JDK 5 新增的Lock接口
java.util.concurrent.locks.ReentrantLock类: Lock的实现类
// 成员方法
voidlock(): 获取锁
voidunlock(): 释放锁
使用方式:
[Java] 纯文本查看 复制代码
public class RunnableImpl implements Runnable {
// 成员变量创建锁对象, 该锁对象也要所有线程共享唯一一个
Lock lock = new ReentrantLock(); // 成员变量
@Override
public void run() {
// 加锁
lock.lock();
try {
// 操作共享变量的代码...
} finally {
// 在finally中保证释放锁
lock.unlock();
}
}
}
线程间的通信
Object类中关于线程的方法:
java.lang.Object类:
// 成员方法 (<<注意>>: 只能通过"锁对象"调用)
void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
void wait(): 让当前线程处于"无限等待"状态
void wait(long timeout): 让当前线程处于"计时等待"状态, 时间到或被唤醒后结束此状态
void wait(long timeout, int nanos): 让当前线程计时等待状态, 时间到或被唤醒后结束此状态
"注意!! 以上方法只能通过锁对象调用"
Object类中wait(long timeout)和notifyAll()方法
java.lang.Object类:
// 成员方法 (只能通过"锁对象"调用)
voidnotifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
voidwait(long timeout): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
wait() 和sleep() 的区别:
1. wait会释放锁, 恢复时需要重新获取锁; sleep不会释放锁
2. wait可以被notify/notifyAll唤醒; sleep不会
3. wait要用锁对象调用; sleep要用Thread类名调用
线程的六种生命周期状态:
1. "NEW 新建":
线程被创建, 但没有调用 start() 启动
2. "RUNNABLE 可运行"
调用 start()方法后已启动, 但可能正在执行 run() 方法的代码, 也可能正在等待CPU的调度
3. "BLOCKED (锁)阻塞"
线程试图获取锁, 但此时锁被其他线程持有
4. "WAITING 无限等待"
通过锁对象调用无参的 wait() 进入此状态.
等待其他线程通过锁对象执行 notify() 或 notifyAll() 才能结束这个状态
5. "TIMED_WAITING 计时等待"
如通过锁对象调用有参的 wait(long millis) 或 sleep(long millis), 则进入此状态.
直到时间结束之前被其他线程通过锁对象执行 notify()或 notifyAll()唤醒, 或时间结束自动唤醒
6. "TERMINATED 终止"
run()方法结束(执行结束, 或内部出现异常), 则进入此状态
作者: Java-yao 时间: 2018-11-20 16:19
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |