1:synchronized(保证原子性和可见性)

1.同步锁。多线程同时访问时,同一时刻只能有一个线程能够访问使synchronized修饰的代码块或方法。它修饰的对象有以下几种:

修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象
修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象

2:volatile修饰的变量(只保证可见性,不保证原子性,且不阻塞线程,执行效率高)

线程在每次使用的时候都会读取变量修改后的最新的值。所以只有状态真正独立于程序内其他内容时,变量才能使用volatile修饰。
java内存模型中的happen-before原则,两个线程同时修改访问volatile变量时,写操作比读操作优先级高。
实现原理:通过加入内存屏障和禁止指令重排序来实现。对volatile变量读之前会加一个load屏障指令,写之后会加一个store指令。

3:保证线程安全的思路

对非安全的代码进行加锁控制
使用线程安全的类
多线程并发情况下,线程共享的变量改为方法级的局部变量

4:synchronized和lock比较

synchronized是在JVM层面上实现的,可以通过一些监控工具监控synchronized的锁定,在代码执行时出现异常时JVM也会自动释放锁定。
Lock是通过代码实现的,必须在finally{}中调用unLock()释放锁。
synchronized是读写,读读,写写操作全互斥(性能不高…),使用lock可以提升性能,如:使用读锁锁住读的代码块,写锁锁住写的代码块。
synchronized取得的锁都是对象,而不是把一段代码或函数当作锁。
每个对象只有一个锁(lock)和之相关联。
避免无谓的同步控制(实现同步需要很大的系统开销且使用不当会造成死锁)。

5:死锁

两个或多个进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

产生死锁的原因:

因为系统资源不足
进程运行推进的顺序不合适
资源分配不当
产生死锁的四个必要条件:

互斥条件:所谓互斥就是进程在某一时间内独占资源。

请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
打破产生死锁的四个必要条件中的一个或几个,保证系统不会进入死锁状态。

打破互斥条件。即允许进程同时访问某些资源。但是,有的资源是不允许被同时访问的,像打印机等等,这是由资源本身的属性所决定的。所以,这种办法并无实用价值。

打破不可抢占条件。即允许进程强行从占有者那里夺取某些资源。就是说,当一个进程已占有了某些资源,它又申请新的资源,但不能立即被满足时,它必须释放所占有的全部资源,以后再重新申请。它所释放的资源可以分配给其它进程。这就相当于该进程占有的资源被隐蔽地强占了。这种预防死锁的方法实现起来困难,会降低系统性能。
打破占有且申请条件。可以实行资源预先分配策略。即进程在运行前一次性地向系统申请它所需要的全部资源。如果某个进程所需的全部资源得不到满足,则不分配任何资源,此进程暂不运行。只有当系统能够满足当前进程的全部资源需求时,才一次性地将所申请的资源全部分配给该进程。由于运行的进程已占有了它所需的全部资源,所以不会发生占有资源又申请资源的现象,因此不会发生死锁。
打破循环等待条件,实行资源有序分配策略。采用这种策略,即把资源事先分类编号,按号分配,使进程在申请,占用资源时不会形成环路。所有进程对资源的请求必须严格按资源序号递增的顺序提出。进程占用了小号资源,才能申请大号资源,就不会产生环路,从而预防了死锁。

6:线程类&池

Thread.sleep()是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
Thread.sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
Thread.start()方法只是让线程处于就绪状态,线程真正抢到CPU时间片后才会执行线程,即此时执行run()方法中的代码。
两个线程互为对方的参数,可以在一个线程的构造器中new一个对方线程的对象并start(),则只启动一个线程即可启动两个线程。
创建线程方法:

继承Thread类,重写run方法
实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
通过Callable和FutureTask创建线程
通过线程池创建线程
说明:

前两种无返回值,通过重写run方法,run方式的返回值是void。访问当前线程,直接使用this即可获得当前线程。
后两种有返回值,通过Callable接口就要实现call方法,返回值是Object。访问当前线程必须使用Thread.currentThread()方法。
Callable和Runable接口方法基本相同,只不过Callable接口定义的方法可以有返回值,而且可以声明抛出异常而已。Future是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果的接口。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future接口定义如下:

public interface Future {
    boolean cancel(boolean mayInterruptIfRunning);//中断任务 
    boolean isCancelled(); boolean isDone();//判断任务是否完成 
    V get() throws InterruptedException,ExecutionException;//获取任务执行结果 
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

cancel方法用来取消任务,取消成功返回true,取消失败返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。
如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;
如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true
如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false。
isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
isDone方法表示任务是否已经完成,若任务完成,则返回true;
get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

同步&异步,阻塞&非阻塞

同步和异步着重点在于多个任务的执行过程中,一个任务的执行是否会导致整个流程的暂时等待;
而阻塞和非阻塞着重点在于发出一个请求操作时,如果进行操作的条件不满足是否会返会一个标志信息告知条件不满足。

CountDownLatch

是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0后,表示所有的线程都已经完成任务,然后在闭锁上等待的线程就可以恢复执行任务。
用法:实现最大的并行性,开始执行前等待n个线程完成各自任务,死锁检测。
CountDownLatch 一组线程等待另一组线程都执行完了再继续执行。CyclicBarrier 使一组线程在一个时间点上达到同步,可以是一起开始执行全部任务或者一部分任务,同时它是可以循环使用的。
Semaphore 只允许一定数量的线程同时执行一段任务。
场景:在一个主线程中,要求有大量(很多很多)子线程执行完之后,主线程才执行完成。多种方式,考虑效率。
1.自定义一个ImportThread类继承自java.lang.Thread,重载run()方法:用一个List属性保存所有产生的线程。这样只要判断这个List是否为空就知道还有没有子线程没有执行完了。
问题:如果线程1开始并且结束了,而其他线程还没有开始,此时runningThreads的size也为0 主线程会以为所有线程都执行完了。
方法:用一个非简单类型的计数器来取代List型的runningThreads,并且在线程创建之前就应该设定好计数器的值。
2.使用CountDownLatch代替MyCountDown,用await()方法代替while(true){…}


前程有光
936 声望618 粉丝