Java —— 线程

32 篇文章 1 订阅
6 篇文章 0 订阅

一、定义

(一)进程

        进程是程序在一个数据集上的一次动态执行的过程,是系统进行资源分配和调度的一个独立单位。

(二)线程

        是进程的一个实体,是cpu调度和分派的基本单位,他是比进程更小的能够独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源。

(三)区别

  • 一个线程只能属于一个进程,而一个进程可以拥有多个线程。
  • 线程是进程工作的最新单位。
  • 一个进程会分配一个地址空间,进程与进程之间不共享地址空间。即不共享内存。
  • 同一个进行下的不同的多个线程,共享父进程的地址空间。
  • 线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
  • 每个进程互相独立,不影响主程序的稳定性,子进程崩溃不影响其他进程。但一个线程的崩溃可能影响到整个程序的稳定性。

(四)并发与并行

        并发是指一个处理器同时处理多个任务。
        并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。 

 二、多线程

(一)java线程的状态

(1)创建

        当new了一个线程,并没有调用start之前,线程处于创建状态;

(2)就绪

        当调用了start之后,线程处于就绪状态,这是,线程调度程序还没有设置执行当前线程;

(3)运行

       线程调度程序执行到线程时,当前线程从就绪状态转成运行状态,开始执行run方法里边的代码;

(4)阻塞

        线程在运行的时候,被暂停执行(通常等待某项资源就绪后在执行,sleep、wait可以导致线程阻塞),这是该线程处于阻塞状态;

(5)死亡

         当一个线程执行完run方法里边的代码或调用了stop方法后,该线程结束运行 

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
    	//创建
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
    	//运行
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
    	//阻塞
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
    	//等待
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
    	//超时等待
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
    	//终止
        TERMINATED;
    }

(二) Java实现多线程有哪几种方式。

(1)继承Thread类创建线程

        定义Thread类的子类,并重写Thread类的run()方法,创建子类对象(即线程对象),调用线程对象的start()方法来启动该线程


(2)实现Runnable接口创建线程

        定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法同样是该线程的执行体。创建该Runnable实现类的实例,并将此实例作为Thread的target来创建Thread对象。最后调用thread对象的start()方法来启动该线程。

        多个Thread可以同时加载一个Runnable


(3)实现Callable接口创建新线程

        创建Callable接口的实现类,并实现call()方法,该方法有返回值;创建Callable实现类的实例,使用FutureTask来包装Callable对象和call()方法的返回值;使用FutureTask作为Thread类的target创建并启动线程;调用FutureTask对象的get()方法返回子线程执行结束后的返回值。

        两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;

 

(三)Callable和Future        

        Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果

Future提供了三种功能:

  1)判断任务是否完成;

  2)能够中断任务;

  3)能够获取任务执行结果。

  

(四)sleep和wait的区别。

     1,这两个方法来自不同的类分别是Thread和Object
     2,最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
     3,wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
             synchronized(x){
             x.notify()
             //或者wait()
             }
     4,sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

(五)ThreadLocal的了解,实现原理。

        ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题。

每个Thread的对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。

在该类中,我觉得最重要的方法就是两个:set()和get()方法。当调用ThreadLocal的get()方法的时候,会先找到当前线程的ThreadLocalMap,然后再找到对应的值。set()方法也是一样。

(六)volitile关键字的作用,原理。

        保证内存可见性

        使用volatile修饰的变量会强制将修改的值立即写入主存, 每次从内存中取值,不从缓存中什么的拿值。这就保证了用 volatile修饰的共享变量,每次的更新对于其他线程都是可见的。volatile只能保证可见性和有序性,不具备原子性。也不能保证线程安全。

还是以最常用的i++来说吧,包含3个步骤
1,从内存读取i当前的值
2,加1
3,把修改后的值刷新到内存

所以这里有两个问题,可见性和原子性,viloate只能保证可见性,即步骤1每次都重新读,步骤3每次都立即刷新到主内存。但1,2之间仍然会被中断,多个线程交叉修改,所以仍然不安全

5. synchronized关键字的用法,优缺点。

        在Java中,每一个对象都拥有一个锁(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

        但是采用synchronized关键字来实现同步的话,就会导致一个问题:

        如果多个线程都只是进行读操作,当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

三、线程池

        线程池中有很多可用线程资源,如果需要就直接从这个池子里拿。当不用的时候,放入池子中,线程池会自动帮我们管理。

        使用线程池的优势:

  • 降低资源消耗
  • 提高响应速度
  • 提高管理性

(一) 线程池的参数

corePoolSize 核心线程数量
maximumPoolSize 线程最大线程数
workQueue 阻塞队列
keepAliveTime线程没有任务时最多保持多久时间终止
unit

keepAliveTime的时间单位

threadFactory 线程工厂
rejectHandler 拒绝策略

系统默认的拒绝策略有以下几种:

  1. AbortPolicy:为线程池默认的拒绝策略,该策略直接抛异常处理。
  2. DiscardPolicy:直接抛弃不处理。
  3. DiscardOldestPolicy:丢弃队列中最老的任务。
  4. CallerRunsPolicy:将任务分配给当前执行execute方法线程来处理。

(二) 线程池的执行流程: 

        提交任务后,线程池先判断线程数是否达到了核心线程数(corePoolSize)。如果未达到线程数,则创建核心线程处理任务;如果达到了就判断任务队列是否满了。如果没满,则将任务添加到任务队列中;否则,就判断线程数是否达到了最大线程数。如果未达到,则创建非核心线程处理任务;否则,就抛出异常。

(三)常见线程池:

newCacheThreadPool():可缓存线程池

newScheduledThreadPool(int n):定时线程池          可以控制线程池内线程定时或周期性执行某任务的线程池

newSingleThreadExecutor():单线程化的线程池

newFixedThreadPool(int n):指定线程数量的线程池

(四)线程池数量如何设置

        如果是CPU密集型应用,则线程池大小设置为N+1

        如果是IO密集型应用,则线程池大小设置为2N+1

四、线程通信

Java中线程通信协作的最常见的两种方式:

  一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

  二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

  线程间直接的数据交换:

  三.通过管道进行线程间通信:1)字节流;2)字符流

         1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

  2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)如果当前线程没有这个对象的锁就调用wait()方法,则会抛出IllegalMonitorStateException.

  3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

  4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程

