java 多线程

线程与进程

  • 线程
    • 一个进程可以产生多个线程
    • 多个线程共享一块内存空间与系统资源
  • 进程
    • 程序的一次执行过程
    • 系统运行程序基本单位

多线程

  • 概念
    • 多个线程同时/交替运行
    • 如果是单核CPU则顺序执行,即交替执行
    • 如果是多核CPU则同时运行
  • 优点
    • 可以将占据长时间的程序放到后台处理
    • 加快程序运行速度

怎么使用多线程

  • 继承Thread类,重写run()方法
public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("MyThread");
    }
}
public class Test{
    public static void main(String args[]) {
        Thread thread = new MyThread();
        thread.start();
    }
}
  • 实现Runnable接口,实现run()方法 推荐使用
public class MyRunnable extends Runnable {
    @Override
    public void run() {
        super.run();
        System.out.println("MyRunnable");
    }
}
public class Test{
    public static void main(String args[]) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
  • 实现Callable<返回对象>接口,实现call()方法,可以使用FutureTask<>接受返回值
public class MyCallable extends Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("MyCallable");
        return 1;
    }
}
public class Test{
    public static void main(String args[]) {
        Callable callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread thread = new MyThread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }
}

简单的共享属性

public class MyThread extends Thread {
    int count = 0;
    @Override
    public synchronized void run() {
        super.run();
        count++;
        System.out.println("MyThread");
    }
}
public class Test{
    public static void main(String args[]) {
        Thread thread = new MyThread();
        Thread a = new Thread(thread, "A");
        Thread b = new Thread(thread, "B");
        a.start();
        b.start();
    }
}

终止线程的方法

  • interrupt() -> interrupted()
  • return;

线程状态

这里写图片描述
这里写图片描述

常用方法

方法名描述
currentThread()返回当前执行线程的引用
getId()返回此线程的标识符
setName(String name)更改线程名称
getName()返回此线程的名称
setPriority(int newPriority)更改线程优先级
getPriority()返回此线程的优先级
siAlive()当前线程是否处于活动状态
sleep(long millis)是当前线程限期休眠
interrupt()为这个线程打上中断标识
interrupted()测试当前线程是否为中断状态,若为中断状态则中断该线程并清除状态标识
isInterrupted()测试当前线程是否有中断标识
isDaemon()测试当前线程是否为守护线程
setDaemon(bolean on)设置当前线程为守护线程/用户线程
join()等待该线程终止,再返回调用这个方法的线程
yield()放弃当前CPU资源,让给其他任务.放弃时间不确定即有可能放弃后马上获取到CPU时间片

优先级

  • 优先级具有随机性,并不一定优先级高的先执行
  • 分级
    • Thread.MIN_PRIORITY(1)
    • Thread.ORM_PRIORITY(5) 默认
    • Thread.MAX_PRIORITY(10)

守护线程

  • 运行在后台为用户线程服务
  • 当所有用户线程都结束,守护线程会随着JVM结束
  • 例如: 垃圾回收线程
  • 注意:
    • setDeamon(true)需要在start()之前执行
    • 守护线程中产生的线程也是守护线程
    • 读写/逻辑运算不能交给守护线程

synchronized

  • 对象锁:锁定的是对象
    • synchronized public void method() {}
    • synchronized(this){…}: 代码块,synchronized锁定方法(尤其是比较大的方法)会导致另一个线程长时间的等待,最好使用synchronized锁定代码块
public class Task {
    private String data1;
    private String data2;
    public void do() {
        system.out.println("start");
        synchronized(this) {
            data1 = "data1" + Thread.currentThread().getName();
            data2 = "data2" + Thread.cyrrentThread().getName();
        }
        system.out.println(data1);
        system.out.println(data2);
    }
}
public MyThread extends Thread {
    private Task task;
    public MyThread(Task task) {
        super();
        this.task = task;
    }
    @Override
    public void run() {
        super.run();
        task.do();
        system.out.println("end");
    }
}
public Run {
    public static void main(String args[]) {
        Task task = new Task;
        MyThread thread1 = new MyThread(task);
        Mythread thread2 = new Mythread(task);
        thread1.start();
        thread2.start();
    }
}
  • synchronized(object){…}: 使用相同的对象监视器可以使得运行同步,若使用不同的对象监视器则会与没有同步块一样
