多线程笔记

1.synchronized

1.synchronized的实现原理

Java中的每个对象都可以做为锁

  • 同步块的锁是括号内的对象
  • 实例同步方法的锁是当前实例对象
  • 静态同步方法的锁是当前类的Class对象

代码块同步由monitorenter和monitorexit实现的。每个对象都有一个monitor与之关联,当monitor被持有后,它将处于锁定状态。当线程执行到monitorenter时,会尝试获取对象所对应的monitor的所有权,即尝试获取锁。

synchronized的锁存在java对象头里。我觉得所谓monitor其实就是对象头里MarkWord中的锁状态。

在jdk1.6中,锁一共有四种状态,级别由低到高为:无锁,偏向锁,轻量级锁,重量级锁。

2.volatile

  1. 将当前处理器缓存行写回到系统内存
  2. 写回内存的操作会让其他cpu缓存了该内存地址的数据无效
//volitile保证可见性不能保证原子性,synchronized既能保证可见性又能保证原子性
count++ //此操作不是原子操作。分为两步:1、获取count值;2、加1
AomicInteger t = new AomicInteger(); //原子操作类。
t.incrementAndset();

3.ReentrantLock与syncronized的区别

  • lock.tryLock(); // ReentrantLock可以尝试性获取锁,获取不到锁就不再等待。
  • ReentrantLock有公平锁和非公平锁。公平锁实现中有先进先出队列,等待时间最长的线程优先获得锁

2.并发编程基础

1.线程的状态

  1. 初始状态(新建状态)
  2. 运行状态:“就绪”和“运行中”统称为运行
  3. 阻塞状态:一个任务进入阻塞状态可能有以下原因:
  • 调用了sleep()方法,使线程休眠;
  • 调用了wait()方法,使线程挂起;
  • 任务在等待某个输入输出完成;
  • 调用同步方法时,对象锁被其他线程获得;
  1. 终止状态

调用对象wait()方法之后,线程进入对象的等待队列(线程从运行中状态进入阻塞状态,调用wait()方法时必须先获取对象锁)—>调用对象的notify()方法之后,线程由等待队列进入对象的阻塞队列—>阻塞队列中的所有线程竞争对象锁,竞争到锁之后线程从阻塞状态进入就绪状态—>就绪状态的线程获取到cpu时间片之后进入运行中状态。

2.线程的方法

  1. Thread.yield():让当前线程放弃锁,与其他线程一起竞争锁。

  2. thread.join():thread线程执行完后,才会返回。

  3. thread.setDaemon(true):将thread线程设为后台线程,当所有非后台线程被执行完毕,会杀死该进程中的所有后台线程。后台线程创建的线程默认都是后台线程。

  4. 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.原理

  1. 任务提交后,如果线程池中线程数少于corepoolsize,则创建线程执行该任务。否则将该任务加入阻塞队列。

  2. 如果阻塞队列已满,且线程数少于maximumPoolsize,则创建线程执行该任务。

  3. 如果线程数已等于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();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值