Java 多线程使用

1. Thread

1.1 创建线程

继承Thread,重写run方法

@Test
public void helloThreadTest() {
    Thread thread = new HelloThread();
    thread.start();
}

public class HelloThread extends Thread {
    @Override
    public void run() {
        System.out.println("HelloThread");
    }
}

实现Runnable接口

@Test
public void helloRunnableTest() {
    Thread thread = new Thread(new HelloRunnable());
    thread.start();
}

public class HelloRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("HelloRunnable");
    }
}

Lambda语法简写

@Test
public void lambdaTest() {
    Thread thread = new Thread(() -> {
        System.out.println("lambdaTest");
    });
    thread.start();
}

1.2 join

join()方法使主线程会等待线程完成后继续执行。

没有join()方法,只能保证主线程先打印main start,然后main end;线程先打印thread start,然后thread end
main end具体打印什么位置未知,由操作系统调度

但是添加join,就可以保证main end在线程执行结束后打印

@Test
public void mainThreadTest() throws InterruptedException {
    System.out.println("main start");

    Thread thread = new Thread(() -> {
        System.out.println("thread start");
        System.out.println("thread end");
    });
    thread.start();
    //thread.join();

    System.out.println("main end");
}

1.3 interrupt

Thread.sleep(1);需要使用main方法,单元测试会不可用。

线程t中断,主线程等待线程t中断后,继续执行

public static void main(String[] args) throws InterruptedException {
    Thread t = new InterruptThread();
    t.start();
    // 暂停1毫秒
    Thread.sleep(1);
    // 中断t线程
    t.interrupt();
    // 等待t线程结束
    t.join();
    System.out.println("end");
}

public class InterruptThread extends Thread {
    public void run() {
        int n = 0;
        while (!isInterrupted()) {
            n ++;
            System.out.println(n + " hello!");
        }
    }
}

线程t启动后,线程t内部会启动线程2,并等待线程2结束
当线程t中断后,主线程等待线程t结束
如果不把线程2中断,则线程2会继续执行

public static void main(String[] args) throws InterruptedException {
    Thread t = new InterruptThread1();
    t.start();
    Thread.sleep(1000);
    // 中断t线程
    t.interrupt();
    // 等待t线程结束
    t.join();
    System.out.println("end");
}

public class InterruptThread1 extends Thread {
    public void run() {
        Thread thread2 = new InterruptThread2();
        // 启动hello线程
        thread2.start();
        try {
            // 等待hello线程结束,如果中断会抛异常
            thread2.join();
        } catch (InterruptedException e) {
            System.out.println("interrupted!" + new Date());
        }

        // 不将thread2中断,还会继续
        thread2.interrupt();
    }
}

public class InterruptThread2 extends Thread {
    public void run() {
        int n = 0;
        while (!isInterrupted()) {
            n++;
            System.out.println(n + " InterruptThread2!" + new Date());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                break;
            }
        }
    }
}

中断也可通过设置标志位来实现

通过关键词volatile将其标记为线程间共享的变量

public static void main(String[] args) throws InterruptedException {
    InterruptThread3 thread = new InterruptThread3();
    thread.start();
    Thread.sleep(1);
    thread.running = false;
}

public class InterruptThread3 extends Thread {
    public volatile boolean running = true;

    @Override
    public void run() {
        int n = 0;
        while (running) {
            n ++;
            System.out.println(n + " hello!");
        }
        System.out.println("end!");
    }
}

2. wait notify

wait和notify用于多线程协调运行。

在synchronized内部可以调用wait()使线程进入等待状态,调用notify()或notifyAll()唤醒其他等待线程;
必须在已获得的锁对象上调用wait()方法,调用notify()或notifyAll()方法;
已唤醒的线程还需要重新获得锁后才能继续执行。

启动两个线程,一个输出 1,3,5,7…99,另一个输出 2,4,6,8…100,最后按序输出 1,2,3,4,5…100

private static final Object lock = new Object();
private volatile int i = 1;

