1.synchronized
1.synchronized的实现原理
Java中的每个对象都可以做为锁
- 同步块的锁是括号内的对象
- 实例同步方法的锁是当前实例对象
- 静态同步方法的锁是当前类的Class对象
代码块同步由monitorenter和monitorexit实现的。每个对象都有一个monitor与之关联,当monitor被持有后,它将处于锁定状态。当线程执行到monitorenter时,会尝试获取对象所对应的monitor的所有权,即尝试获取锁。
synchronized的锁存在java对象头里。我觉得所谓monitor其实就是对象头里MarkWord中的锁状态。
在jdk1.6中,锁一共有四种状态,级别由低到高为:无锁,偏向锁,轻量级锁,重量级锁。
2.volatile
- 将当前处理器缓存行写回到系统内存
- 写回内存的操作会让其他cpu缓存了该内存地址的数据无效
//volitile保证可见性不能保证原子性,synchronized既能保证可见性又能保证原子性
count++ //此操作不是原子操作。分为两步:1、获取count值;2、加1
AomicInteger t = new AomicInteger(); //原子操作类。
t.incrementAndset();
3.ReentrantLock与syncronized的区别
- lock.tryLock(); // ReentrantLock可以尝试性获取锁,获取不到锁就不再等待。
- ReentrantLock有公平锁和非公平锁。公平锁实现中有先进先出队列,等待时间最长的线程优先获得锁
2.并发编程基础
1.线程的状态
- 初始状态(新建状态)
- 运行状态:“就绪”和“运行中”统称为运行
- 阻塞状态:一个任务进入阻塞状态可能有以下原因:
- 调用了sleep()方法,使线程休眠;
- 调用了wait()方法,使线程挂起;
- 任务在等待某个输入输出完成;
- 调用同步方法时,对象锁被其他线程获得;
- 终止状态
调用对象wait()方法之后,线程进入对象的等待队列(线程从运行中状态进入阻塞状态,调用wait()方法时必须先获取对象锁)—>调用对象的notify()方法之后,线程由等待队列进入对象的阻塞队列—>阻塞队列中的所有线程竞争对象锁,竞争到锁之后线程从阻塞状态进入就绪状态—>就绪状态的线程获取到cpu时间片之后进入运行中状态。
2.线程的方法
-
Thread.yield():让当前线程放弃锁,与其他线程一起竞争锁。
-
thread.join():thread线程执行完后,才会返回。
-
thread.setDaemon(true):将thread线程设为后台线程,当所有非后台线程被执行完毕,会杀死该进程中的所有后台线程。后台线程创建的线程默认都是后台线程。
-
TimeUnit.MILLISECONDS.sleep(1000);比Thread.sleep(1000)有更好的阅读性。
注:yield与sleep都是Thread的静态方法,表示让当前线程执行相应操作
3.ThreadLocal(线程本地存储)
防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。为使用相同变量的每个不同的线程都创建不同的存储,即为每个线程创建该变量的副本。
4.wait和while一般结合使用
public synchtonized void put(){
while(size == max){ //用while不用if,是因为如果等待的线程有多个,被叫醒之后,线程都直接往下走,而不再判断,会导致出现问题
this.wait();
}
this.notifyAll();
}
5.ReadWriteLock
适用于读多写少的情况,写的时候只能有一个线程获取写锁,读的时候可以有多个线程同时获得读锁。
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
Lock wlock = lock.writeLock(); //获取写锁
wlock.lock();
wlock.unlock();
Lock rlock = lock.readLock(); //获取读锁
rlock.lock();
rlock.unlock();
6.免锁容器
原理:对容器写入时,创建一个该容器的副本,对副本进行写入修改等操作,同时可以对原容器执行读操作,当写入操作完成时,将原容器的引用指向副本。可以避免加锁。
比如:copyOnWriteArrayList、copyOnWriteArraySet
3.线程池
1.原理
-
任务提交后,如果线程池中线程数少于corepoolsize,则创建线程执行该任务。否则将该任务加入阻塞队列。
-
如果阻塞队列已满,且线程数少于maximumPoolsize,则创建线程执行该任务。
-
如果线程数已等于maximumpoolsize,则执行饱和策略。
注:每次创建线程的时候都会获取全局锁,所以使用阻塞队列,可以尽可能避免获取全局锁。
2.Excutor框架
思想:将线程的工作单元和执行分开。由runnable和callable定义任务,由Excutor执行任务。
1.ThreadPoolExecutor
其类型有SingleThreadExcutor、FixedThreadPool、CacheThreadPool。由Excutors的静态方法生成相应对象,
//如:Executors.newFixedThreadPool(),其返回值为ExecutorService--->继承了Executor接口。
public static ExecutorService newFixedThreadPool(int nThreads) {
//ThreadPoolExecutor是ExecutorService的实现类。
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- SingleThreadExecutor:线程池只有一个线程,corepoolsize和maxpoolsize都为1,阻塞队列为无界的LinkedBlockingQueue。
- FixedThreadPool:线程池有指定的线程数,corepoolsize和maxpoolsize都为指定线程数,阻塞队列为无界的LinkedBlockingQueue。
- CacheThreadPool:corepoolsize为0,maxpoolsize为Integer.MAX_VALUE,使用SynchronousQueue作为阻塞队列,其没有容量,只有对应的空闲线程的poll操作和主线程的offer操作时,该任务才被线程执行,如果没有空闲线程,该线程池会一直创建线程;执行完任务的线程会再执行其他任务。线程空闲60s后,会被删除。
注:FixedThreadPool和SingleThreadExecutor使用了LinkedBlockingQueue,所以在没有空闲线程时,任务会进入阻塞队列,即任务等待;而CacheThreadPool则不会等待。
2.任务执行
execute():是Executor接口定义的方法,其参数只能为Runnable。
submit():是ExecutorService接口定义的方法,其参数可为Runnable和Callable,且有返回值,可用futureTask.get()获取返回值,get方法会阻塞当前线程直到获取到返回值。
FutureTask:实现了Future和Runnable接口,可以通过execute(futureTask)和submit(futureTask)执行任务。
//注:不使用线程池时,如果需要有返回值,则
FutureTask futureTask = new FutureTask(new Callable(){
public Object call(){}
});
new Thread(futureTask).start();
Object o = futureTask.get();