进程

就是正在运行的程序。也就是代表了程序锁占用的内存区域。

特点

独立性:进程是系统中独立存在的实体,它可以拥有自己的独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。
动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。
并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

线程

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以开启多个线程。
多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。
简而言之,一个程序运行后至少一个进程,一个进程里包含多个线程。
如果一个进程只有一个线程,这种程序被称为单线程。
如果一个进程中有多条执行路径被称为多线程程序
image

线程创建方式

image

继承Thread类

Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。Start()方法是一个native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。

步骤:

(1)创建线程类去继承Thread类
(2)重写Thread类的run方法,run方法中编写需要完成的任务
(3)创建线程类对象
(4)调用start方法启动线程

创建对象
Thread()//分配新的 Thread 对象。 
Thread(String name)//分配新的 Thread 对象。
Thread(Runnable target)//分配新的 Thread 对象。 
Thread(Runnable target,String name)//分配新的 Thread 对象。 
常用方法
返回值方法名描述
longgetId()返回该线程的标识符。
StringgetName()返回该线程的名称。
voidrun()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
voidsetName(String name)改变线程名称,使之与参数 name 相同。
static voidsleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),
voidstart()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
voidstop()终止线程。
测试
//测试  Thread类
public class Test_Thread {
    public static void main(String[] args) {
        // 1,创建对象
        MyThread t = new MyThread();// 新建状态
        // 2,开启线程
        // t.run();
        t.setName("线程1");// 修改线程名
        t.start();// 可运行状态
        // 3,run()和start()有什么区别?
        // run()和start()都能执行任务,但是run()执行时就只是一个普通的方法调用没有多线程并发抢占的效果
        // --模拟多线程--
        MyThread t2 = new MyThread();
        // t2.run();
        t2.setName("线程2");// 修改线程名
        t2.start();
        //4,多线程的执行结果具有随机性 
    }
}
// 1,继承Thread类,实现多线程编程
class MyThread extends Thread {
    // 2,在多线程编程里,把所有的业务放入--重写的run()里--generate--override methods..
    // --需求:重复的打印10次线程名称
    @Override
    public void run() {// 运行状态
        for (int i = 0; i < 10; i++) {// fori
            System.out.println(super.getName() + "---" + i);// getName()--获取线程名称
        }
    }// 结束状态
}
线程1---0
线程2---0
线程2---1
线程2---2
线程2---3
线程2---4
线程1---1
线程1---2
线程1---3
线程1---4
线程1---5
线程1---6
线程1---7
线程1---8
线程1---9
线程2---5
线程2---6
线程2---7
线程2---8
线程2---9

实现Runnable接口

Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run。使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中断。

步骤:

(1)创建线程类实现Runnable接口
(2)线程类中实现Runnable接口的run方法
(3)通过Thread类创建线程对象。
(4)将Runnable接口的实现类对象作为实际参数传递给Thread类的构造器中。
(5)调用Thread类的start()方法启动线程

方法:
void run()//使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。
测试:
//测试 Runnable接口
public class Test_Runnable {
    public static void main(String[] args) {
        // 1,创建目标对象
        MyRunnable target = new MyRunnable();
        // 2,把要执行的目标 和Thread绑定关系
        Thread t = new Thread(target);
        t.setName("猪猪侠");
        // 3,启动线程
        t.start();

        // 4,模拟多线程
        Thread t2 = new Thread(target);
        t2.setName("蜘蛛侠");
        t2.start();
        /*
         * 5,多线程的随机性 Thread-0===0 Thread-0===1 Thread-1===0 Thread-1===1 Thread-1===2
         * Thread-1===3 Thread-1===4 Thread-0===2 Thread-0===3
         */
    }
}

// 1,实现Runnable接口,实现多线程编程
class MyRunnable implements Runnable {
    // 2,如果有自己的业务,需要把业务 -- 放在重写的run()里
    // --需求:打印10次线程名称
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            // Thread.currentThread()--获取正在执行任务的线程对象
            // getName()--获取线程的名字
            // 3,Thread.currentThread().getName() -- 获取当前执行任务的线程对象的 名字
            System.out.println(Thread.currentThread().getName() + "===" + i);
        }
    }
}
猪猪侠===0
猪猪侠===1
猪猪侠===2
蜘蛛侠===0
蜘蛛侠===1
蜘蛛侠===2
蜘蛛侠===3
蜘蛛侠===4
猪猪侠===3
猪猪侠===4

实现Callable接口

