Java多线程学习总结

MyThread01 线程创建

package com.yenroc.ho.多线程;

public class MyThread01 {

    public static void main(String[] args) throws InterruptedException {
        System.out.print(Thread.currentThread().getName() + "\tmain running..\n");

        // 线程类start 执行线程类的run 方法
        Thread t = new MyThread();
        // t.setDaemon(true); // 把该线程标记为守护线程
        t.start();// 启动一个线程

        // java 8 lambda 表达式简写
        Thread t2 = new Thread(()->{
            System.out.print(Thread.currentThread().getName() + "\tlambda Thread running..\n");
            try {
                Thread.sleep(100L);
            } catch (java.lang.InterruptedException e){

            }
            System.out.print(Thread.currentThread().getName() + "\tlambda Thread end..\n");
        });
        t2.start();

        // 执行实现Runnable类。
        Thread t3 = new Thread(new MyRunnable());
        t3.start();

        t2.join();// 只有当t2 线程执行完,main 线程才会继续
        // t.interrupt(); // 中断t线程

        System.out.print(Thread.currentThread().getName() + "\tmain end..\n");

    }
    
    /**
     * 线程的状态:
     *  New: 新建
     *  Runnable : 运行中的线程
     *  Blocked: 运行中的线程,因为某些操作被阻塞挂起
     *  waiting: 运行中的线程,因为某写操作在等待中
     *  Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待
     *  Terminated: 终止的线程
     */
    private void threadStatus() {}
}

// 1.继承Thread 类,重写run方法
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.print(Thread.currentThread().getName() + "\tMyThread class is running..\n");
        try {
            Thread.sleep(500L);
        } catch (java.lang.InterruptedException e){

        }
        System.out.print(Thread.currentThread().getName() + "\tMyThread class is end..\n");
    }
}

// 2. 实现Runnable接口,
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.print(Thread.currentThread().getName() + "\tMyRunnable class is running..\n");
        try {
            Thread.sleep(500L);
        } catch (java.lang.InterruptedException e){

        }
        System.out.print(Thread.currentThread().getName() + "\tMyRunnable class is end..\n");
    }
}

创建线程

Thread t = new MyThread();// 编写继承Thread类的实现类MyThread,实现run方法
Thread t2 = new Thread(()->{方法体});// 根据lambda 表达式简写
Thread t3 = new Thread(new MyRunnable());// 执行实现Runnable的类

t2.join();// 只有当t2 线程执行完,主线程才会继续

中断线程运行

t.interrupt(); // 中断t线程的执行

守护线程

t.setDaemon(true); // 把该线程标记为守护线程,需要注意守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失

volatile

volatile关键字解决了共享变量在线程间的可见性问题:当一个线程修改了某个共享变量的值,其他线程能够立刻看到修改后的值。
在x86的架构下,JVM回写主内存的速度非常快,但是,换成ARM的架构,就会有显著的延迟

MyThread02 线程同步

package com.yenroc.ho.多线程;

/**
 * synchronized 关键字保证一段代码的原子性
 */
public class MyThread02 {
    public static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread addThread = new Thread(()->{
            for (int i=0; i<10000; i++) {
                synchronized (MyThread02.class) {
                    count += 1;
                }
            }
        });
        Thread decThread = new Thread(()->{
            for (int i=0; i<10000; i++) {
                synchronized (MyThread02.class) {
                    count -= 1;
                }
            }
        });
        addThread.start();
        decThread.start();

        addThread.join();
        decThread.join();

        System.out.print(Thread.currentThread().getName() + "\t count=" + count);
    }
}

synchronized

使用synchronized 关键字保证一段代码的原子性。

可重入锁

JVM允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁,就叫做可重入锁
由于Java的线程锁是可重入锁,所以,获取锁的时候,不但要判断是否是第一次获取,还要记录这是第几次获取。每获取一次锁,记录+1,每退出synchronized块,记录-1,减到0的时候,才会真正释放锁

死锁的产生

