《Java编程思想》并发篇:
-
单核CPU某个时间点只能执行一个线程
-
Thread类实现了Runnable接口
- Runnable接口中就一个run方法
-
Thread类的线程对象调用start方法来启动一个线程
- start方法底层还调用了run方法
- 这其实是JVM底层与OS的交互,启动了一个线程
-
执行线程代码的方法:
-
写一个继承Thread类的子类,去重写run方法,再去创建这个子类的对象,调start方法
-
写一个类实现Runnable接口,去重写run方法,再去创建这个类的对象,把引用当作参数放入new Thread(),调start方法、
-
用线程池:
ExecutorService exec = Executors.newCachedThreadPool();//这种线程池是需要好多线程就创建好多线程 ExecutorService exec = Executors.newFixedThreadPool(线程数量);//预先指定线程数量 ExecutorService exec = Executors.newSingleThreadExxcutor();//就是FixedThreadPool创建一个线程 exec.execute(new LifeOff());
-
-
第二种方法比第一种更加灵活,平时使用线程池
-
Runnable是执行工作的独立任务,但它不返回任何值;如果需要任务完成时返回一个值,那么可以实现Callable接口而不是Runnable接口,实现它的call()方法,且必须使用ExecutorService.submit()方法调用它
-
yield()让步方法:让当前线程让出CPU的执行权,再由所有线程来公平竞争获取执行权
-
TimeUnit.SECONDS.sleep(休眠时间);使该线程进入睡眠状态
-
后台线程(守护线程)且必须在线程启动之前调用setDaemon(true)方法
-
join()方法:调用join()可以控制线程的执行顺序
-
共享资源:
-
内置锁synchornized:
-
java提供synchornized关键字为防止资源冲突提供了内置支持
-
当任务要执行的被synchornized保护的代码片段时,它将检查锁是否可以,执行代码,释放锁
-
所有对象都自动含有单一的锁(也称监视器)
-
对于某个特定对象来说,其所有方法共享同一把锁
-
synchornized (含锁的对象) {执行的代码块}
-
synchornized对实例方法修饰,其实就是加上this锁
-
synchornized对静态方法修饰,其实就是加上Class锁
-
-
外置锁Locks:
- 与synchornized相比,不优雅,更灵活
- 用synchornized关键字不能尝试着获取锁且最终获取锁会失败,或者尝试着获取锁一段时间,然后放弃它,要实现这些,必须实现concurrent库
-
-
原子性:
- 操作的原子性(不可切分)
- 原子性可以应用于除long和double之外的所有基本类型的简单操作
- 简单操作:简单的赋值和返回操作
- 但是使用volatile关键字修饰long和double时,就获得所谓的简单操作的原子性
- volatile关键字还确保了应用的可见性
- 可见性:就是对一个域进行写操作,那么所有的读操作就可以看到这个修改,读的时候就不会读成之前没改的时候的值,因为有些时候改了,存在缓存中,还没更新到内存
- 也就是说,volatile域会立即被写入到主存中,而读取操作就发生在主存中
- volatile关键字还确保了应用的可见性
- 如果多个任务在同时访问某个域,那这个域就应该是volatile的,否则只能同步来访问
- 同步操作也会导致向主存中刷新,导致可见性
- 一个任务所作的任何读写操作对于这个任务来说都是可见的,因此如果它只需要在这个任务中内部可视,那就不需设置volatile
- 第一选择都是使用synchornized关键字,这是最安全的方式,尝试其他方式都是有风险的
线程本地存储:
- 防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享
- 创建和管理线程本地存储可以由ThreadLocal类来实现
线程状态:
- 一个线程可能是以下四种状态:
- 新建(new):刚创建时,短暂处于这个状态,此时它被分配了必需的系统资源,执行了初始化
- 就绪(Runnable):只要调度器把时间片分配给线程就可以运行
- 阻塞(Blocked): 某个条件阻止它的运行
- 可能如下原因:
- sleep()
- wait()
- 在等待某个输入/输出
- 等待锁的释放
- 可能如下原因:
- 死亡(Dead):run()完就结束