public class MyObject {
}
public class Task {
    private String data1;
    private String data2;
    public void do(MyObject object) {
        sychronized(object) {
            try{
                system.out.println("start");
                data1 = "data1" + Thread.currentThread().getName();
                data2 = "data2" + Thread.cyrrentThread().getName();
                system.out.println(data1);
                system.out.println(data2);
            } finaly { }
        }
    }
}
public MyThread extends Thread {
    private Task task;
    private MyObject object;
    public MyThread(Task task, MyObject object) {
        super();
        this.task = task;
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        task.do(object);
        system.out.println("end");
    }
}
public Run {
    public static void main(String args[]) {
        Task task = new Task;
        MyObject object = new MyObject;
        MyThread thread1 = new MyThread(task, object);
        Mythread thread2 = new Mythread(task, object);
        thread1.start();
        thread2.start();
    }
}
  • 类锁:锁定的是类(静态方法)
    • public static void method() { synchronized(className.class){…}}
    • synchronized public static void method(){}
  • 同步不具备继承性:父类的同步方法,子类继承重写不具备同步需重写添加
  • 一个对象只有一个锁:当A线程有Object对象的锁,当B线程调用Object对象中synchronized方法时需要等待,若只是调用非synchronized方法则可以异步调用.
  • 锁重入机制:只要线程拥有某个对象锁,则可以在拥有这个锁的期间不断重新获得这个锁
  • 异常自动释放锁:当线程执行出现异常,此线程持有的锁会自动释放

volatile

这里写图片描述
- 被volatile修饰的成员变量,每次被线程访问时都将从主内存中重读值,发生变化时都将写到主内存中.保证数据的可见性
- 在while语句中加了输出/sleep()语句,JVM会尽力的保证内存的可见性即使没有volatile修饰关键字,也可以退出while循环
- 不能保证原子性

wait/notify机制

  • 线程通过调用对象Object.wait()使线程进入等待状态,而其他线程通过Object.notify()唤醒线程(这个Object类似于一个lock)
  • 方法
