多线程
一、基本概念
1.1程序、进程、线程
程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程
- 特点:
- 在单核CPU下同一个时间点只能有一个程序运行
- 宏观并行,微观串行
- 运行中的QQ,运行中的MP3播放器
- 程序是静态的,进程是动态的
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患
- 每个Java程序都有一个隐含的主线程:
main
方法 - 进程可以有多个线程,他们之间独立运行,交通执行称之为多线程
线程的组成:
- 任何一个线程都具有基本的组成部分:
- CPU时间片
- 操作系统(OS)会为每个线程分配执行时间
- 运行数据
- 堆空间:存储线程需使用对象,多个线程可以共享堆中的对象
- 栈空间 :存储线程需使用的局部变量,每个线程都拥有独立的栈
- 线程的逻辑代码
- CPU时间片
进程和线程的区别:
- 进程是操作系统资源分配的基本单位,而线程是CPU的基本单位
- 一个程序运行后至少有一个进程
- 一个进程可以包含多个线程,但是至少需要有一个线程,线程不能独立运行必须依附于线程当中
- 进程间不能共享数据段地址,但同进程下的线程之间可以
1.2多核CPU
-
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务
-
如果是多核的话,才能更好的发挥多线程的效率,现在的服务器都是多核的
1.3多线程应用场合
- 程序需要同时执行两个或多个任务;
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等;
- 需要一些后台运行的程序时
二、线程的创建和使用
2.1创建方式一:继承Thread
类
步骤:
- 定义子类继承
Thread
类 - 子类重写
Thread
类中的run
方法 - 创建
Thread
子类对象,即创建线程对象 - 调用线程对象
start
方法(启动线程,调用run
方法)
public class MyThread extends Thread{
//覆盖run方法
@Override
public void run() {
for (int i = 0; i < 50; i++) {
//currentThread()获取到当前进程 getName()获取对象名
System.out.println(Thread.currentThread().getName()+"======"+i);
}
}
}
public class Demo {
public static void main(String[] args) {
//创建线程对象
MyThread t1 = new MyThread();
//启动线程
t1.start();
//创建多个线程对象
MyThread t2 = new MyThread();
t2.start();
MyThread t3 = new MyThread();
t3.start();
}
}
2.2创建方式二:实现Runnable
接口
步骤:
- 定义子类实现
Runnable
接口 - 子类中重写
Runnable
接口中的run
方法 - 通过
Thread
类构造方法创建线程对象 - 将
Runnable
接口的子类对象作为实际参数传递给Thread
类的构造方法中 - 调用
Thread
类的start
方法:开启线程,调用Runnable
子类接口的run
方法
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//currentThread()获取到当前进程 getName()获取对象名
System.out.println(Thread.currentThread().getName()+"======"+i);
}
}
}
public class Demo {
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread2());//MyThread2()作为一个线程任务启动线程
t1.start();
}
}
2.3两种方式的使用细节
- 启动线程不是调用run,而是调用start方法
- 同一个线程不能启动多次
- 继承Thread类和实现Runnable接口的区别
- 继承Thread类则无法继承别的类(java是单继承),使用不够灵活,而实现接口Runnable可以继承别的类,更灵活
- 继承Thread类只能表示这是一个线程类,可以之间调用start方法启动线程,接口只能表示当前类为一个线程任务,需要创建Thread对象执行这个线程任务
2.4启动线程需要注意的问题
- 不要调用run方法进行启动线程
- 一个线程只能调用一次start方法
2.5线程的状态
- New 初始状态
- 线程对象被创建即为初始状态 只在堆中开辟内存,与常规对象无异
- Ready 就绪状态
- 调用start()之后,进入就行状态 等待OS选中,并分配时间片
- Running 运行状态
- 获得时间片之后,进入运行状态如果时间片到期,则返回到就绪状态
- Terminated 终止状态
- 主线程main()或独立线程run()结束,进入终止状态,并释放持有的时间片
2.6常见方法
2.6.1设置线程名称
setName()
、getName()
获取到当前进程对象currentThread()
**注意:**如果没有设置线程名称,那么默认的名字就为Tread-0、Tread-1、Tread-2…
public class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"======"+"启动了");
}
}
public class Demo{
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread3());
//setName给线程取名字
t1.setName("线程");
t1.start();
}
}
2.6.2设置线程的优先级
设置优先级方法setPriority()
- 线程优先级1-10 线程的优先级越高 抢占CPU概率越大
获取线程的优先级方法getPriority()
- 线程的优先级默认为5
2.6.3休眠
sleep(long m)
- 当前线程主动休眠 m 毫秒
- 让线程进入休眠,让出CPU的使用权,直到休眠结束,才会继续抢占CPU
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//线程休眠 参数:休眠的时间 (毫秒)
try {
//让当前线程处于睡眠时间,知道时间结束才会继续向下执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
public class Demo {
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread4());
//setName给线程取名字
t1.setName("线程t1");
t1.start();
}
}
2.6.4放弃(礼让)
yield()
- 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
- 让出CPU的使用权,但是立马又会重新抢占CPU
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i==5){
//线程礼让 放弃当前的CPU使用权,进入到就绪状态
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"启动了");
}
}
}
public class MyThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"启动了");
}
}
}
public class Demo {
public static void main(String[] args) {
MyThread5 t1 = new MyThread5();
t1.setName("线程1");
t1.start();
Thread t2 = new Thread(new MyThread3());
t2.setName("线程2");
t2.start();
}
}
2.6.5结合(加入线程)
join()
- 允许其他线程加入到当前线程中
- 在当前线程加入另一个线程,必须将另一个加入的线程完全执行后才能执行当前线程
public class MyThread1 extends Thread{
//覆盖run方法
@Override
public void run() {
for (int i = 0; i < 20; i++) {
//currentThread()获取到当前进程 getName()获取对象名
System.out.println(Thread.currentThread().getName());
}
}
}
public class MyThread2 extends Thread{
@Override
public void run() {
MyThread t = new MyThread();
t.setName("加入");
for (int i = 0; i <50; i++) {
if (i==5){
t.start();
try {
//将t加入到线程中执行 特点:必须要t完全指向结束之后 才能执行当前的线程任务
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"执行"+i+"次");
}
}
}
public class Demo {
public static void main(String[] args) {
MyThread6 t1 = new MyThread6();
t1.start();
}
}
2.7线程的状态
期限等待(Timed Waiting)和无期限等待(Waiting)
- 初始状态—就绪状态—运行状态—(sleep)—期限等待(睡眠时间到期)—就绪状态—运行状态—终止状态
- 初始状态—就绪状态—运行状态—(join)—无期限等待(满足条件、加入的线程完全执行后)—就绪状态—运行状态—终止状态
三、线程安全问题
存在的问题
public class ThreadDemo extends Thread{
//临界资源
//模拟一百张票
static int ticket = 100;
@Override
public void run() {
while (true){
if(ticket==0){//当临界资源 小于0或者等于0时候表示没有票了
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖出第"+(ticket--)+"票");
}
}
}
public class Demo {
public static void main(String[] args) {
ThreadDemo01 t1 = new ThreadDemo01();
t1.setName("售票员1号");
ThreadDemo01 t2 = new ThreadDemo01();
t2.setName("售票员2号");
ThreadDemo01 t3 = new ThreadDemo01();
t3.setName("售票员3号");
ThreadDemo01 t4 = new ThreadDemo01();
t4.setName("售票员4号");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
- 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其准确性
- 原子操作:不可拆分的步骤,被视作一个整体,其顺序和步骤不可打乱或省缺
Java对于多线程的安全问题提供了专业的解决方式:同步机制
3.1线程同步
synchronized
是互斥锁
3.1.1synchronized
代码块(同步代码块)
public class ThreadDemo extends Thread {
//临界资源
//模拟一百张票
static int ticket = 100;
@Override
public void run() {
while (true) {
/* synchronized代码块运行完毕,才会切换到另一个线程执行
* 涉及到多个线程修改的地方要放到synchronized代码块中
* 锁:一定要是所有线程共享的
*/
synchronized (this.getClass()) {//互斥锁对象,互斥锁对象可以是Java中任意对象但是要保证唯一
if (ticket <= 0) {//当临界资源 小于0或者等于0时候表示没有票了
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出第" + (ticket--) + "票");
}//释放锁标记
}
}
}
public class Demo{
public static void main(String[] args) {
ThreadDemo02 t1 = new ThreadDemo02();
t1.setName("售票员1号");
ThreadDemo02 t2 = new ThreadDemo02();
t2.setName("售票员2号");
ThreadDemo02 t3 = new ThreadDemo02();
t3.setName("售票员3号");
ThreadDemo02 t4 = new ThreadDemo02();
t4.setName("售票员4号");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
2.1.2synchronized
方法(同步方法)
语法:
synchronized 返回值类型 方法名称(形参列表){//当前对象(this)加锁
//代码(原子操作)
}
注:
- 只有拥有对象互斥锁标记的线程,才能进入该对象的同步方法中
- 线程退出同步方法时,会释放相应的互斥锁标记
方式一:
public class ThreadDemo extends Thread{
static int ticket = 100;
@Override
public void run() {
while (true) {
sale();
}
}
//成员方法,那么同步方法的互斥锁对象就是this
//类(static)方法,那那么同步方法的互斥锁对象就是类对象
public synchronized static void sale(){
if (ticket<=0){
System.exit(0);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出第" + (ticket--) + "票");
}
}
public class Demo {
public static void main(String[] args) {
ThreadDemo03 t1 = new ThreadDemo03();
ThreadDemo03 t2 = new ThreadDemo03();
ThreadDemo03 t3 = new ThreadDemo03();
ThreadDemo03 t4 = new ThreadDemo03();
t1.setName("售票员1号");
t2.setName("售票员2号");
t3.setName("售票员3号");
t4.setName("售票员4号");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
方式二:
public class ThreadDemo implements Runnable {
//都是线程任务,因为都是通过同一个Runnable 接口实现类
static int ticket = 100;
@Override
public void run() {
while (true) {
sale();
}
}
public synchronized static void sale(){
if (ticket<=0){
System.exit(0);
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出第" + (ticket--) + "票");
}
}
public class Demo {
public static void main(String[] args) {
ThreadDemo04 task = new ThreadDemo04();
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
Thread t3 = new Thread(task);
Thread t4 = new Thread(task);
t1.setName("售票员1号");
t2.setName("售票员2号");
t3.setName("售票员3号");
t4.setName("售票员4号");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
注意:
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记
- 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用
已知JDK中线程安全的类:
- StringBuffer
- Vector
- Hashtable
- 以上类中的的公开方法,均为synchronized修饰的同步方法
四、线程的生命周期
创建状态 就绪状态 运行状态 阻塞\等待状态 终止状态
五、死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
public class MyLock {
static Object obj1 = new Object();//一根筷子
static Object obj2 = new Object();//另一根筷子
}
public class Boy extends Thread {
@Override
public void run() {
synchronized (MyLock.obj1){//男孩有一根筷子
System.out.println("男孩有一根筷子在等待另一根筷子");
synchronized (MyLock.obj2){//obj2 另一根筷子
System.out.println("男孩吃饭了");
}
}
}
}
public class Girl extends Thread{
@Override
public void run() {
synchronized (MyLock.obj2){//女孩孩有另一根筷子
System.out.println("女孩有一根筷子在等待另一根筷子");
synchronized (MyLock.obj1){//obj1 另一根筷子
System.out.println("女孩吃饭了");
}
}
}
}
public class Demo {
public static void main(String[] args) {
Boy boy = new Boy();
Girl girl = new Girl();
boy.start();
girl.start();
}
}
解决方法:
- 专门的算法、原则
- 打破互斥条件
- 尽量避免嵌套同步
六、线程通信
相关方法:
wait()
:令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()
或notifyAll()
方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行notify()
:唤醒正在排队等待同步资源的线程中优先级最高者结束等待notifyAll()
:唤醒正在排队等待资源的所有线程结束等待
以上三个方法只有在synchronized
方法或synchronized
代码块中才能使用
public class Phone {
String name;
}
public class Shop {
// 表示一个买卖的商品
Phone phone;
//进货(生产)
public synchronized void putPhone(Phone phone){//参数表示进货的商品
if(this.phone!=null){
System.out.println("表示有商品,需要等待");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//表示没有商品需要生产商品
System.out.println("正在生产商品"+phone.name);
this.phone=phone;
//通过消费者来消费
this.notify();
}
//消费
public synchronized void getPhone(){
if (this.phone==null){//表示没有商品需要等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("表示正在消费商品"+phone.name);
//模拟消费商品
this.phone=null;
//通知生产者生产9
this.notify();
}
}
public class Test {
static Shop shop = new Shop();
public static void main(String[] args) {
ProducerThread pro = new ProducerThread();//生产者
CustomerThread cus = new CustomerThread();//消费者
pro.start();
cus.start();
}
}
class ProducerThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
Test.shop.getPhone();
}
}
}
class CustomerThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
Phone phone = new Phone();
phone.name = "Iphone"+i;
Test.shop.putPhone(phone);
}
}
}
七、线程池概念
- 现有问题:
- 线程是宝贵的内存资源,单个线程约占1MB空间,过多分配易造成内存溢出
- 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销、造成程序性能下降
7.1线程池
- 线程容器,可设定线程分配的数量上限
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象
- 避免频繁的创建和销毁
- 线程池维护着一个对了,队列中保存着处于等待(空闲状态的)线程,不用每次创建新的线程
7.2线程池原理
- 将任务交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程
7.4获取线程池
- 常用线程池接口和类(所在包java.util.concurrent)
- Executor:线程池顶级接口
- ExecutorService:线程池接口,可以通过submit(Runnable task)提交任务代码
- Executors工厂类:通过此类可以获得一个线程池
- newFixedThreadPool(int nThreads)获取固定数量的线程池。参数指定线程池中的线程的数量
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyTaskDemo01 {
public static void main(String[] args) {
//创建线程池对象(创建一个线程数为3的线程对象)
ExecutorService es = Executors.newFixedThreadPool(3);
//提交线程任务
es.submit(new MyTask());
es.submit(new MyTask());
es.submit(new MyTask());
es.submit(new MyTask());
//关闭线程池
es.shutdown();
}
}
class MyTask implements Runnable{//线程任务类
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
- newCacheThreadPool()获得动态线程数量的线程池,如不够则创建新的,无上限
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyTaskDemo02 {
public static void main(String[] args) {
//创建无上限的线程池对象
ExecutorService es = Executors.newCachedThreadPool();
//往线程池提交线程任务
//匿名内部类
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
});
//关闭线程池
es.shutdown();
}
}
八、实现Callable
接口
JDK5.0新增线程创建方式Callable
与Runnable
类似,实现之后代表一个线程任务
与使用Runnable
相比, 功能更强大些
- 有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助
FutureTask
类,比如获取返回结果
import java.util.concurrent.*;
public class MyTaskDemo03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建有限的线程池对象 容量为3
ExecutorService es = Executors.newFixedThreadPool(3);
//通过线程池提交线程任务
/* Future f1 = es.submit(new MyTask01());
es.submit(new MyTask01());
es.submit(new MyTask01());
es.submit(new MyTask01());
es.submit(new MyTask01());
System.out.println("这是主线程的代码1");
System.out.println(f1.get());
*/
Future f2 = es.submit(new MyTask02());
es.submit(new MyTask02());
es.submit(new MyTask02());
es.submit(new MyTask02());
es.submit(new MyTask02());
System.out.println("这是主线程的代码2");
System.out.println(f2.get());
//关闭线程池
es.shutdown();
}
}
class MyTask01 implements Callable{
@Override
public Object call() throws Exception {
//线程执行的代码
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
return null;
}
}
class MyTask02 implements Callable<String>{//泛型表示:coll方法的返回值
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "Hello";
}
}
关于Future
接口
- 可以对具体
Runnable
、Callable
任务的执行结果进行取消、查询是否完成、获取结果等 FutrueTask
是Futrue
接口的唯一的实现类FutureTask
同时实现了Runnable
、Future
接口。它既可以作为Runnable
被线程执行,又可以作为Future
得到Callable
的返回值
import java.util.concurrent.*;
public class MyTaskDemo04 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(2);
//Future接口就是用于接收Callable线程任务的返回值
Future<String> future = es.submit(new MyTask1());
System.out.println(future.get());
es.shutdown();
}
}
class MyTask1 implements Callable<String> {
@Override
public String call() throws Exception {
//线程执行代码
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
return "返回值";
}
}
九、Lock接口
JDK 5.0加入与synchronized比较,显示定义,更加灵活
提供更多实用性方法,功能更强大、性能更优越
9.1Lock锁
ReentrantLock()
重入锁Lock``synchronized
都具有互斥锁功能
常用方法:
lock()
获取锁tryLock()
尝试获取锁unlock()
释放锁
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
public class Demo {
public static void main(String[] args) {
//创建线程池
ExecutorService es = Executors.newFixedThreadPool(4);
//通过线程池提交线程任务
TicketTask task = new TicketTask();
es.submit(task);
es.submit(task);
es.submit(task);
es.submit(task);
//关闭线程池
es.shutdown();
}
}
class TicketTask implements Runnable{
ReentrantLock lock = new ReentrantLock();//重入锁
static int ticket = 300;
@Override
public void run() {
while (true) {
try {
lock.lock();//获取锁
if (ticket<1){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖出第"+(ticket--)+"号票");
}finally {
lock.unlock();//释放锁
}
}
}
}
9.2读写锁
ReentrantReadWriteLock
- 一种支持一写多读的同步锁,实现读写分离,可分别分配读、写锁
- 支持多分配读锁,使多个读操作并发执行
互斥规则
- 写写:互斥 阻塞
- 读写:互斥 写阻塞读 读阻塞写
- 读读:不互斥 不阻塞
- 在读操作高于写操作的环境中,可在保障线程安全的情况下提高运行效率
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class User {
String name;
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();
Lock writeLock = lock.writeLock();
//写
public void setName(String name){
readLock.lock();
try {
this.name = name;
} catch (Exception e) {
e.printStackTrace();
}finally {
readLock.unlock();
}
}
//读
public String getName() {
writeLock.lock();
try {
return name;
} catch (Exception e) {
e.printStackTrace();
}finally {
writeLock.unlock();
}
return null;
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo02 {
public static void main(String[] args) {
User user = new User();
ExecutorService es = Executors.newFixedThreadPool(20);
//提交线程任务
long start = System.currentTimeMillis();//记录时间
//写的任务
es.submit(new WriteTask(user));
es.submit(new WriteTask(user));
//读的任务
for (int i = 0; i <18; i++) {
es.submit(new ReadTask(user));
}
//关闭线程池
es.shutdown();
//判断线程是否还在运行
System.out.println(es.isTerminated());
while (!es.isTerminated()) {//让代码空转
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
}
class ReadTask implements Runnable{
User user =new User();
public ReadTask(User user){
this.user = user;
}
@Override
public void run() {
user.getName();
}
}
class WriteTask implements Runnable{
static int i=1;//辨认操作 写了几次
User user =new User();
public WriteTask(User user){
this.user = user;
}
@Override
public void run() {
user.setName("aaa");
}
}
9.3重入锁
- 重入锁
- 也叫递归锁,指的是同一个线程外层函数获得了一把锁,内层函数同样具有这把锁的控制权限
synchronized
和Lock
都可以实现锁的重入
public class Demo03 {
public static void main(String[] args) {
new Thread(new MyRunnable()).start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
a();
}
public synchronized void a(){
System.out.println("a");
b();
}
//当前锁对象为this
public synchronized void b(){
System.out.println("b");
c();
}
//当前锁对象为this
public synchronized void c(){
System.out.println("c");
}
}
9.4公平锁
-
公平锁与非公平锁
-
非公平锁:优先使用上一个线程接着执行下面的线程任务
synchronized
是非公平锁,无法实现公平锁lock
默认是非公平锁,那么就需要在构造方法中设置ture
-
公平锁:每个线程公平的执行任务
-
lock
可以实现公平锁 -
new ReentrantLock( true )//实现公平锁
-
synchronized
无法实现公平锁
-
-
9.5synchronized
锁升级
-
synchronized
锁的状态有四种-
无锁
- 当锁对象被创建的时候,还没有进入到线程时,此时锁对象处于无锁状态
-
偏向锁
- 当有线程A进入同步代码块并获得锁对象,此时保存线程ID,以后此线程进入到同步代码块,无需使用CAS加锁或者释放锁,只需要验证线程ID即可
-
轻量级锁(自旋锁)
- 当前锁处于偏向锁,此时有线程B进入到同步代码块,这个时候线程AB会使用CAS(比较和交换算法)竞争,并升级为轻量级锁
-
重量级锁
- 如果线程没有获得轻量级锁,线程会通过CAS自旋获得锁对象如果自旋次数大于阈值(10次),则升级为重量级锁
-
-
synchronized
在JDK1.6中提供锁的四种状态,可以自动升级,但不允许降级
{
new Thread(new MyRunnable()).start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
a();
}
public synchronized void a(){
System.out.println(“a”);
b();
}
//当前锁对象为this
public synchronized void b(){
System.out.println(“b”);
c();
}
//当前锁对象为this
public synchronized void c(){
System.out.println(“c”);
}
}
### 9.4公平锁
- 公平锁与非公平锁
- 非公平锁:优先使用上一个线程接着执行下面的线程任务
- `synchronized`是非公平锁,无法实现公平锁
- `lock`默认是非公平锁,那么就需要在构造方法中设置ture
- 公平锁:每个线程公平的执行任务
- `lock`可以实现公平锁
- ```java
new ReentrantLock( true )//实现公平锁
```
- `synchronized`无法实现公平锁
### 9.5`synchronized`锁升级
- `synchronized`锁的状态有四种
- 无锁
- 当锁对象被创建的时候,还没有进入到线程时,此时锁对象处于无锁状态
- 偏向锁
- 当有线程A进入同步代码块并获得锁对象,此时保存线程ID,以后此线程进入到同步代码块,无需使用CAS加锁或者释放锁,只需要验证线程ID即可
- 轻量级锁(自旋锁)
- 当前锁处于偏向锁,此时有线程B进入到同步代码块,这个时候线程AB会使用CAS(比较和交换算法)竞争,并升级为轻量级锁
- 重量级锁
- 如果线程没有获得轻量级锁,线程会通过CAS自旋获得锁对象如果自旋次数大于阈值(10次),则升级为重量级锁
- `synchronized`在JDK1.6中提供锁的四种状态,可以自动升级,但不允许降级
- `synchronized`在代码出现异常时会释放锁 `Lock`在代码出现异常时不会释放锁