Java多线程与IO流学习笔记(二)
多线程
并发与并行
- 并发:指两个或多个事件在同一时间段内发生
- 并行:指两个或多个事件在同一时刻发生(同时发生)。
线程和进程
- 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
- 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
线程调度
- 分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用cpu的时间。
- 抢占式调度:优先让优先级高的线程使用cpu,如果进程的优先级相同,那么会随机选择一个(线性随机性),Java使用的为抢占式调度。
创建线程类
Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是 完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。 Java中通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
代码如下:
1 | public class MyThread extends Thread { |
测试类:
1 | public class Demo01Thread { |
多线程原理
程序启动运行main的时候,Java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的 start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。
通过这张图我们可以很清晰的看到多线程的执行流程,那么为什么可以完成并发执行呢?我们再来讲一讲原理。 多线程执行时,到底在内存中是如何运行的呢?以上个程序为例,进行图解说明: 多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。
Thread类常用方法
- public String getName():获取当前线程名称
- public void start():导致次线程开始执行;Java虚拟机调用此线程的run方法
- public void run():此线程要执行的任务在此处定义代码。
- public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停
- public static Thread currentThread():返回当前正在执行的线程对象的引用
创建线程的第二种方式
采用 java.lang.Runnable 也是非常常见的一种,我们只需要重写run方法即可。 步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动线程。
代码如下:
1 | public class RunnableImpl implements Runnable { |
测试类:
1 | public class Demo01Runnable { |
Thread和Runnable的区别
实现Runnable接口创建多线程的好处:
- 避免了单继承的局限性
- 增强了程序的扩展性,降低了程序的耦合性:把设置线程任务和开启新线程进行了分离
匿名内部类的方式实现线程的创建
1 | /** |
线程安全
多线程访问了共享的数据会产生线程安全问题。
案例代码:
1 | /** |
主方法:
1 | public class Demo01Tickket { |
出现结果:
过程图示:
线程同步
解决线程安全问题可以使用线程同步机制(synchronized)来解决。
有三种同步操作:
- 同步代码块
- 同步方法
- 锁机制
同步代码块
- 同步代码块:synchronized关键字可以用于方法的某个区块中,表示支队这个区块的资源实行互斥访问
格式:
1 | synchronized(同步锁){ |
注意:
- 同步代码块中的锁对象,可以使用任意的对象
- 但是必须保证多个线程使用的锁对象是同一个
- 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行同步技术的原理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class RunnableImpl implements Runnable {
private int ticket = 100;
// 创建一个锁对象
Object obj = new Object();
public void run() {
while (true) {
synchronized (obj) {
if (ticket > 0) {
// 提高安全问题出现的概率
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->正在买第" + ticket + "张票");
ticket--;
}
}
}
}
}
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器。
3个线程一起抢夺CPU的执行权,谁抢到了谁执行run方法进行卖票,t0抢到了CPU的执行权,执行run方法,遇到synchronized代码块,这时t0会检查synchronized代码块是否有锁对象,发现有,就会获取到锁对象,进入到同步中执行。
t1抢到了CPU的执行权,执行run方法,遇到synchronized代码块,这时t1会检查synchronized代码块是否有锁对象,发现没有,t1就会进入到阻塞状态,会一直等待t0线程执行完同步中的代码,会把锁对象归还给同步代码块,t1才能获取到锁对象进入到同步中执行
总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步。
问题:程序频繁的判断锁,获取锁,释放锁,程序的效率会降低。
同步方法
- 同步方法: 使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外
等着。
格式:步骤:1
2
3public synchronized void method(){
//可能会产生线程安全问题的代码
}
- 把访问了共享数据的代码抽取出来,放入一个方法中
- 在方法上添加synchronized关键字
同步方法的锁对象是谁?就是实现类对象,new RunnableImpl(),也就是this。
静态的同步方法:
1 | public static synchronized void method(){ |
静态方法的锁对象是本类的class属性–>class文件对象(反射)
Lock锁
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,
同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
- public void lock() :加同步锁。
- public void unlock() :释放同步锁。
使用步骤:
- 在成员位置创建一个ReentrantLock对象
- 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
- 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
1 | public class RunnableImpl implements Runnable { |