一、线程
- 线程的概念
线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 线程的生命周期
- 线程状态转换图
二、创建线程的方法
1.创建Thread类的子类
java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
- 实现步骤
1.创建一个Thread类的子类;
2.在Thread类的子类中重写Thread类的run方法,设置线程任务(线程需要做什么?);
3.创建Thread类的子类对象;
4.调用Thread类中的start()方法,开启新的线程,执行run()方法;void start()使该线程开始执行,Java虚拟机调用该线程的run方法,结果是两个线程并发的执行,当前线程(main线程)和开启的新线程(执行其run方法);
- 注意
1.多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动;
2.java程序属于抢占式调度,哪个线程的优先级高,哪个线程先执行;同一优先级,随机选择一个执行。
- 代码示例
测试类
//main线程
public static void main(String[] args) {
//创建Thread对象
MyThread thread = new MyThread();
//调用start()方法开启线程
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:" + i);
}
}
MyThread类
//MyThread类集成Thread类,重写run()方法
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run:" + i);
}
}
}
运行结果:随机打印两个两个for循环里面的内容
2.实现Runnable接口
java.lang.Runnable接口应该由打算通过某一线程执行其实现类来实现,类中必须定义一个称为run的无参数方法。
- Thread类的构造方法:
Thread(Runnable target):分配新的Thread对象
Thread(Runnable target,String name): 分配新的Thread对象
- 实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法传递Runnable接口的实现类对象
5.调用Thread类中的start()方法,开启新的线程执行run方法
- 实现Runnable接口创建多线程程序的好处
1.避免了单继承的局限性:一个类智能集成一个类(一个人只有一个亲爹),类集成了Thread类就不能集成其他的类,但实现了Runnable接口,还可以继承其他的类,实现其他的接口;
2.增强了程序的扩展性,降低了程序的耦合性(解耦):实现Runnable接口的方法,把设置线程任务和开启线程进行了分离(解耦。
- 代码示例
Runnable实现类
//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {
//2.实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
测试类
public static void main(String[] args) {
//3.创建一个Runnable实现类的实现对象
RunnableImpl impl = new RunnableImpl();
//4.创建Thread对象,构造方法中传递Runnable接口的实现了对象
Thread t = new Thread(impl);
//5.调用Thread类中的start()方法开启线程
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
三、解决线程安全问题的方法
1.线程安全概述
如果有多个线程同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期是一样的,就是线程安全的。
下面,通过一个案例,演示线程的安全问题:
电影院卖票,我们模拟电影院的卖票过程,假设有三个售票窗口同时售卖同一场电影的票,就会出现安全问题,如下图所示
那么我们如何来解决线程的安全问题呢,下面我们就介绍两种方法来解决线程的安全问题
2.使用同步代码块
- 同步技术原理
使用一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
例如有三个线程(t0、t1、t2)一起抢夺CPU执行权进行卖票,
t0抢到了CPU的执行权,执行run方法,遇到synchronized代码块,这是t0会检查synchronized代码块是否有锁对象,发现有,就会获取大锁对象,进入到同步中执行;此时t1抢到了CPU的执行权,执行run方法,遇到synchronized代码块,t1会检查synchronized代码块是否有锁对象,发现没有,此时t1就会就如到阻塞状态,一直等到t0线程归还锁对象(一直到t0线程只想玩同步代码块中的代码,会把锁对象归还给同步代码块,此时t1才能获取到锁对象进入到同步代码块中执行)
- 格式
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
- 注意
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行
- 代码示例:
线程类
public class SynchonizedThread implements Runnable{
//定义一个多线程共享的票源
private int ticket = 100;
//设置一个锁对象
Object obj = new Object();
//设置线程任务
@Override
public void run() {
while(true){
//同步代码块
synchronized(obj){
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票,ticket--
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "张票");
ticket--;
}
}
}
}
}
测试类
public static void main(String[] args) {
LockThread st = new LockThread();
//创建三个线程
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
Thread t3 = new Thread(st);
//同时启动三个线程
t1.start();
t2.start();
t3.start();
}
- 总结
同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步,必须等到当前执行的线程执行完毕释放锁以后才能刚进入同步代码块中执行
3.使用Lock锁
java.util.concurrent.locks.Lock接口实现提供了比使用synchronized方法和语句可获得更更广泛的锁定操作
- Lock接口中的方法:
void lock() 获取锁
voidunlock() 释放锁
- 使用步骤
1.在成员位置创建一个ReentrantLock对象
2.在可能出现安全问题的代码前调用Lock接口中的方法lock()获取锁
3.在可能出现安全问题的代码后调用Lock()接口中的方法unlock()释放锁
- 代码示例
public class LockThread implements Runnable{
//定义一个多线程共享的票源
private int ticket = 100;
//1.在成员变量的地方创建一个ReentrantLock锁对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
//2.在可能出现安全问题的代码前调用Lock接口实现对象的lock()方法获取锁
lock.lock();
try {
if(ticket>0){
Thread.sleep(10);
//票存在,卖票,ticket--
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "张票");
ticket--;
}
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
//3.在可能出现安全问题的代码后调用Lock接口实现对象的unlock()方法释放锁
//无论程序是否出现异常,最后都将锁释放
lock.unlock();
}
}
}
}