方法名描述
wait()调用该方法的线程释放资源,进入等待队列
wait(long timeout)超时等待, 超出时间就返回不需要notify
wait(long timeout, int nanos更好的操作粒度
notify()随机唤醒一个等待这个资源的线程
notifyAll()唤醒所有等待这个资源的线程,是按优先级/随机,看JVM
  • 与synchronized相配合,将Object作为关键字.wait()方法会释放同步的锁.notify()不会释放同步的锁.
  • 如果wait()状态遇到interrupt标识会报InterruptException

ThreadLocal

  • 线程本地变量
  • 在不同线程中调用同一个ThreadLocal类的值(方法)具有隔离性
  • ThreadLocal其实是操作每个线程的ThreadLocalMap
  • InheritableThreadLocal类: 是ThreadLcoal的升级,如果父线程使用过该对象,则其子线程将延续使用父线程的记录.
  • 方法
方法名描述
get()返回当前线程中此变量的值
set(T value)设置当前线程中此变量的值
remove()删除当前线程中此变量的值
initialValue()返回当前线程此变量的初始值

ThreadGroup

  • 线程组: 批量管理线程/线程组
  • 初始化
    • ThreadGroup(String name)
    • ThreadGroup(ThreadGroup parent, String name)
  • 线程启动后才能回归到制定线程组

组件

  • CountDownLatch:机制是多个线程达到预期状态通过CountDownLatch方法将通知时间合一,使等待线程开始任务(多个线程)
  • Semaphore:并发管理器,控制部分代码,通过acquire()获取信号,release()归还信号
  • Exchanger:两个线程间的数据交换,需要两个线程都运行到交换处再交换(先到的阻塞)
  • CyclicBarrier:像一堵墙让多个线程在这等待,直到所有线程到达再一起继续

Lock

  • Lock接口的实现类
    • ReentrantLock(排他锁)
    • ReenteantReadWriteLock(读写锁)
      具有两个锁,一个共享锁(读操作),一个排它锁(写操作)
  • Lock接口的基本方法
方法名描述
void lock()获得锁,若锁不可用则休眠知道获得锁. 最好在try方法块外调用
void unlock()释放锁. 在finally中调用
boolean tryLock()尝试获取锁,若可用则获取锁并返回true,若不可用则返回false
boolean tryLock(long time, TimeUnit unit)超时获取锁. 1.在time时间内获取锁 2.time时间内被中断 3.超出time返回false
void lockInterruptibly()获取锁,如果可用就立即返回.若不可用则休眠(可以中断当前线程),直至获得锁
Condition newCondition()获得等待通知组件. 组件与当前锁绑定,只有拥有锁才能调用改组件的wait()方法并将释放锁
  • ReentrantLock类方法
方法名描述
ReentrantLock()
ReentrantLock(boolean fair)公平锁(FIFO)/非公平锁(随机,Lock默认)
int getHoldCount()查询当前线程保持这个锁的个数
protected Thread getOwner()返回当前拥有此锁的线程
protected Collection getQueuedThreads()返回正在等待此锁的线程的集合
int getQueueLength()返回等待此锁线程数的估计
boolean hasWaiters(Condition condition)查询是否有在某种等待条件下等待此锁的线程
protected Collection getWaitingThreads(Condition condition)返回在某种等待条件下等待此锁的线程的集合
int getWaitQueueLength(Condition condition)返回在某种等待条件下等待此锁的线程的集合
boolean hasQueuedThread(Thread thread)查询特定线程是否在等待获取锁
boolean hasQueuedThreads查询是否有线程在等待获取锁
boolean isFair()查询此锁是否是公平的
boolean isHeldByCurrentThread()查询当前线程是否持有此锁
boolean isLocked()查询此锁是否有任何线程持有
  • ReentrantReadWriteLock类方法
方法名描述
ReentrantReadWriteLock()
ReentrantReadWriteLock(boolean fair)公平锁(FIFO)/非公平锁(随机,Lock默认)

Condition接口

  • 类似于wait()/notify(), 可以实现多路通知(一个Lock可以创建多个Condition)
  • 方法
方法名描述
void await()相当于wait()
boolean await(long time, TimeUnit unit)相当于wait(long timeout)
singnal()相当于notify()
signalAll()相当于notifyAll()

线程池

  • 作用: 限制/管理资源,维护统计信息.
  • 优点:
    • 降低资源消耗
    • 提高响应速度
    • 提高线程的可管理性
  • Executor接口框架
    • 结构
      • 任务: 实现Runnable/Callable接口.被ThreadPoolExecutor/ScheduledThreadPoolExecutor接受
      • 任务的执行: 继承Executor接口的ExecutorService接口.ScheduledThreadPoolExecutor/ThreadPoolExecutor实现了ExecutorService接口. ScheduledThreadPoolExecutor实际上继承了ThreadPoolExecutor.
      • 异步计算结果: Future接口及FutureTask类.用Runnable/Callable接口调用submit方法给ThreadPoolExecutor/ScheduledThreadPoolExecutor时会返回FutureTask对象
    • 使用
      • 主线程创建Runnable/Callable接口的对象(Executors工具类可以将两者互相转换)
      • 将Runnable/Callable对象交给ExecutorService执行(ExecutorService.execute(Runnable runnable)无返回值 / ExecutorService.submit(Runnable/Callable)有返回值)
      • 用FutureTask接受ExecutorService.submit(Runnable/Callable)的执行结果
      • 主线程执行FutureTask.get()来获得返回值 / 执行FutureTask.cancel()取消此次任务的执行
  • ThreadPoolExecutor类

    • 构造函数

          /**
           * 用给定的初始参数创建一个新的ThreadPoolExecutor。
      
           * @param corePoolSiza 核心线程池的大小
           * @param maximumPoolSize 最大线程池的大小
           * @param keepAliveTime 当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
           * @param unit  keepAliveTime参数的时间单位
           * @param workQueue 等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列;
           * @param threadFactory 执行者创建新线程时使用的工厂
           * @param handler RejectedExecutionHandler类型的变量,表示线程池的饱和策略。
           * 如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。
           * 线程池提供了4种策略:
              1.AbortPolicy:直接抛出异常,这是默认策略;
              2.CallerRunsPolicy:用调用者所在的线程来执行任务;
              3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
              4.DiscardPolicy:直接丢弃任务;
           */
          public ThreadPoolExecutor(int corePoolSize,
                                    int maximumPoolSize,
                                    long keepAliveTime,
                                    TimeUnit unit,
                                    BlockingQueue<Runnable> workQueue,
                                    ThreadFactory threadFactory,//可忽略
                                    RejectedExecutionHandler handler//可忽略) {
              if (corePoolSize < 0 ||
                  maximumPoolSize <= 0 ||
                  maximumPoolSize < corePoolSize ||
                  keepAliveTime < 0)
                  throw new IllegalArgumentException();
              if (workQueue == null || threadFactory == null || handler == null)
                  throw new NullPointerException();
              this.corePoolSize = corePoolSize;
              this.maximumPoolSize = maximumPoolSize;
              this.workQueue = workQueue;
              this.keepAliveTime = unit.toNanos(keepAliveTime);
              this.threadFactory = threadFactory;
              this.handler = handler;
          }
    • 创建ThreadPoolExecutor

      • ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, [threadFactory], [handler])共四种方法
      • Executors工具类生成(实际上调用了ThreadPoolExecutor的构造方法)
        • Executors.newFixedThreadPool(int, [threadFactory])
          即ThreadPoolExecutor(int, int, 0L, TimeUnit.MILLSECONDS, new LinkedBlockigQueue(), threadFactory)
          可重用固定线程数的线程池, LinkedBlockigQueue是无边界的(容量Integer.MAX_VALUE),新任务都将直接到队列中
          newFixedThreadPool
        • Executors.newSignleThreadExecutor([threadFactory])
          即ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), threadFactory)
          单线程的线程池, 同样而是无边界的任务队列
          newSignleThreadExecutor
        • Executors.newCachedThreadPool([threadFactory])
          即ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, new SynchronousQueue(), threadFactory)
          无边界线程数的线程池
          初试线程数为0,而线程数上限为无边界.
          newCachedThreadPool
    • 使用ThreadPoolExecutor

      public cass MyThreadPoolExecutor {
          public static void main(String args[]) {
              ExecutorService executor = new Executors.newFixedThreadPool(5);
              Runnable runnable = new MyRunnable();
              executor.execute(runnable);
              executor.shutdown();
              while(!executor.isTerminated()){}
          }
      }
      • shutdown()与shutdownNow()
        shutdown()表明关闭已经在Executor上调用不会再添加任务,但还会执行已经添加的任务
        shutdownNow()会尝试终止正在运行的任务并返回未处理的任务List
      • isTerminated()与isShutdown()
        isShutdown()当调用shutdown()/shutdownNow()返回true
        isTerminated()当调用shutdown()并所有任务完成后返回true/调用shutdownNow()成功停止后返回ture
        如果线程池任务是正常完成的则都返回false
  • ScheduledThreadPoolExecutor类

    • 主要是延迟/定时执行任务
    • 相对于Timer来说ScheduledThreadPoolExecutor是多个线程而Timer只是单线程
    • 获得: ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(int)
    • 方法:
      • schedule(Runnable runnable, long initialDelay, TimeUnit unit)
      • scheduleAtFixedRate(Runnable runnable,long initialDelay,long period,TimeUnit unit) 延迟是两个任务开始的时间差 即initialDelay+n*period
      • scheduleWithFixedDelay(Runnable runnable,long initialDelay,long delay,TimeUnit unit) 延迟是上个任务结束到下个任务开始的时间差
    • 机制
      这里写图片描述
      • 调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()/scheduleWithFixedDelay()会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask
      • 线程从DelayQueue中获取ScheduledFutureTask,再执行任务
    • 执行步骤
      这里写图片描述
      • 从DelayQueue中获取到期的ScheduledFutureTask(DelayQueue.take()),
      • 分配线程执行这个ScheduledFutureTask
      • 将这个ScheduledFutureTask的time变量更改为下次执行的时间
      • 再将这个ScheduledFutureTask放回DelayQueue中(DelayQueue.add())
    • 与ThreadPoolExecutor的不同
      • 使用DelayQueue作为任务队列,DelayQueue封装了一个PriorityQueue,它会对任务按 1执行所需时间/2先后 进行排序.
      • 获取任务方法不同
      • 添加了额外的处理
  • 各种线程池使用场景

    • FixedThreadPool: 负载较重的服务器
    • SingleThreadExecutor: 保证顺序执行各个任务且不在意时间的
    • CachedThreadPool: 执行很多短期异步小程序
    • ScheduledThreadPool: 多个后台周期执行任务

