java多线程和并发

java线程周期
一:五中基本状态
new:新建线程
runnable:线程就绪
running:运行
blocked:堵塞
Dead:结束

二:java线程的实现方式:
1:继承Thread
class MyFirstThread extends Thread{
private int i=0;
@Override
public void run() {
// TODO Auto-generated method stub
for(i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+” “+i);
}
}
}
2:重写run方法
class MyFirstRunnable implements Runnable{
private int i=0;
@Override
public void run() {
// TODO Auto-generated method stub
for(i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+” “+ i);
}
}
}
3:使用callable和future接口创建线程(具有返回值的线程)
callable:创建callable接口的实现类并实现call()方法
使用futureTask类来包装callable实现的对象,并以此FutureTask对象作为Thread对象的Target来创建线程
class MyCallable implements Callable {
private int i=0;
@Override
public Integer call() throws Exception {
int sum=0;
for(i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+” “+i);
sum++;
}
return sum;
}
}

线程具体实现的时候:
public static void main(String[] args) {
Callable myCallabel= new MyCallable();
FutureTask ft=new FutureTask(myCallabel);
System.out.println(Thread.currentThread().getName()+” “+i);
Thread th= new Thread(ft);
th.start();

try { 

 int sum = ft.get(); //取得新创建的线程中的call()方法返回的                    System.out.println("sum = " + sum);             

} catch (InterruptedException e) {            

  e.printStackTrace();          

  } catch (ExecutionException e) {

       e.printStackTrace(); 

}

三:锁提供的两种主要特性:原子性和可见性。
线程同步synchronize和volatile
synchronize具有原子性,可以修饰:类,代码块,静态方法,方法,可以保证代码只有一个线程访问
valatile具有可见性,用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。
.synchronized 和 Volatile的比较

在访问volatile变量时不会执行加锁操作,因此也不会使线程阻塞。

加锁机制既可以确保可见性又可以确保原子性,而 volatile 变量只能确保可见性。

如果过度依赖volatile变量来控制状态的可见性,通常会比使用锁的代码更脆弱。仅当volatile变量能简化代码的实现以及对同步策略的验证时,才使用它

在需要同步的时候,第一选择应该是synchronized关键字,这是最安全的方式,

四:TheadLocal

ThreadLocal类的实例,即便被多个线程锁共享,但是在每个线程当中都有一份私有拷贝,并且多个线程无法看到对方的值,即线程对于此变量的使用完全是在自己拷贝对象上。

private ThreadLocal myThreadLocal = new ThreadLocal();

ThreadLocal可以储存任意对象

myThreadLocal.set(“A thread local value”);//存储此对象的值
String threadLocalValue = (String) myThreadLocal.get();//读取
ThreadLocal myThreadLocal1 = new ThreadLocal();//泛型使用

类里面有一个成员变量,叫做a,类里面两个方法A,B都需要操作a(类成员变量什么时候会用?就是类里面多个方法要操作他的时候,不然用方法内的局部变量就行了)。
现在这个方法A和B都被多线程方式调用了,线程1和线程2互不关心对方a的值。也就是说没有线程共享的需求。

五:死锁的四个必要条件:

互斥条件:线程对所分配到的资源进行排他性使用,即在一段时间内,某资源只能被一个线程占用。如果此时还有其它线程请求该资源,则只能等待,直到占有该资源的线程用毕释放。
请求和保持条件:已经保持了至少一个资源,但是又提出新的资源请求,而资源已被其他线程占有,此时请求线程只能等待,但对自己已获得的资源保持不放。
不可抢占条件:线程已获得的资源在未使用完之前不能被抢占,只能线程使用完之后自己释放。
循环等待条件:在发生死锁时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的资源,这样一直下去,直到有一个任务所持有的资源,使得大家被锁住。

避免死锁的方式:
1、只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法;
2、设计时考虑清楚锁的顺序,尽量减少潜在的加锁交互数量
3、既然死锁的产生是两个线程无限等待对方持有的锁,我们可以使用Lock类中的tryLock方法去尝试获取锁,这个方法可以指定一个超时时限,获取锁超时后会返回一个失败信息,放弃取锁。

六:线程池
线程池的作用:

线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要用线程池:

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

比较重要的几个类:

ExecutorService

真正的线程池接口。

ScheduledExecutorService

能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。

ThreadPoolExecutor

ExecutorService的默认实现。

ScheduledThreadPoolExecutor

继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

  1. newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2.newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

  1. newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,

那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4.newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

三:ThreadPoolExecutor详解

ThreadPoolExecutor的完整构造方法的签名是:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) .