Callable的call()方法类似于Runnable接口中run()方法,都定义任务要完成的工作,实现这两个接口时要分别重写这两个方法,主要的不同之处是call()方法是有返回值的(其实还有一些区别,例如call方法可以抛出异常,run方法不可以),运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

步骤:

(1)创建一个实现Callable接口的实现类
(2)实现call方法,设置此线程需要完成的任务
(3)创建Callable接口实现类的对象
(4)将此Callable接口实现类的对象作为参数传递到FutureTask的构造器中,创建FutureTask类的对象
(5)将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,调用start方法
(6)可以获取call方法的返回值

方法:
call()//计算结果,如果无法计算结果,则抛出一个异常。
测试:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class Test_Callable {
    public static void main(String[] args) {
         MyCallable c = new MyCallable();
        //1.创建FutureTask对象
        FutureTask<String> futureTask = new FutureTask<String>(c);
        //2.创建Thread对象,传入futureTask
        Thread t = new Thread(futureTask);
        t.start();
        //String s = futureTask.get(); //3.调用get方法可以获取call方法的返回值
        for (int i = 0; i<10; i++){
            System.out.println("main:"+i);
        }
    }
}
class MyCallable implements Callable{
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println("Callable线程:" + i);
        }
        return "MyCallable";
    }
}
main:0
Callable线程:0
Callable线程:1
Callable线程:2
main:1
Callable线程:3
Callable线程:4
Callable线程:5
Callable线程:6
Callable线程:7
main:2
Callable线程:8
main:3
Callable线程:9
main:4
main:5
main:6
main:7
main:8
main:9

使用线程池

创建一个线程的完整过程(jvm在底层做了什么)

  1. 它为一个线程栈分配内存,该栈为每个线程方法调用保存一个栈帧
  2. 每一栈帧由一个局部变量数组、返回值、操作数堆栈和常量池组成
  3. 一些支持本机方法的 jvm 也会分配一个本机堆栈
  4. 每个线程获得一个程序计数器,告诉它当前处理器执行的指令是什么
  5. 系统创建一个与Java线程对应的本机线程
  6. 将与线程相关的描述符添加到JVM内部数据结构中
  7. 线程共享堆和方法区域

总结:从数据角度看,创建一个线程大约消耗1M内存空间

为什么要用线程池?

在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池。

方法:

newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

但是阿里巴巴Java开发手册说明禁止使用 Executors 创建线程池

image

原因:

1.静态方法参数传入的workQueue 是一个边界为 Integer.MAX_VALUE 队列,我们也可以变相的称之为无界队列了,因为边界太大了,这么大的等待队列也是非常消耗内存的
2.ThreadPoolExecutor方法使用的是默认拒绝策略(直接拒绝),但并不是所有业务场景都适合使用这个策略,当很重要的请求过来直接选择拒绝显然是不合适的

ThreadPoolExecutor详解(Executors的底层也是这种方法)

构造方法:

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

参数详解:

corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize-池中允许的最大线程数。当workqueue使用无界队列LinkedBlockingQueue等时,此参数无效。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - 参数的时间单位。
workQueue - 任务队列,如果当前线程池达到核心线程数时,且当前所有线程都处于活动状态,则将新加入的任务放到此队列中。当任务队列满了之后,且工作线程介于核心线程数和最大线程数之间,则创建新的线程执行任务。
threadFactory - 执行程序创建新线程时使用的工厂。
handler - 饱和策略,当队列和线程池都满了,必须采取一种策略处理提交的任务,默认直接抛出异常

任务队列的四种类型(workQueue)

ArrayBlockingQueue:基于数组结构有界队列,FIFO原则对任务进行排序,队列满了之后的任务,调用拒绝策略。
LinkedBlockingQueue:基于链表结构的无界队列,FIFO原则对任务进行排序。
SynchronousQueue:直接将任务提交给线程而不是将它加入到队列,实际上此队列是空的。每个插入的操作必须等到另一个调用移除的操作;如果新任务来了线程池没有任何可用线程处理的话,则调用拒绝策略。
PriorityBlockingQueue:具有优先级的队列的有界队列,可以自定义优先级;默认是按自然排序。

饱和(拒绝)策略的四种类型(handler)

AborttPolicy:直接抛出异常
CallerRunsPolicy:使用调用者所在的线程执行任务
DiscardOldestPolicy:丢弃队列里最老的任务,用来执行当前的任务
DiscardPolicy:不进行处理,直接丢弃掉

