掌握Java多线程编程:从基本概念到实战技巧(篇一)

一、进程与线程

  • 进程:进程是程序的一次执行过程,是系统运行程序的基本单位
  • 线程: 线程是执行进程中的一个任务的过程,是系统运行程序的最小单位

一个进程中可以包含多个进程

二、线程的创建方式

线程只能通过new Thread()来创建,但是其执行逻辑共有四种

1.通过实现Runnable接口

  • 通过Thread调用start()方法启动线程
public class Test11 {
    public static void main(String[] args) {
        ToRunnable toRunnable = new ToRunnable();
        Thread thread = new Thread(toRunnable);
        thread.start();
    }
}
class ToRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("通过实现Runnable接口创建线程~~~");
    }
}

2.通过实现Callable接口

  • Callable可以有返回值、抛出异常,返回值通过 FutureTask 进行封装。
package com.yk;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ToCallable callable = new ToCallable(1,10);
        //FutureTask封装了一个计算任务允许在不同的线程中异步执行这个任务
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        Integer sum = futureTask.get();
        System.out.println("1-10的累加和为"+sum);
    }
}
class ToCallable implements Callable<Integer> {
    private int begin,end;

    public ToCallable(int begin, int end) {
        this.end = end;
        this.begin = begin;
    }

    @Override
    public Integer call() throws Exception {
        int rest = 0;
        for (int i = begin; i < end; i++) {
            rest+=i;
        }
        return rest;
    }
}

3.通过继承Thread类

public class Test12 {
    public static void main(String[] args) {
        ToTread thread = new ToTread();
        thread.start();
    }
}
class ToTread extends Thread{
    public void run(){
        System.out.println("继承Tread类~~~");
    }
}

4.线程池(后面会说到)

三、线程的优先级

在线程中通过setPriority(n)方法设置优先级,范围是1-10,默认为5

操作系统会对优先级较高的线程进行更频繁的调度

public class Demo04 {
    public static void main(String[] args) {
        Thread thread1 = new Pro();
        Thread thread2 = new Pro2();
        thread1.setPriority(4);
        thread2.setPriority(5);
        thread1.start();
        thread2.start();


    }
}

class Pro extends Thread {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("数字" + i);
        }
    }
}

class Pro2 extends Thread {
    public void run(){
        for (char i = 'A'; i < 'Z'; i++) {
            System.out.println("字母"+i);
        }
    }
}

四、线程的状态

一个线程从创建开始到执行结束,共有6种状态:

1.新建(New)

未执行star()方法

2.可运行状态(Runable)

已经调用star()方法,可能正在运行,也可能在等待cpu分配资源

3.阻塞(Bloking)

运行时的线程在竞争同一把锁时,未争到的线程为阻塞状态

4.等待(Wait)

线程执行时调用.wait()或者.join()方法,让当前线程让出cpu资源,进入等待

5.计时等待(Timed Wait)

线程执行时调用.sleep(等待时间毫秒).wait(等待时间毫秒)或者.join(等待时间毫秒)方法,使线程进入到计时等待

  • 阻塞和等待的区别:
    • 阻塞是属于被动,在等待获取到一把锁
    • 等待是主动的,通过调用Thread.sleep() 和 Object.wait() 等方法进入
进入方法退出方法
Thread.sleep(等待时间)方法等时间结束
设置了 时间 参数的 Object.wait() 方法时间结束 / Object.notify() / Object.notifyAll()
设置了 时间 参数的 Object.join() 方法时间结束 / 被调用的线程执行完毕

6.终止(Terminated)

线程执行任务(Run方法)结束后终止,或是线程执行过程中抛出异常而终止

五、基础线程机制

1.线程的休眠(sleep)

线程中可以通过调用Thread.sleep(休眠时长),来使线程强制休眠(等待)指定的时间

public class Demo {
        public static void main(String[] args) {
        System.out.println("主线程开始~");
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10 ; i++) {
                System.out.println(i);
            }
        });
        thread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("主线程结束~");
    }
}

2.线程的插队(join)

join()方法会使当前线程进入等待池,等待调用该方法的线程执行完才会被唤醒

    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();

        myThread.join();
        System.out.println("主线程会在子线程myThread执行完才会执行~");
    }
}
class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("数字" + i);
        }
    }
}
  • join()方法的实现原理

join()底层是利用wait()方法实现,用synchronized修饰,当主线程执行join()方法时 ,主线程先获得当前线程对象的锁,随后进入join方法,调用当前对象的wait()方法,使主线程进入等待,等到当前线程对象执行完毕之后,线程会自动调用自身的notifyAll()方法,唤醒所有处于等待状态的线程

源码:

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

3.线程的让出(yield)

  • 分时调度模型:所有线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU时间
  • 抢占式调度模型:优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,就随机选择一个线程,使其占用CPU(JVM虚拟机采用的是抢占式调度模型)

eg:线程t2在执行过程中调用yield()让出Cpu,使t1的执行概率变高

public class Demo02 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        },"数字");

        Thread t2 = new Thread(() -> {
            for (char i = 'A'; i < 'Z'; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
                Thread.yield(); //让当前线程让出cpu
            }
        },"字母");

        t1.start();
        t2.start();
    }
}