corePoolSize - 池中所保存的线程数,包括空闲线程。

maximumPoolSize-池中允许的最大线程数。

keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

unit - keepAliveTime 参数的时间单位。

workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。

threadFactory - 执行程序创建新线程时使用的工厂。

handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

ThreadPoolExecutor是Executors类的底层实现

我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池

如何合理配置线程池大小,仅供参考。

  一般需要根据任务的类型来配置线程池大小:

  如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1

  如果是IO密集型任务,参考值可以设置为2*NCPU
六:java并发包下面的常见类
1:Reentrantlock和ReentrantReadWriteLock

为什么JAVA有了synchronize还需要Reentrantlock和ReentrantReadWriteLock?

synchronized与ReentrantLock ,使用上看区别

1, synchronize在获取锁阻塞的时候是不能打断的

2, synchronize无超时机制,阻塞了的话只能一直阻塞造成死锁

3,synchronize只能notify,wait,如果需要两个或以上条件就不能用了,如: JAVA阻塞队列的实现,需要用是否为空和是否已满两个条件来阻塞线程
4:ReentrantLock的性能要好于synchronized
5:相比于synchronized,ReentrantLock在功能上更加丰富,它具有可重入、可中断、可限时、公平锁等特点。

看lock相关的API就知道, 主要就是解决这几个问题
方法名称 描述
lock 获取锁,如果锁无法获取,那么当前的线程就变为不可被调度,直到锁被获取到
lockInterruptibly 获取锁,除非当前线程被中断。如果获取到了锁,那么立即返回,如果获取不到,那么当前线程变得不可被调度,一直休眠直到下面两件事情发生:1、当前线程获取到了锁

2、其他的线程中断了当前的线程
tryLock 如果调用的时候能够获取锁,那么就获取锁并且返回true,如果当前的锁无法获取到,那么这个方法会立刻返回false
tryLcok(long time,TimeUnit unit) 在指定时间内尝试获取锁如果可以获取锁,那么获取锁并且返回true,如果当前的锁无法获取,那么当前的线程变得不可被调度,直到下面三件事之一发生:1、当前线程获取到了锁

2、当前线程被其他线程中断

3、指定的等待时间到了
unlock 释放当前线程占用的锁
newCondition 返回一个与当前的锁关联的条件变量。在使用这个条件变量之前,当前线程必须占用锁。调用Condition的await方法,会在等待之前原子地释放锁,并在等待被唤醒后原子的获取锁

那ReentrantReadWriteLock呢?

读写锁用于读多写少的情况,即当一条线程获取写锁后,后面的读锁都被阻塞,等待获取写锁的线程完成释放。读和写是两种不同的功能,读-读不互斥,读-写互斥,写-写互斥。

这样的设计是并发量提高了,又保证了数据安全。

场景,如本地缓存失效,当需要去DB拿数据进行写入的操作,需要阻塞其它读的操作.

当然,读写锁也是可以基于notifyAll和wait实现

需要注意的是

如果无写锁,读是不阻塞,
持有读锁后,不能直接调用写锁的lock方法 ,否则会造成死锁

2: ConcurrentHashMap

我们知道HashMap不是一个线程安全的容器,最简单的方式使HashMap变成线程安全就是使用Collections.synchronizedMap,它是对HashMap的一个包装

猜你喜欢

转载自blog.csdn.net/mingmingzhanghao1/article/details/81222298