概念
- 并行:多个任务在同一个时刻点同时执行。效率高
- 并发:多个任务在同一段时间内分时执行。效率低,宏观上同时执行,微观上分时执行。
- 进程:进程就是程序的一次执行过程,即是一个进程从加载到内存到从内存中释放消亡的过程。
- 线程:一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
- 多线程: 在一个进程中,可以同时开启多个线程,让多个线程同时去执行某些任务(功能)。多线程的目的是提高程序的运行效率。
线程调度
分时调度:多个任务平均分配执行时间。
抢占式调度:线程之间抢夺CUP的执行权,谁抢到谁执行(随机性)。
多线程的实现
1.创建子类,继承Thread类
- 声明Thread的子类,声明一个类继承Thread。
- 在子类中重写run()方法,即线程任务。
- 创建子类对象。
- 启动多线程。
void run•() 线程任务方法,需要多线程执行的代码都写在此方法内。 void start•() 开启新的线程,java虚拟机调用此线程的run方法。
// 声明Thread子类
public class MyThread extends Thread {
// 重写run()方法,线程任务
@Override
public void run() {
for( int i = 1; i <= 20; i++ ) {
System.out.println("旺财..." + i );
}
}
}
测试代码
public class Demo {
public static void main(String[] args) {
// 创建子类对象
MyThread mt = new MyThread();
// 启动线程任务
// mt.run(); 直接调用run()方法无法开启新的线程
mt.start(); // 开启新线程并调用run()方法
for( int i = 1; i <= 20; i++ ) {
System.out.println("启动...." + i );
}
}
}
2.实现Runnable接口
声明Runnable接口的实现类
实现类中重写run()方法
创建实现类对象
创建Thread线程对象,并将实现类对象传递给构造方法。
启动线程
public class MyRunnable implements Runnable {
// 线程任务
@Override
public void run() {
for( int i = 1; i <= 20; i++ ) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
测试代码
public class Demo {
public static void main(String[] args) {
// 创建线程任务对象
MyRunnable mr = new MyRunnable();
// 创建线程对象
Thread thread1 = new Thread(mr);
Thread thread2 = new Thread(mr);
// 启动线程
thread1.start();
thread2.start();
}
}
3.实现Callable接口
- 与使用Runnable接口相比, Callable功能更强大些
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值(需要借助FutureTask类,获取返回结果)
//1.创建一个实现Callable的实现类
class MyCallable implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
System.out.println("子线程在进行计算");
int sum = 0;
for (int i = 0;i < 5; i++) {
sum = sum + i;
}
return sum;
}
}
public class TestMyCallable {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
MyCallable mc = new MyCallable();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask ft = new FutureTask<>(mc);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(ft).start();
System.out.println("主线程在执行任务");
//6.get()接收返回值
try {
Object sum = ft.get();
System.out.println("和为:"+sum);
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("所有任务执行完毕");
}
}
同步代码块
语法:
synchronized (任意唯一锁对象) { 操作共享数据的代码; }
public class Ticket implements Runnable {
// 声明变量 模拟车票
private int num = 100;
// 声明锁对象
private Object lock = new Object();
// 线程任务就是售票
@Override
public void run() {
// 死循环模拟窗口在一直售票
while ( true ) {
// 同步代码块
synchronized ( lock ) {
// 判断是否还有车票
if (num > 0) {
// 获取线程名字
String name = Thread.currentThread().getName();
System.out.println(name + "售票:" + num);
// 线程休眠,模拟出票时间
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票数迭代,减去售出的票
num--;
}
}
}
}
}
同步方法
- 当在run()方法中调用了其他方法时,那么被调用的方法也间接变为了线程任务。
- 同步方法,就是使用synchronized关键字修饰的方法,当某个方法中的代码全部是操作共享数据的代码时,我们可以直接将当前方法声明为同步方法。
注意事项
- 只有方法中的所有代码都是操作共享数据的代码时,才使用synchronized声明为同步方法,否则会降低执行效率。
- run()方法不能使用synchronized修饰,否则线程任务无法被多线程执行。
public synchronized void methodName( 形参 ) {
操作共享数据的代码;
}
线程死锁
- 死锁:多个线程操作共享数据,但是要求线程获取锁的先后次序不同。但是都必须根据自己的次序获取所有的锁才能去执行这个任务。而在获取锁的过程中,不同的线程获取方式不同,导致锁会被其他的线程占有。一旦发生这个问题,就立刻发生死锁问题。
public class DieThread implements Runnable{
// 声明AB两个锁对象
private Object lock_A = new Object();
private Object lock_B = new Object();
// 声明变量 控制执行流程
boolean flag = false;
@Override
public void run() {
if( flag ) {
while ( true ) {
// 获取A锁的同步代码块
synchronized (lock_A ) {
System.out.println("if..lock_A 锁...");
// 获取B锁的同步代码块
synchronized (lock_B) {
System.out.println("if..lock_B锁...");
}
}
}
} else {
while (true){
// 获取B锁的同步代码
synchronized (lock_B) {
System.out.println("else..lock_B锁...");
// 获取A锁的同步代码块
synchronized (lock_A) {
System.out.println("else..lock_A锁...");
}
}
}
}
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException {
// 创建线程任务对象
DieThread dt = new DieThread();
// 创建线程对象
Thread a = new Thread(dt);
Thread b = new Thread(dt);
// 启动线程任务
a.start();
// 线程休眠
Thread.sleep(1);
// 修改flag的值,让线程可以进入if语句
dt.flag = true;
// 启动线程
b.start();
}
}