扩展

  • Unsafe类
    • java无法直接访问底层操作系统,只能通过native方法来进行操作
    • Unsafe类是JVM的后门,提供硬件级别的原子操作
    • 尽管Unsafe类中都是public但未经授信的代码无法使用此类
    • Unsafe是CAS的核心
    • Unsafe是LockSupport的依赖
  • CAS(Compare and swap)
    • java.util.concurrent包建立在CAS基础上
    • 应用
      • 原子类
        • 方法compareAndSet(int a, int update)原子化操作
        • 方法其实使用了unsafe类
        • public final boolean compareAndSet(int expect, int update) {
          return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
          }
      • 原子类无法保证线程安全: 只有在单线程中原子类是安全的,多线程无法保证中间状态zhuangtai
    • 原理
      • 得到内存值V, 预期值A, 需要修改的值B
      • 当A = V时, 将V = B,返回true,若什么都没做则返回false
      • 即原先的值为A, 当前的值为V, 若V == A则表示此时的状态与原先的状态相同(虽然可能会有中间状态A-B-A)
    • 通过加锁使得CAS是原子操作
    • 通过volatile通知V的改变
    • 如何解决A-B-A中间状态的出现? 带标记的原子引用AtomicStampedReference
  • LockSupport类
    • 类似于Object.wait()/notify()
    • LockSupport不一定要在同步代码块中使用
    • 个人看法: 类似于标记,可以先标记一个unpark(),当之后标记park()时可以直接执行.