常用方法:
方法描述
submit()提交线程任务
shutdown()/shutdownNow()关闭线程池
getCorePoolSize获取核心线程数,这不是显示当前的线程数,是总共可以创建多少个核心线程数
getLargestPoolSize之前线程池中,存在过的最大线程的数目
getMaximumPoolSize获取当前线程池允许创建的最大线程数
getPoolSize获取当前存活在线程池中的线程的个数
getQueue获取当前队列中存在的任务
getCompletedTaskCount获取曾经执行完成过的任务大致总数,为啥是大致呢?因为你在调用的时候,要是刚好有个线程完成任务,那数量就变化了
getTaskCount获取当前线程曾经计划执行的个数,也就是执行过了多少个线程,启动的时候就算数的
getRejectedExecutionHandler获取当前线程池的拒绝策略

线程的生命周期

image
状态图:来自《Java并发编程的艺术》

五大状态

新建状态
用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。
就绪状态
处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。因为cpu的调度不一定是按照先进先出的顺序来调度的),等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
运行状态(此时去调用run方法)
处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
处于就绪状态的线程:
如果获得了cpu的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。
如果该线程失去了cpu资源,就会又从运行状态变为就绪状态。重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。
当线程的run()方法执行完,或者被强制性地终止,例如出现异常,或者调用了stop()、desyory()方法等等,就会从运行状态转变为死亡状态。
阻塞状态
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。有三种方法可以暂停Threads执行:
死亡状态
当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

控制线程状态的方法:
返回值方法描述
voiddestroy()已过时。_该方法最初用于破坏该线程,但不作任何清除。_
voidinterrupt()中断线程。
voidjoin()等待该线程终止。
voidjoin(long millis)等待该线程终止的时间最长为 millis 毫秒。
voidjoin(long millis, int nanos)等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
voidresume()已过时。 该方法只与 suspend()一起使用,但 suspend()已经遭到反对,因为它具有死锁倾向。
voidsetDaemon(boolean on)将该线程标记为守护线程或用户线程。
voidsetPriority(int newPriority)更改线程的优先级。
static voidsleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响
static voidsleep(long millis, int nanos)在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
voidstart()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
voidstop()已过时。 该方法具有固有的不安全性。用 Thread.stop 来终止线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查ThreadDeath 异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。stop 的许多使用都应由只修改某些变量以指示目标线程应该停止运行的代码来取代。目标线程应定期检查该变量,并且如果该变量指示它要停止运行,则从其运行方法依次返回。如果目标线程等待很长时间(例如基于一个条件变量),则应使用interrupt 方法来中断该等待。
voidstop(Throwableobj)已过时。 该方法具有固有的不安全性。有关详细信息,请参阅 stop()。该方法的附加危险是它可用于生成目标线程未准备处理的异常(包括若没有该方法该线程不太可能抛出的已检查的异常)。
voidsuspend()已过时。 该方法已经遭到反对,因为它具有固有的死锁倾向。如果目标线程挂起时在保护关键系统资源的监视器上保持有锁,则在目标线程重新开始以前任何线程都不能访问该资源。如果重新开始目标线程的线程想在调用resume 之前锁定该监视器,则会发生死锁。这类死锁通常会证明自己是“冻结”的进程。
static voidyield()暂停当前正在执行的线程对象,并执行其他线程。

重点方法

线程睡眠——sleep

如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread的sleep方法,从上面可以看到sleep方法有两种重载的形式,但是使用方法一样。
sleep是静态方法,最好不要用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效。

线程让步——yield

yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出cpu资源给其他的线程。但是和sleep()方法不同的是,它不会进入到阻塞状态,而是进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次,完全可能出现这样的情况:当某个线程调用yield()方法之后,线程调度器又将其调度出来重新进入到运行状态执行。
实际上,当某个线程调用了yield()方法暂停之后,优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程更有可能获得执行的机会,当然,只是有可能,因为我们不可能精确的干涉cpu调度线程。

线程合并——join

线程的合并的含义就是将几个并行线程的线程合并为一个单线程执行,应用场景是当一个线程必须等待另一个线程执行完毕才能执行时,Thread类提供了join方法来完成这个功能,注意,它不是静态方法。
从上面的方法的列表可以看到,它有3个重载的方法:
void  join()当前线程等该加入该线程后面,等待该线程终止。    
void  join( long  millis)  当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度    
void  join( long  millis, int  nanos)  等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度

线程的优先级——setPriority(int newPriority)

每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。
Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~·0之间,也可以使用Thread类提供的三个静态常量:
MAX_PRIORITY   =10
MIN_PRIORITY   =1
NORM_PRIORITY   =5

