Java 多线程

进程 & 线程

进程是指正在运行的程序,确切的说,当一个程序进入内存开始运行,就开启了一个进程。进程就是处于运行状态的程序,并且具有一定的独立功能。

线程是进程中的一个执行单元,负责当前进程中程序的执行。一个进程中至少有一个线程。

一个程序运行后至少有一个进程,一个进程中可以有多个线程。

什么是多线程?

多线程就是指一个程序中,多个线程“同时”执行。

可以通过下图来理解分多线程程序与单线程程序的不同:
- 单线程程序:若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。如,去网吧上网,单线程的网吧同一时间只能让一个人上网,只有当这个人下机后,下一个人才能上网。
- 多线程程序:即,若有多个任务可以同时执行。如,多线程网吧能够让多个人同时上网。
单线程&多线程

程序运行原理

大部分操作系统都支持多进程并发运行,也就是可以同时运行多个程序。比如说你在上网逛淘宝的同时(开一个浏览器),还可以听音乐(开一个音乐播放器),同时使用迅雷下载电影,感觉这些软件都在同时运行。而实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。

分时调度 & 抢占式调度

分时调度就是说所有线程轮流获取CPU 的使用权,平均分配每个线程占用 CPU 的时间。
而抢占式调度就是
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

创建线程的几种方法

1. 继承Thread类

  1. 自定义类并继承Thread类,重写run()方法
  2. 创建该自定义类的对象,并调用其start()方法

class MyThread extends Thread {

    @override
    public void run() {
        System.out.println("Thread Body...");
    }
}

class Test {
    public static void main(String[] args) {

        MyThread thread = new MyThread();
        thread.start(); // 开启线程
    }
}

2. 实现Runnable接口

  1. 自定义类实现Runnable接口,并实现run()方法
  2. 创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象
  3. 调用Thread对象的start()方法

class MyThread implements Runnable {

    @override
    public void call() {
        System.out.println("Thread Body...");
    }
}

class Test {
    public static void main(String[] args) {

        Thread thread = new MyThread(new MyThread());
        thread.start(); // 开启线程
    }
}

3. 实现Callable接口

  1. 实现Callable接口, 实现call()方法
  2. 创建实现Callable接口的参数,并将之作为参数实例化一个 FutureTask 对象
  3. 创建Thread对象,并用刚创建的FutureTask对象作为参数实例化该Thread对象
  4. 调用Thread对象的start()方法

class MyThread implements Callable<String> {

    @override
    public String call() throws Exception {
        System.out.println("Thread Body...");

        return "hello";
    }
}

class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        Callable<String> callable = new MyThread(); 
        FutureTask<String> task = new FutureTask<>(callable);
        Thread thread = new Thread(task);
        thread.start(); // 开启线程

        String result = task.get(); // 等待线程结束获取返回值
    }
}

Runnbale接口和Callable接口的区别

  1. Callable接口可以在任务结束后提供一个返回值,Runnable接口无法提供该功能
  2. Callable接口的call()方法可以抛出异常,而Runnable接口的run()方法不能抛出异常
  3. 运行实现Callable接口的对象可以获取一个Future对象,Future表示异步计算的结果,提供了检查计算是否完成的方法

注意点:Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

线程的生命周期

image
1. 新建(new Thread): Thread t = new Thread();
2. 就绪(Runnable): t.start();
3. 运行(Running): run() 运行
4. 死亡(dead): 执行完毕(自然死亡,即run()方法执行结束)/ 被其他线程杀死(异常死亡,stop())
5. 堵塞(blocked): sleep()、 wait()、suspend()

各个方法的使用

  • wait() 方法是一种使线程暂停执行的方法,直到被唤醒(notify()方法唤醒)或者等待超时
  • sleep() 方法使当前运行的线程休眠指定时间
  • stop() 终止线程执行,释放已经锁定它的所监视的资源
  • suspend() 方法将一个线程挂起(暂停),且不会自动恢复,必须通过调用resume()方法使之重新进入可执行状态

解决多线程安全问题:

  1. 明确哪些代码是多线程运行代码
  2. 明确共享数据
  3. 明确多线程运行代码中哪些语句是操作共享数据的

注意: 同步函数的锁是this, 而静态同步函数的锁是Class对象:类名.class

解决线程同步问题的方法

1. 同步方法

即有synchronized关键字修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

代码如:

public synchronized void save(){}

注意: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

2. 同步代码块

即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

代码如:

synchronized(object){ 
    ...
}

**注意:同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。**

3. 使用特殊域变量(volatile)实现线程同步
  • volatile关键字为域变量的访问提供了一种免锁机制,
  • 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
  • 因此每次使用该域就要重新计算,而不是使用寄存器中的值
  • volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。用final域,有锁保护的域和volatile域可以避免非同步的问题。

4. 使用重入锁实现线程同步

在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,
它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力

ReenreantLock类的常用方法有:

    ReentrantLock() : 创建一个ReentrantLock实例 
    lock() : 获得锁 
    unlock() : 释放锁 

注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值