死锁

  • 两个线程互相等待对方释放锁
  • 避免
    • 避免一个线程同时获得多个锁
    • 避免一个锁占有多个资源
    • 尝试定时锁,Lock.tryLock(timeout)代替内部锁机制
    • 对于数据库锁,加锁和解锁必须在同一个链接中,否则会出现解锁失败的情况

推荐

https://blog.csdn.net/mine_song/article/details/70948223

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 建立三个线程,并且同时运行它们。当运行时输出线程的名称。 实验步骤: (1)、创建类sy6_1 (2)、创建三个线程,调用start()方法启动这三个线程 (3)、保存文件,调试并编译运行程序。 参考程序运行效果: 2. 实现3个类:Storage、Counter和Printer。 Storage类应存储整数。 Counter应创建线程,线程从0开始计数(0,1,2,3…)并将每个值存储到Storage类中。 Printer类应创建一个线程,线程读取Storage类中的值并打印值。编写程序创建Storage类的实例,并创建一个Counter对象和Printer对象操作此实例。 实验步骤: (1)、创建三个类Counter, Printer,Storage (2)、创建TestCounter类,在该类中定义main函数,在main函数中定义Storage对象、Counter对象和 Printer对象,创建Counter线程和Printer线程并启动 (3)、保存文件,调试并编译运行程序。 参考程序运行效果: 3. 修改实验1第2题的程序,添加适当代码,以确保每个数字都恰好只被打印一次。 实验步骤: (1)、创建三个类Counter, Printer,Storage (2)、 创建TestCounter类,在该类中定义main函数,在main函数中定义Storage对象、Counter1对象和 Printer对象,创建Counter线程和Printer线程并启动 (3)、在定义Storage类中的setValue(int i) 和getValue ()方法时使用synchronized关键字,将其定义为同步方法 (4)、保存文件,调试并编译运行程序。 参考程序运行效果:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值