这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。

  上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行

(一)ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

 Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。

  • Condition是个接口,基本的方法就是await()和signal()方法;
  • Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition() 
  •  调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

  Conditon中的await()对应Object的wait();

  Condition中的signal()对应Object的notify();

  Condition中的signalAll()对应Object的notifyAll()

(二)通过管道进行线程间通信:1)字节流;2)字符流

         Java中有各种各样的输入、输出流(Stream),其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据。

         一个线程发送数据到输出管道,另一个线程从输入管道读数据。

(三)进程同步的方法

 五、锁

(一)Lock接口的实现类

(1)Lock接口中定义的方法

        1、void lock() 
获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态

        2、void lockInterruptibly() 
如果当前线程未被中断,则获取锁。

        3、Condition newCondition() 
返回绑定到此 Lock 实例的新 Condition 实例。

        4、boolean tryLock() 
仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false

        5、boolean tryLock(long time, TimeUnit unit) 
如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。

        6、void unlock() 
释放锁。在等待条件前,锁必须由当前线程保持。调用 Condition.await() 将在等待前以原子方式释放锁,并在等待返回前重新获取锁。

        Lock接口有三个实现类分别是        ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock。后面两个是内部类。

(2)ReentrantLock

ReentrantLock是一个可重入的互斥锁 Lock.

ReentrantLock的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。也就是说这里可以设置锁的类型为公平锁还是非公平锁。但是,需要注意的是公平锁的情况下,也不能完全确保公平,它总是趋向公平的情况。

ReentrantLock类中还定义了Lock接口之外的方法,例如int getHoldCount() 、boolean hasQueuedThreads() 、boolean hasWaiters(Condition condition)等等,这些方法可以查询当前锁的状态。

(3)ReentrantReadWriteLock实现类

ReentrantReadWriteLock 望文生义就是可重复进入读写锁,读取锁 和 写入锁 ,

读取锁含义:在同一时刻可以被多个读线程访问,线程之间不存在阻塞;

写入锁含义:  在多个写入线程同一时刻进行访问写入时,只有一个线程可以写入,其他写入线程和所有读取线程都会被阻塞。


(二)可重入锁的用处及实现原理

Synchronized和ReentrantLock两者都是可重入锁.

通俗来说:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。

重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁.

(三)ABC三个线程如何保证顺序执行。 

 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
 public class ABC {
     private static Lock lock = new ReentrantLock();//通过JDK5中的锁来保证线程的访问的互斥
     private static int state = 0;
     
     static class ThreadA extends Thread {
         @Override
         public void run() {
             for (int i = 0; i < 10;) {
                 lock.lock();
                 if (state % 3 == 0) {
                     System.out.print("A");
                     state++;
                     i++;
                 }
                 lock.unlock();
             }
         }
     }
     
     static class ThreadB extends Thread {
         @Override
         public void run() {
             for (int i = 0; i < 10;) {
                 lock.lock();
                 if (state % 3 == 1) {
                     System.out.print("B");
                     state++;
                     i++;
                 }
                 lock.unlock();
             }
         }
     }
     
     static class ThreadC extends Thread {
         @Override
         public void run() {
             for (int i = 0; i < 10;) {
                 lock.lock();
                 if (state % 3 == 2) {
                     System.out.print("C");
                     state++;
                     i++;
                 }
                 lock.unlock();
             }
         }
     }
     
     public static void main(String[] args) {
         new ThreadA().start();
         new ThreadB().start();
         new ThreadC().start();
     }
     
 }

六、JUC

(一)JUC辅助类

(1)CountDownLatch

        减法计数器。

(2)CyclickBarrier

        加法计数器。

(3)Semaphore

        信号量,用于多个共享资源的互斥应用。

(二)阻塞队列 

(1)BlockingQueue

        常用于多线程并发处理、线程池。

(2)SynchronousQueue同步队列

        同步队列 没有容量,也可以视为容量为1的队列

七、AQS —— AbstractQueuedSynchronizer

       

        ReentrantLock、CountDownLatch等源码中都有使用。实现了一个FIFO的队列。底层实现的数据结构是一个双向链表。

        AQS核心思想是,如果被请求的共享资源空闲,则将当前请求的线程设置为有效,并且将共享资源设置为锁定状态。如果共享资源被占用,就通过一个基于一个双向链表的队列阻塞等待。

        AQS的同步状态——State。AQS中维护了一个名为state的字段,意为同步状态,是由Volatile修饰的,用于展示当前临界资源的获锁情况。

        AQS的大致实现思路

        AQS内部维护了一个CLH队列来管理锁。线程会首先尝试获取锁,如果失败就将当前线程及等待状态等信息包装成一个node节点加入到同步队列sync queue里。 接着会不断的循环尝试获取锁,条件是当前节点为head的直接后继才会尝试。如果失败就会阻塞自己直到自己被唤醒。而当持有锁的线程释放锁的时候,会唤醒队列中的后继线程。

        CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇宙超级无敌程序媛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值