线程1 获取A锁之后,继续获取B锁。。
线程2 获取B锁之后,继续获取A锁。。
当线程1 获取A锁后,线程2同时获取到B锁,此时线程1需要获取B锁,线程2需要获取A锁。
此时,两个线程各自持有不同的锁,然后各自试图获取对方手里的锁,造成了双方无限等待下去,这就是死锁。
避免死锁: 线程2的逻辑优化成先获取A锁再获取B锁。。

MyThread03 多线程协调wait和notify

package com.yenroc.ho.多线程;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 多线程间的协调:wait().notify()
 */
public class MyThread03 {

    public static void main(String[] args) {
        QueueList<String> queueList = new QueueList<>();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(()->{
                String s = queueList.get();
                System.out.print(Thread.currentThread().getName() + "\tget s="+s+" \n");
            });
            t.start();
        }
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(()->{
                queueList.add(Thread.currentThread().getName());
                System.out.print(Thread.currentThread().getName() + "\t add s="+Thread.currentThread().getName()+" \n");
            });
            t.start();
        }
    }

}

class QueueList<T> {

    private Queue<T> queue = new LinkedList();

    public synchronized void add(T t){
        queue.add(t);
        // 使用notifyAll()将唤醒所有当前正在this锁等待的线程,而notify()只会唤醒其中一个
        this.notifyAll();// 这个方法会唤醒一个正在this锁等待的线程
    }

    public synchronized T get() {
        while (queue.isEmpty()) {
            try {
                this.wait();//当一个线程在this.wait()等待时,它就会释放this锁,从而使得其他线程能够在add()方法获得this锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return queue.remove();
    }
}

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

当一个线程在this.wait()等待时,它就会释放this锁,从而使得其他线程能够在add()方法获得this锁
这个方法会唤醒一个正在this锁等待的线程
在synchronized内部可以调用wait()使线程进入等待状态;
必须在已获得的锁对象上调用wait()方法;
在synchronized内部可以调用notify()或notifyAll()唤醒其他等待线程;
必须在已获得的锁对象上调用notify()或notifyAll()方法;
已唤醒的线程还需要重新获得锁后才能继续执行

java.util.concurrent

接口线程不安全线程安全
ListArrayListCopyOnWriteArrayList
MapHashMapConcurrentHashMap
SetHashSet / TreeSetCopyOnWriteArraySet
QueueArrayDeque / LinkedListArrayBlockingQueue / LinkedBlockingQueue
DequeArrayDeque / LinkedListLinkedBlockingDeque

Atomic

Atomic类是通过无锁(lock-free)的方式实现的线程安全(thread-safe)访问。
它的主要原理是利用了CAS.
Atomic的类在 java.util.concurrent.atomic 包下面,如:AtomicBoolean,AtomicInteger,AtomicLong...

CAS(compare-and-swap)

乐观锁主要采用CAS算法,
思想:获取当前变量最新值A(预期值),然后进行CAS操作。
    此时如果内存中变量的值V(内存值V)等于预期值A,说明没有被其他线程修改过,
    我就把变量值改成B(更新值);如果不是A,便不再修改。
    CAS的缺点:
        1. ABA问题:可以参考数据库乐观锁的处理,加版本号
        2. 自旋时间长带来性能消耗: (自旋)重试策略,

代码简单实现说明:

// ++i 的实现
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
        return next;
    }
}
// 其中 compareAndSet方法:
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 其中unsafe.compareAndSwapInt 方法类似:
if (this == expect) {
    this = update
    return true;
} else {
    return false;
}

使用java.util.concurrent.atomic提供的原子操作可以简化多线程编程
原子操作实现了无锁的线程安全;
适用于计数器,累加器等

MyThread04 线程池

package com.yenroc.ho.多线程;

import java.util.Date;
import java.util.concurrent.*;

/**
 * 线程池
 */
public class MyThread04 {

