一、多线程
1、并发与并行
并行:指两个或多个事件在同一时刻发生(同时执行)。
并发:指两个或多个事件在同一个时间段内发生(交替执行)。
2、线程与进程
进程:进程是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;
—— 进程是应用程序的可执行单元
——一个应用程序可以有多个进程
—— 每个进程执行都会有独立的内存空间
线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一条线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
——线程是进程的可执行单元一个
——进程可以有多条线程
——每个线程执行都会有独立的内存空间
java只有单进程,然后有多线程
一个进程一次只能执行一条线程,所以java中只有多线程并发,没有多线程并行
线程的调度:
——分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间
——抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)
——Java线程的调度方式: 抢占式
3、Thread类
概述:
——java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例 每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码
——Java使用线程执行体来代表这段程序流,在Tread线程中,使用run()方法代表线程执行体构造方法
public Thread():创建一个新的线程对象,默认名称
public Thread(String name):创建一个指定名字的新的线程对象
public Thread(Runnable target):创建一个带有指定任务的线程对象,通过参数Runnable指定任务
public Thread(Runnable target,String name):创建一个带有指定任务的线程对象并指定线程名字常用方法
public String getName():获取当前线程名称
public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法
public void run():此线程要执行的任务在此处定义代码
public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停执行
public static Thread currentThread() :返回对当前正在执行的线程对象的引用
通过Thread类的api,可以指定创建线程有2种方式:
—— 1.通过继承Thread类的方式
—— 2.通过实现Runnable接口的方式
4、继承方式创建线程
步骤:
——创建一个子类继承Thread类在
——子类中重写run方法,把线程需要执行的任务代码放入run方法中
——创建子类对象,调用start()方法启动线程,执行任务
实现:
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程i循环:第"+i+"次循环");
}
System.out.println("子线程任务结束了...");
}
}
public class Test {
public static void main(String[] args) {
/*
注意:
1.每个java程序都有一条主线程,主线程中会调用main方法
2.线程不能重复启动,只能启动一次
3.线程执行完任务就会销毁
4.主线程必须等所有的子线程都执行完毕才能销毁
5.启动线程一定要调用start方法,不要直接调用run方法
通过继承方式创建线程:
1.创建子类继承Thread类,重写run方法
2.在run方法中书写线程需要执行的任务代码
3.创建子类线程对象
4.调用start方法启动线程,执行任务
*/
// 1.创建线程子类对象
MyThread mt = new MyThread();
// 2.启动线程,执行任务
mt.start();
for (int j = 0; j < 100; j++) {
System.out.println("主线程j循环:第"+j+"次循环");
}
}
}
5、实现方式创建线程
Runnable是一个任务接口,里面有一个抽象方法run(),可以在run方法中书写线程的任务代码
实现步骤:
——创建实现类实现Runnable接口
——在实现类中,重写run方法,把线程需要执行的任务代码放入run方法中创建实现类对象
——创建Thread线程对象,并传入实现类对象
——使用Thread线程对象调用start方法启动线程,执行任务
实现:
public class Test {
public static void main(String[] args) {
/*
通过实现Runnable方式:
1.创建实现类实现Runnable接口,重写run方法
2.把线程需要执行的任务代码放入run方法中
3.创建Thread线程对象,并传入Runnable的实现类对象
4.调用start方法启动线程执行任务
*/
// 1.创建实现类对象
MyRunnable mr = new MyRunnable();
// 2.创建Thread线程对象,传入Runnable的实现类对象
Thread t = new Thread(mr);
// 3.调用start方法启动线程执行任务
t.start();
// 主线程的任务代码
for (int j = 0; j < 100; j++) {
System.out.println("主线程j循环:第"+j+"次循环");
}
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程i循环:第"+i+"次循环");
}
}
}
6、匿名内部类方式
原理: 可以传入Runnable接口的匿名内部类
步骤:
——创建Thread线程对象,并传入Runnable接口的匿名内部类
——在Runnable匿名内部类中重写run方法,书写线程需要执行的任务代码
——使用Thread线程对象调用start方法启动线程,执行任务
实现:
public class Test {
public static void main(String[] args) {
/*
通过Runnable接口的匿名内部类方式:
1.创建Thread线程对象,并传入Runnable接口的匿名内部类
2.在匿名内部类中重写run方法,把线程需要执行的任务代码放在run方法中
3.调用start方法启动线程执行任务
*/
// 1.创建并启动线程
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// 线程任务代码
for (int i = 0; i < 100; i++) {
System.out.println("子线程的i循环:第" + i + "次循环");
}
}
});
t.start();
// 2.主线程任务代码
for (int j = 0; j < 100; j++) {
System.out.println("主线程的j循环:第" + j + "次循环");
}
}
}
7、创建并启动多条线程
通过继承的方式:
public class Test {
public static void main(String[] args) {
// 需求: 创建并启动4条子线程
// 创建4个线程对象
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
MyThread mt3 = new MyThread("线程3");
MyThread mt4 = new MyThread("线程4");
// 启动线程执行任务
mt1.start();
mt2.start();
mt3.start();
mt4.start();
}
}
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
// 线程任务代码
System.out.println(getName() + ":子线程的任务代码...");
}
}
通过实现的方法:
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程任务代码
System.out.println(Thread.currentThread().getName() + ":子线程的任务代码...");
}
}
public class Test {
public static void main(String[] args) {
// 需求: 创建并启动4条子线程
// 1.创建Runnable实现类对象
MyRunnable mr = new MyRunnable();
// 2.创建Thread线程对象,并传入Runnable实现类对象
Thread t1 = new Thread(mr,"线程1");
Thread t2 = new Thread(mr,"线程2");
Thread t3 = new Thread(mr,"线程3");
Thread t4 = new Thread(mr,"线程4");
// 3.调用start方法启动线程,执行任务
t1.start();
t2.start();
t3.start();
t4.start();
}
}
8、实现方式创建线程的优势
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源(任务)。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
二、线程安全
1、线程安全问题
线程安全问题演示:需求:
- 使用多线程模拟4个窗口共同卖100张电影票
- 分析:
——4个窗口---->4条线程
——共同卖100张电影票
——卖票的代码都是一样的---->4条线程的任务代码是一样的 - 实现:
public class MyRunnable implements Runnable {
// 多条线程共享
int tickets = 100;
@Override
public void run() {
// 线程任务代码--->卖票
// 循环卖票
while (true) {
// 判断--出口
if (tickets < 1){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
tickets--;
}
}
}
public class Test {
public static void main(String[] args) {
// 需求: 使用多线程模拟4个窗口共同卖100张票
// 创建任务对象
MyRunnable mr = new MyRunnable();
// 创建4条线程并启动
new Thread(mr,"窗口1").start();
new Thread(mr,"窗口2").start();
new Thread(mr,"窗口3").start();
new Thread(mr,"窗口4").start();
/*
问题:
1.重复票
2.遗漏票
3.负数票
*/
}
}
- 原因: 线程的调度是抢占式
——当某条线程在执行卖票的代码的时候,被其他线程干扰了,导致程序运行结果受影响 - 解决:
——当某条线程在执行卖票的代码的时候,不要被其他线程干扰了---->加锁
——Synchronized—>同步代码块,同步方法
——Lock锁
2、synchronized
synchronized关键字:表示“同步”的。它可以对“多行代码”进行“同步”
——将多行代码当成是一个完整的整体,一个线程如果进入到这个代码块中,会全部执行完毕,执行结束后,其它线程才会执行。
这样可以保证这多行的代码作为完整的整体,被一个线程完整的执行完毕。
synchronized被称为“重量级的锁”方式,也是“悲观锁”——效率比较低。
synchronized有几种使用方式:
a).同步代码块
b).同步方法【