多线程简介

概念

  • 并行多个任务在同一个时刻点同时执行。效率高
  • 并发多个任务在同一段时间内分时执行。效率低,宏观上同时执行,微观上分时执行。
  • 进程:进程就是程序的一次执行过程,即是一个进程从加载到内存到从内存中释放消亡的过程。
  • 线程:一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 多线程 在一个进程中,可以同时开启多个线程,让多个线程同时去执行某些任务(功能)。多线程的目的是提高程序的运行效率。

线程调度 

  • 分时调度多个任务平均分配执行时间。

  • 抢占式调度线程之间抢夺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--;
                }
            }
        }
    }
}

同步方法  

  1. 当在run()方法中调用了其他方法时,那么被调用的方法也间接变为了线程任务。
  2. 同步方法,就是使用synchronized关键字修饰的方法,当某个方法中的代码全部是操作共享数据的代码时,我们可以直接将当前方法声明为同步方法。

注意事项

  1. 只有方法中的所有代码都是操作共享数据的代码时,才使用synchronized声明为同步方法,否则会降低执行效率。
  2. 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();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值