4.线程的中断 (interrept)

一个线程会在执行完毕后自动结束,但如果在运行的过程中发生了异常就会使线程中断

  • 我们可以通过调用interrup()方法来中断线程,如果该线程处于阻塞、等待(wait()/join()/sleep())状态,就会抛出异常
    InterruptedException使线程提前结束

在这个案例中,由于t1线程调用了join()方法而抛出了异常,导致l线程t1的中断

public class Demo02 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        },"数字");

        Thread t2 = new Thread(() -> {
            for (char i = 'A'; i < 'Z'; i++) {
                System.out.println(Thread.currentThread().getName()+":"+i);
                Thread.yield(); //让当前线程让出cpu
            }
        },"字母");

        try {
            t1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t1.start();
        t2.start();
    }
}
  • 在线程执行过程中我们还可以通过**isinterrupted()**方法判断当先线程是否中断
public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程:开始执行");

        // main主线程创建子线程MyThread
        MyThread t = new MyThread();
        t.start();

        // 主线程休眠1000毫秒
        Thread.sleep(1000);

        // 结束休眠后
        t.interrupt(); // 中断t线程
        t.join(); // 等待t线程结束

        System.out.println("主线程:结束执行");
    }
}

class MyThread extends Thread {
    public void run() {
        System.out.println("MyThread线程:开始执行");

        // MyThread线程创建子线程HelloThread
        HelloThread hello = new HelloThread();
        hello.start(); // 启动HelloThread线程

        try {
            hello.join(); // 等待hello线程结束
        } catch (InterruptedException e) {
            System.out.println("MyThread线程:结束执行,interrupted!");
        }

        // MyThead线程结束后,中断子线程HelloThread
        hello.interrupt();
    }
}

class HelloThread extends Thread {
    public void run() {
        System.out.println("Hello线程:开始执行");
        int n = 0;

        // 检查当前线程是否已经中断
        while (!isInterrupted()) {
            n++;
            System.out.println(n + " hello!");
        }

        System.out.println("Hello线程:结束执行!");
    }
}

5.守护线程 (Daemon Thread)

  • 什么是守护线程?

    • 守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
    • 当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程
  • 设置守护线程(setDaemon)

    • 在线程调用star()方法前调用setDaemon(true),将当前线程标记为守护线程
Thread tProject = new Thread(() -> {
    while (true) {
        System.out.println("守护线程");
    }
});
tProject.setDaemon(true);
tProject.start();

六、synchronized关键字(锁)

在多线程模型下,要保证逻辑正确,对共享变量进行读写时,必须保证一组指令以原子方式执行:即某一个线程执行时,其他线程必须等待:

synchronized关键字可以保证代码块在任意时刻最多只有一个线程可以执行

可以将synchronized加在方法本身,或者单独为一个代码块

在使用synchronized关键字的时候,无论是否有异常,都会在synchronized结束后将锁释放

代码块形式:手动指定锁的对象,可以是this,也可以是自定义的锁

//自定义锁
class Count {
    public static int count = 0;
    //创建Object对象,实现同步锁
    public final static Object LOCK = new Object();
}

class DecThread extends Thread {
    public void run() {
        for (int i = 0; i < 1000; i++) {
            synchronized (Count.LOCK) {
                Counter.count -= 1;
            }
        }

    }

synchronized修饰普通方法时,锁的对象默认为this

    //在方法声明上使用synchronized关键字
    //对整个方法体进行加锁,使用this对象作为锁
    public synchronized void add() {
        for (int i = 0; i < 1000; i++) {
            Counter.count += 1;
        }
    }
  • 不需要synchronized的操作

    • 在我们使用原子操作实现多线程操作时,可以不使用synchronized关键字

    JVM规范定义了几种原子操作:

    • 基本类型(long 和double 除外)赋值,例如:int n= m
      • long和double是64位数据,JVM没有明确规定64位赋值操作是不是一个原子操作,不过在x64平台的JVM是把1ong和double的赋值作为原子操作实的。
    • 引用类型赋值,例如:List list = anotherList

案例

  1. 使用多线程打印字母加数字

数字类

public class Numbers implements Runnable {
    private final Object lock;

    public Numbers(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            for (int i = 1; i < 53; i++) {
                //判断当为奇数时加空格
                if (i % 2 == 1) {
                    System.out.print(" ");
                }
                System.out.print(i);
                //如果为偶数则等待一下
                if (i % 2 == 0) {//唤醒字母线程
                    lock.notify();
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}

字母类

public class Character implements Runnable {
    private final Object lock;

    public Character(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            for (char i = 'A'; i <= 'Z'; i++) {
                System.out.print(i);
                //唤醒数字线程
                lock.notify();
                //
                if (i < 'Z') {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        final Object LOCK = new Object();
        Thread t1 = new Thread(new Numbers(LOCK));
        Thread t2 = new Thread(new Character(LOCK));

        t1.start();
        t2.start();
    }
}

结果:

12A 34B 56C 78D 910E 1112F 1314G 1516H 1718I 1920J 2122K 2324L 2526M 2728N 2930O 3132P 3334Q 3536R 3738S 3940T 4142U 4344V 4546W 4748X 4950Y 5152Z
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值