    public static void main(String[] args) {
        // 创建线程池:创建这些线程池的方法都被封装到Executors这个类中
        // 固定大小的线程池
        ExecutorService executors = Executors.newFixedThreadPool(3);
        // 动态线程池 ExecutorService executors = Executors.newCachedThreadPool();
//        ExecutorService executors = new ThreadPoolExecutor(4,64,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

        // 反复执行的线程池(定时执行,线程池不要shutdown)
        ScheduledExecutorService  sExecutors = Executors.newScheduledThreadPool(2);
        // 延迟1s后只执行一次
        sExecutors.schedule(new MyTaskThread(),1, TimeUnit.SECONDS);

        // 延迟2秒后开始执行定时任务,每3秒执行(当上一个线程执行完成才会继续下一个执行,如果线程执行5s结束,那么则每次执行间隔是5s)
        sExecutors.scheduleAtFixedRate(new MyTaskThread(),2, 3, TimeUnit.SECONDS);

        // 2秒后开始执行定时任务,以3秒为间隔执行:(当上一个线程执行完成才会继续下一个执行,如果线程执行5s结束,则需要再间隔3s,5+3s后执行)
        sExecutors.scheduleWithFixedDelay(new MyTaskThread(),2, 3, TimeUnit.SECONDS);

        for (int i = 0; i < 10; i++) {
            Thread t = new MyTaskThread();
            executors.submit(t);
        }
        System.out.println("main Thread end.. ");
        // 关闭线程池:
        executors.shutdown();
    }
}

class MyTaskThread extends Thread {
    @Override
    public void run() {
        System.out.printf("线程[%s]\t 当前时间=[%s] \n",Thread.currentThread().getName(), new Date());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Executors 类进行创建线程池

线程池在程序结束的时候要关闭。使用shutdown()方法关闭线程池的时候,它会等待正在执行的任务先完成,然后再关闭

FixedThreadPool:线程数固定的线程池;
CachedThreadPool: 会根据任务数量动态调整线程池的大小
       可以通过ExecutorService executors = new ThreadPoolExecutor(4,64,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>())
ScheduledThreadPool: 反复执行的线程池
    // 延迟1s后只执行一次
    executors.schedule(new MyTaskThread(),1, TimeUnit.SECONDS);
    
    // 延迟2秒后开始执行定时任务,每3秒执行(当上一个线程执行完成才会继续下一个执行,如果线程执行5s结束,那么则每次执行间隔是5s)
    executors.scheduleAtFixedRate(new MyTaskThread(),2, 3, TimeUnit.SECONDS);
    
    // 延迟2秒后开始执行定时任务,以3秒为间隔执行:(当上一个线程执行完成才会继续下一个执行,如果线程执行5s结束,则需要再间隔3s,5+3s后执行)
    // 上一次任务执行完毕后,等待固定的时间间隔,再执行下一次任务
    executors.scheduleWithFixedDelay(new MyTaskThread(),2, 3, TimeUnit.SECONDS);

MyThread05 返回结果的线程池

package com.yenroc.ho.多线程;

import java.util.concurrent.*;

/**
 * 带返回结果的线程,Future,CompletableFuture
 */
public class MyThread05 {

    public static void main(String[] args) throws Exception {
        // 1. Future
        ExecutorService executors = Executors.newFixedThreadPool(3);
        Future<String> myTaskThreadCallableFuture = executors.submit(new MyTaskThreadCallable());
        executors.submit(new My05Runnable());
        // 执行get 会阻塞线程,需要放到下面执行
        System.out.println(myTaskThreadCallableFuture.get(550L,TimeUnit.MILLISECONDS));

        executors.shutdown();


        // 2. CompletableFuture
        CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> { // Supplier 接口 T get();
            System.out.println("CompletableFuture 执行逻辑。。。");
            try {
                Thread.sleep(500L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // int i = 1/0; // 进入exceptionally 打印CompletableFuture 执行逻辑异常,异常信息=[java.lang.ArithmeticException: / by zero]
            return "success";
        });
        stringCompletableFuture.thenAccept((result)->{// Consumer 接口 void accept(T t);
            System.out.printf("stringCompletableFuture 执行逻辑成功,返回结果=[%s] \n", result);
        });
        stringCompletableFuture.exceptionally(e->{// Function 接口 R apply(T t);
            System.out.printf("stringCompletableFuture 执行逻辑异常,异常信息=[%s] \n", e.getMessage());
            return null;
        });

        // anyOf()可以实现“任意个CompletableFuture只要一个成功”,allOf()可以实现“所有CompletableFuture都必须成功”,这些组合操作可以实现非常复杂的异步流程控制
        CompletableFuture<String> stringCompletableFuture2 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(800L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "stringCompletableFuture2 success";
        });
        CompletableFuture<Object> objectCompletableFuture = CompletableFuture.anyOf(stringCompletableFuture, stringCompletableFuture2);
        objectCompletableFuture.thenAccept((result)->{// Consumer 接口 void accept(T t);
            System.out.printf("stringCompletableFuture,stringCompletableFuture2 执行逻辑成功,返回结果=[%s] \n", result);
        });

        // 主线程不能直接结束
        Thread.sleep(1000L);
    }

}

/**
 * 实现Runnable的没有办法获取返回值
 */
class My05Runnable implements Runnable {
    @Override
    public void run() {
        System.out.print(Thread.currentThread().getName() + "\tMyRunnable class is running..\n");
        try {
            Thread.sleep(500L);
        } catch (java.lang.InterruptedException e){
            e.printStackTrace();
        }
        System.out.print(Thread.currentThread().getName() + "\tMyRunnable class is end..\n");
    }
}

/**
 * 实现Callable的支持 获取返回值
 */
class MyTaskThreadCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.print(Thread.currentThread().getName() + "\tMyTaskThreadCallable class is running..\n");
        try {
            Thread.sleep(500L);
        } catch (java.lang.InterruptedException e){
            e.printStackTrace();
        }
        System.out.print(Thread.currentThread().getName() + "\tMyTaskThreadCallable class is end..\n");
        return "MyTaskThreadCallable run is success.";
    }
}