守护线程——setDaemon(boolean on)

守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。
守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。

结束一个线程

Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit这些终止线程运行的方法已经被废弃了,使用它们是极端不安全的!想要安全有效的结束一个线程,可以使用下面的方法。
1、正常执行完run方法,然后结束掉
2、控制循环条件和判断条件的标识符来结束掉线程

多线程安全问题

当多个线程同时共享,同一个全局变量或者静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。

多线程三大特性

原子性:一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看的到修改的值
有序性:程序执行的数序顺序按照代码的先后顺序执行,如使用线程中join()来实现

解决多线程安全问题的办法

synchronized(同步锁)

将可能会发生线程安全问题的代码,给包括起来。也称同步代码块。synchronized 使用的锁可以是对象锁也可以是静态资源,如xxx.class,只有持有锁的线程才能执行同步代码块中的代码。没持有锁的线程即使获取CPU的执行权,也进不去。
image
同步的前提:
1,必须要有两个或者两个以上的线程 ,如果小于2个线程,则没有用,且还会消耗性能(获取锁,释放锁)
2,必须是多个线程使用同一个锁
弊端:
1.多个线程需要判断锁,较为消耗资源、抢锁的资源。
2.同步中嵌套同步,导致锁无法释放
线程t1,运行后在同步代码块中需要object对象锁,运行到类里面的方法时需要this对象锁
线程t2,运行后需要调用类里面方法,需要先获取this锁,再获取object对象锁
那这样就会造成,两个线程相互等待对方释放锁。就造成了死锁情况。

volatile关键字

Volatile 关键字的作用是变量在多个线程之间可见。使用Volatile关键字将解决线程之间可见性, 强制线程每次读取该值的时候都去“主内存”中取值。
只需要在flag属性上加上该关键字即可,例如:

    public volatile boolean flag = true;

子线程每次都不是读取的线程本地内存中的副本变量了,而是直接读取主内存中的属性值。
volatile虽然具备可见性,但是不具备原子性。

AtomicXxx

  1. volatile关键字修饰value字段,保证value字段对各个线程的可见性;
  2. Unsafe类的compareAndSetInt方法,compareAndSetInt方法是一个CAS(乐观锁原理实现)操作,用native关键字修饰。CAS操作保证了多线程环境下对数据修改的安全性。

image

synchronized,volatile 和Atomicxx的区别

synchronized 不仅保证可见性,而且还保证原子性,synchronized保证了synchronized块中变量的可见性。只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。
volatile 保证了所修饰的变量的可见性,不保证原子性。因为volatile只是在保证了同一个变量在多线程中的可见性,所以它更多是用于修饰作为开关状态的变量,即Boolean类型的变量。在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制(只保证变量可见,不保证同步)volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢。
Atomic修饰的类如AtomicInteger 保证了数据的原子性。 由于使用的CAS,在进行比如说自增操作时,如果有线程在执行自增操作,那么此线程会一直循环尝试运行自增操作,直到成功。而不会让线程进入堵塞状态。 进行的操作相对于synchronized修饰的代码块会有很大性能上的提升。建议在计数器相关的多线程并发中用Atomic开头的相关类进行操作。
总结一下:volatile多用于修饰类似开关类型的变量;
Atomic多用于类似计数器相关的变量;
其它多线程并发操作用synchronized关键字修饰(还有Lock接口的一系列实现)。

多线程间的通信

第一种方式:在synchronized 同步代码块中使用wait(),notify()和notifyAll()

一个线程一旦调用了任意对象的wait()方法,就会变为非运行状态,直到另一个线程调用了同一个对象的notify()方法。为了调用wait()或者notify(),线也程必须先获得那个对象的锁。就是说,线程必须在同步块里调用wait()或者notify(),且使用同一个锁对象调用wait,notify
如果对象调用了wait方法就会使持有该对象的线程把该对象锁的控制权交出去,然后处于等待状态。
如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
等待线程将调用doWait(),而唤醒线程将调用doNotify()。当一个线程调用一个对象的notify()方法,正在等待该对象的所有线程中将有一个线程被唤醒并允许执行(校注:这个将被唤醒的线程是随机的,不可以指定唤醒哪个线程)。同时也提供了一个notifyAll()方法来唤醒正在等待一个给定对象的所有线程。 一个线程如果没有持有对象锁,将不能调用wait(),notify()或者notifyAll()。否则,会抛出IllegalMonitorStateException异常。