@Test
public void test1() {
    Thread thread1 = new Thread(() -> {
        while (i <= 100) {
            synchronized (lock) {
                // 偶数则等待
                if (i % 2 == 0) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 奇数打印并判断
                } else {
                    System.out.print(i + " ");
                    i++;
                    lock.notifyAll();
                }
            }
        }
    });

    Thread thread2 = new Thread(() -> {
        while (i <= 100) {
            synchronized (lock) {
                // 奇数则等待
                if (i % 2 != 0) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 偶数打印并判断
                } else {
                    System.out.print(i + " ");
                    i++;
                    lock.notifyAll();
                }
            }
        }
    });

    thread1.start();
    thread2.start();
}

也可以这样:

@Test
public void test2() {
    Thread t1 = new Thread(() -> {
        while(i <= 100) {
            synchronized (lock) {
                if (i % 2 == 0) {
                    System.out.print(i++);
                    System.out.print(" ");
                } else {
                    // 奇数,就wait,释放锁
                    try{
                        lock.wait();
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    });

    Thread t2 = new Thread(() -> {
        while(i <= 100) {
            synchronized (lock) {
                if (i % 2 != 0) {
                    System.out.print(i++);
                    System.out.print(" ");
                } else {
                    // 偶数,就notify,会唤醒上方锁等待的线程
                    lock.notifyAll();
                }
            }
        }
    });

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

两个线程,一个线程输出奇数,一个线程输出偶数,保证输出顺序是:2、1、4、3、……、50、49、52、51、54、53、……、100、99

private static final Object lock = new Object();
private volatile int count = 2;

@Test
public void test3() {
    Thread t1 = new Thread(() -> {
        while(count <= 100) {
            synchronized (lock){
                // 偶数则打印,并减1
                if (count % 2 == 0) {
                    System.out.print(count + " ");
                    count--;

                    // 需要notify,否则两个线程执行一次后都进行等待
                    lock.notifyAll();

                    if (count <= 100) {
                        try{
                            lock.wait();
                        }catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    });

    Thread t2 = new Thread(() -> {
        while(count <= 100) {
            synchronized (lock){
                // 奇数则打印,并加3
                if (count % 2 != 0) {
                    System.out.print(count + " ");
                    count += 3;

                    // 需要notify,否则两个线程执行一次后都进行等待
                    lock.notifyAll();

                    if (count <= 100) {
                        try{
                            lock.wait();
                        }catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    });

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

3. ReentrantLock Condition

ReentrantLock可以替代synchronized进行同步,并且获取锁更安全;必须先获取到锁,再进入try {…}代码块,最后使用finally保证释放锁;可以使用tryLock()尝试获取锁。

用ReentrantLock,可以使用Condition对象来实现,等同于synchronized,wait和notify的功能

启动两个线程,一个输出 1,3,5,7…99,另一个输出 2,4,6,8…100,最后按序输出 1,2,3,4,5…100

使用ReentrantLock和Condition:

private static final Lock reentrantLock = new ReentrantLock();
private static final Condition condition = reentrantLock.newCondition();
private volatile int i = 1;

@Test
public void reentrantLockTest() {
    Thread thread1 = new Thread(() -> {
        while (i <= 100) {
            reentrantLock.lock();
            try {
                // 偶数则等待
                if (i % 2 == 0) {
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 奇数打印并判断
                } else {
                    System.out.print(i + " ");
                    i++;
                    condition.signalAll();
                }
            } finally {
                reentrantLock.unlock();
            }
        }
    });

    Thread thread2 = new Thread(() -> {
        while (i <= 100) {
            reentrantLock.lock();
            try {
                // 齐数则等待
                if (i % 2 != 0) {
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 偶数打印并判断
                } else {
                    System.out.print(i + " ");
                    i++;
                    condition.signalAll();
                }
            } finally {
                reentrantLock.unlock();
            }
        }
    });

    thread1.start();
    thread2.start();
}

4. ReadWriteLock

使用ReadWriteLock可以保证:只允许一个线程写入(其他线程既不能写入也不能读取);没有写入时,多个线程允许同时读(提高性能)。

如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,即读的过程中不允许写,这是一种悲观的读锁。

private static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static final Lock readLock = readWriteLock.readLock();
private static final Lock writeLock = readWriteLock.writeLock();

/**
 * 获取写锁
 * @param i
 */
private void setI (Integer i) {
    writeLock.lock();
    try {
        this.i = i;
    } finally {
        writeLock.unlock();
    }
}

/**
 * 获取读锁
 * @return
 */
private Integer getI () {
    readLock.lock();
    try {
        return i;
    } finally {
        readLock.unlock();
    }
}

5. StampedLock

StampedLock和ReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入!这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入,这种读锁是一种乐观锁。

乐观锁的意思就是乐观地估计读的过程中大概率不会有写入,因此被称为乐观锁。反过来,悲观锁则是读的过程中拒绝有写入,也就是写入必须等待。显然乐观锁的并发效率更高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行。

StampedLock提供了乐观读锁,可取代ReadWriteLock以进一步提升并发性能;StampedLock是不可重入锁。

private static final StampedLock stampedLock = new StampedLock();
private Integer x;
private Integer y;

private void set(Integer x , Integer y) {
    long stamp = stampedLock.writeLock();
    try {
        x += x;
        y += y;
    } finally {
        stampedLock.unlockWrite(stamp);
    }
}

private Integer get() {
    // 先获取乐观读锁
    long stamp = stampedLock.tryOptimisticRead();
    Integer newX = x;
    Integer newY = y;

    // 判断获取乐观读锁后是否有获取其他写锁
    if (!stampedLock.validate(stamp)) {
        // 有获取其他写锁,则获取悲观读锁。写锁需要等悲观读锁释放后,才能写
        stamp = stampedLock.readLock();
        try {
            // 重新赋值
            newX = x;
            newY = y;
        } finally {
            stampedLock.unlockRead(stamp);
        }
    }

    return newX * newY;
}

4. ExecutorService

ExecutorService接口表示线程池,Java标准库提供的几个常用实现类有:
FixedThreadPool:线程数固定的线程池,任务超过则等待;
CachedThreadPool:线程数根据任务动态调整的线程池;
SingleThreadExecutor:仅单线程执行的线程池,定期反复执行。

@Test
public void executorServiceTest() {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    for (int i = 1; i <= 3; i++) {
        executorService.submit(new Task(i + ""));
    }

    executorService.shutdown();
}

public class Task implements Runnable {
    private final String name;

    public Task(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(new Date() + " start " + name);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(new Date() + " end " + name);
    }
}

5. Future

一个Future接口表示一个未来可能会返回的结果,可以用来获取线程返回值。

当我们提交一个Callable任务后,我们会同时获得一个Future对象,然后,我们在主线程某个时刻调用Future对象的get()方法,就可以获得异步执行的结果。

在调用get()时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么get()会阻塞,直到任务完成后才返回结果。

@Test
public void callableTest() {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    Callable<String> callable = new TaskCallable();
    Future<String> future = executorService.submit(callable);
    String returnStr = "";
    try {
        returnStr = future.get();
    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println(returnStr);
}

public class TaskCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "TaskCallable";
    }
}

6. CompletableFuture

CompletableFuture针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

CompletableFuture可多个串行或并行执行

@Test
public void CompletableFutureTest() {
    while (true) {
        CompletableFuture<Double> completableFuture = CompletableFuture.supplyAsync(ThreadTest::fetchPrice);
        // 成功
        completableFuture.thenAccept((result) -> {
            System.out.println(result);
        });
        // 失败
        completableFuture.exceptionally((e) -> {
            e.printStackTrace();
            return null;
        });
    }
}

static Double fetchPrice() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
    }
    if (Math.random() < 0.3) {
        throw new RuntimeException("fetch price failed!");
    }
    return 5 + Math.random() * 20;
}

参考:
Java 多线程
编程题:两个线程,一个线程输出奇数,一个线程输出偶数,保证输出顺序是:2、1、4、3、……、50、49、52、51、54、53、……、100、99
你真的懂wait、notify和notifyAll吗

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值