Future

当我们提交一个Callable任务后,可以获取返回的Future类型的对象,Future类型的实例代表一个未来能获取结果的对象。

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

Future接口表示一个未来可能会返回的结果,它定义的方法有

get():获取结果(可能会等待)
get(long timeout, TimeUnit unit):获取结果,但只等待指定的时间;
cancel(boolean mayInterruptIfRunning):取消当前任务;
isDone():判断任务是否已完成。

CompletableFuture

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

创建一个CompletableFuture是通过CompletableFuture.supplyAsync()实现的,它需要一个实现了Supplier接口的对象.

线程执行成功回调的实例:completableFuture.thenAccept(),完成时,CompletableFuture会调用Consumer对象

线程执行异常回调的实例:completableFuture.exceptionally(), 异常时,CompletableFuture会调用 Function对象

anyOf()可以实现“任意个CompletableFuture只要一个成功”,
allOf()可以实现“所有CompletableFuture都必须成功”

CompletableFuture的优点
    异步任务结束时,会自动回调某个对象的方法;
    异步任务出错时,会自动回调某个对象的方法;
    主线程设置好回调后,不再关心异步任务的执行
CompletableFuture的命名规则
    xxx():表示该方法将继续在已有的线程中执行;
    xxxAsync():表示将异步在线程池中执行。

MyThread06 线程变量ThreadLocal

package com.yenroc.ho.多线程;


import java.util.Date;

/**
 * ThreadLocal
 */
public class MyThread06 {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new MyThreadLocal()).start();
        }
    }

}


class MyThreadLocal implements Runnable {
    // 每个线程获取ThreadLocal变量时,总是使用Thread自身作为key
    // 因此,ThreadLocal相当于给每个线程都开辟了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不干扰
    protected static ThreadLocal<String> name = new ThreadLocal<>();

    @Override
    public void run() {
        try {
            initName();
            printName();
            Thread.sleep(1000);
            printName2();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            clearName();// 线程结束要销毁线程变量
        }
    }

    private void initName(){
        name.set(Thread.currentThread().getName() + new Date().toString());
    }
    private void clearName(){
        name.remove();
    }

    private void printName(){
        System.out.println("printName="+ name.get());
    }

    private void printName2(){
        System.out.println("printName2="+ name.get());
    }
}

每个线程获取ThreadLocal变量时,总是使用Thread自身作为key
因此,ThreadLocal相当于给每个线程都开辟了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不干扰
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值