public class TestThread {
    public static void main(String[] args) {
        Object object = new Object();
        //模拟两条线程协同工作
        //下载图片
        Thread down = new Thread() {
            public void run() {
                System.out.println("down开始下载图片...");
                for(int i=0;i<100;i++) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                    }
                    System.out.println(++i);
                }
                System.out.println("down下载完成。");
                synchronized (object) {
                    object.notify();
                }
                
            }
        };
        //显示图片
        Thread show = new Thread() {
            public void run() {
                System.out.println("show开始准备显示图片...");
                synchronized (object) {
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("show显示图片成功");
                }
            }
        };
        down.start();
        show.start();
    }
}
public class TestThread {
    private static int count = 10000;
    public static void main(String[] args) {
        Thread t1 = new Thread() {
            public void run() {
            while(true) {
                synchronized (TestThread.class) {
                if(count!=0) {
                    Thread.yield();
                    count--;
                }else {
                    throw new RuntimeException("出错了1");
                }
                System.out.println(count+""+Thread.currentThread().getName());
            }
            }
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                while(true) {
                    synchronized (TestThread.class) {
                    if(count!=0) {
                        Thread.yield();
                        count--;
                    }else {
                        throw new RuntimeException("出错了2");
                    }
                    System.out.println(count+""+Thread.currentThread().getName());
                }
                }
                }
        };
        t1.start();
        t2.start();
    }
}

一旦一个线程被唤醒,不能立刻就退出wait状态,直到调用notify()的线程退出了它自己的同步块且还需要wait线程能抢到锁才能继续执行。线程被唤醒后,是从上一次被wait的地方继续向下执行,而不是重新执行代码。
当有多个线程wait,调用notifyAll时,会唤醒全部持有该锁的wait状态线程,但是只有一个wait线程能抢到锁而继续执行,其他线程依然需要进行抢锁,直到抢到了锁才能完全退出wait状态

wait与sleep区别?

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但不释放锁,当指定的时间到了又会自动恢复就绪状态。只要抢到了时间片段即可执行
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,然后需要抢到锁和cpu执行权才能进入执行状态

停止线程的方式

--使用退出标识,使得线程正常退出,即当run方法完成后进程终止。
--使用stop强行中断线程(此方法为作废过期方法),不推荐使用,暴力终止,可能使一些清理性的工作得不到完成。还可能对锁定的内容进行解锁,容易造成数据不同步的问题。
--使用interrupt方法中断线程:当线程处于堵塞状态下,那么直接使用退出标识的方式可能就会存在结束不了线程的问题。可以使用interrupt来结束(并不会直接结束进程),当调用了线程的interrupt方法后,如果线程是堵塞的,那么就会抛出异常,那我们只要捕获异常,并使用退出标识变量去结束线程即可。如果线程没有堵塞,调用interrupt方法只会修改线程的interrupt标识状态,并不会抛出异常

第二种方式:Lock&Condition(await()和signal())

方法:
// 获取锁 
void lock() 
// 如果当前线程未被中断,则获取锁,可以响应中断 
void lockInterruptibly() 
// 返回绑定到此 Lock 实例的新 Condition 实例 
Condition newCondition() 
// 仅在调用时锁为空闲状态才获取该锁,可以响应中断 
boolean tryLock()
// 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁 
boolean tryLock(long time, TimeUnit unit)
// 释放锁 
void unlock()
Lock的实现类 ReentrantLock

ReentrantLock,即 可重入锁。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
构造方法(不带参数 和带参数 true: 公平锁; false: 非公平锁)

ReadWriteLock锁

ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持,而写入锁是独占的。

//返回用于读取操作的锁 
Lock readLock() 
//返回用于写入操作的锁 
Lock writeLock()
Condition接口与newCondition() 方法
 void await() //造成当前线程在接到信号或被中断之前一直处于等待状态。 
 boolean await(long time, TimeUnit unit) //造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 
 long awaitNanos(long nanosTimeout) //造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 
 void awaitUninterruptibly() //造成当前线程在接到信号之前一直处于等待状态。 
 boolean awaitUntil(Date deadline) //造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。 
 void signal() //唤醒一个等待线程。 
 void signalAll() //唤醒所有等待线程。 

第三种方式:Semaphore线程同步的辅助类

方法:
void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
void release():释放一个许可,将其返回给信号量。
int availablePermits():返回此信号量中当前可用的许可数。
boolean hasQueuedThreads():查询是否有线程正在等待获取。

禾白少二
57 声望26 粉丝