• 基本概念
  • 进程是电脑程序运行的最小单位,而线程则是进程的最小执行单位
  • 多线程呢就是指一个进程运行时,多个线程并行交替执行的过程。可以提高代码的执行效离

实现方式
1.继承thread类
特点:因为是通过继承的方式,所以该类就不能继承其他的类了,有局限性
使用:直接使用该类调用start方法
2.实现Runnable接口
特点:通过实现接口的方式避免了第一种方式的单继承的局限性问题
使用:new Thread(实现了Runnable的类对象).start()
3.实现Callnable接口

解决多线程安全
1.使用image.pngimage.png

public class a {     //方法1     public void display1(Thread t) {         synchronized (this){             System.out.println(t.getName());             for (int i = 0; i < 100; i++) {                 System.out.println(i);             }         }     }      //方法2     public synchronized void display2(Thread t) {             System.out.println(t.getName());             for (int i = 0; i < 100; i++) {                 System.out.println(i);         }     }     public static void main(String[] args) {         a a = new a();         Thread thread1 = new Thread(new Runnable() {             @Override             public void run() {                 a.display2(Thread.currentThread());             }         },"a1");         Thread thread2 = new Thread(new Runnable() {             @Override             public void run() {                 a.display2(Thread.currentThread());             }         },"a2");          thread1.start();          thread2.start();     } } 

2.使用lock锁
举例:

 public class a {     Lock lock = new ReentrantLock(); //用于方法1     ReentrantReadWriteLock   locks = new ReentrantReadWriteLock  (); //用于方法2          //方法1   一般要写成try catch块的形式,把lock.unclock()方法放到finally里面     public void display1(Thread t) {         lock.lock();             System.out.println(t.getName()+":获取到了锁");             for (int i = 0; i < 100; i++) {                 System.out.println(i);             }         lock.unlock();     }      //方法2     public void display2(Thread t) {         locks.writeLock().lock(); //        locks.writeLock().lock();         try{             System.out.println(t.getName()+":获取到了锁");             for (int i = 0; i < 100; i++) {                 System.out.println(i);             }         }catch (Exception e){            e.printStackTrace();         }finally {             locks.writeLock().unlock(); //            locks.writeLock().unlock();         }     }       //方法3   锁降级     public void display4(Thread t) {         locks.writeLock().lock();         locks.readLock().lock();         try{             System.out.println(t.getName()+":获取到了锁");             for (int i = 0; i < 100; i++) {                 System.out.println(i);             }         }catch (Exception e){            e.printStackTrace();         }finally {             locks.readLock().unlock();             locks.writeLock().unlock();         }     }      //方法4   锁升级  (会产生死锁问题)     public void display4(Thread t) {     	locks.readLock().lock();         locks.writeLock().lock();         try{             System.out.println(t.getName()+":获取到了锁");             for (int i = 0; i < 100; i++) {                 System.out.println(i);             }         }catch (Exception e){            e.printStackTrace();         }finally {         	locks.writeLock().unlock();             locks.readLock().unlock();         }     }      public static void main(String[] args) {         a a = new a();         Thread thread1 = new Thread(new Runnable() {             @Override             public void run() {                 a.display1(Thread.currentThread());             }         },"a1");         Thread thread2 = new Thread(new Runnable() {             @Override             public void run() {                 a.display1(Thread.currentThread());             }         },"a2");          thread1.start();          thread2.start();     } }  

注意
使用lock锁时,lock必须定义到类里面,这样线程调用的时候拿到的是同一个lock对象,这样才能锁住

用法

lock实现ReentrantLock
常用方法及用处:
lock() 获取锁,获取不到会一直等待
trylock() 返回值是boolean类型,拿到锁就返回true,没有拿到就返回false,带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false
unlock() 释放锁

lock实现ReetrantReadWriteLock
读写锁的特性
1.获取锁顺序问题
非公平模式:读锁和写锁的获取的顺序是不确定的,可能会延缓一个或多个读或写线程,但是会比公平锁有更高的吞吐量
公平模式:线程将会以队列的顺序获取锁,等待锁时间越长的先获取
2.可重入:就是说一个线程在获取某个锁后,还可以继续获取该锁。synchronized内置锁就是可重入的,如果不可重入,那么比如一个类的两个方法都被synchronized所修饰,那么方法A就不能够调方法B了。
3.锁降级
首先要知道
锁升级:从读锁变成写锁
锁降级:从写锁变成读锁;
ReetrantReadWriteLock是不支持锁升级的,会产生死锁问题
4.读锁跟写锁是互斥的
对比synchronized使用,测试读操作效率

public class ReadAndWriteLockTest {     public synchronized static void get(Thread thread) {         System.out.println("start time:" + System.currentTimeMillis());         for (int i = 0; i < 5; i++) {             try {                 Thread.sleep(20);             } catch (InterruptedException e) {                 e.printStackTrace();             }             System.out.println(thread.getName() + ":正在进行读操作……");         }         System.out.println(thread.getName() + ":读操作完毕!");         System.out.println("end time:" + System.currentTimeMillis());     }      public static void main(String[] args) {         new Thread(new Runnable() {             @Override             public void run() {                 get(Thread.currentThread());             }         }).start();          new Thread(new Runnable() {             @Override             public void run() {                 get(Thread.currentThread());             }         }).start();     } } 

image.png

从运行结果可以看出,两个线程的读操作是顺序执行的,整个过程大概耗时200ms。

public class ReadAndWriteLockTest {     ReentrantReadWriteLock lock = new ReentrantReadWriteLock();      public static void get(Thread thread) {         lock.readLock().lock();         System.out.println("start time:" + System.currentTimeMillis());         for (int i = 0; i < 5; i++) {             try {                 Thread.sleep(20);             } catch (InterruptedException e) {                 e.printStackTrace();             }             System.out.println(thread.getName() + ":正在进行读操作……");         }         System.out.println(thread.getName() + ":读操作完毕!");         System.out.println("end time:" + System.currentTimeMillis());         lock.readLock().unlock();     }      public static void main(String[] args) {         new Thread(new Runnable() {             @Override             public void run() {                 get(Thread.currentThread());             }         }).start();          new Thread(new Runnable() {             @Override             public void run() {                 get(Thread.currentThread());             }         }).start();     } } 

image.png
从运行结果可以看出,两个线程的读操作是同时执行的,整个过程大概耗时100ms。通过两次实验的对比,我们可以看出来,ReetrantReadWriteLock的效率明显高于Synchronized关键字
判断读锁跟写锁互斥

public class ReadAndWriteLockTest {      public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();      public static void main(String[] args) {         //同时读、写         ExecutorService service = Executors.newCachedThreadPool();         service.execute(new Runnable() {             @Override             public void run() {                 readFile(Thread.currentThread());             }         });         service.execute(new Runnable() {             @Override             public void run() {                 writeFile(Thread.currentThread());             }         });     }      // 读操作     public static void readFile(Thread thread) {         lock.readLock().lock();         boolean readLock = lock.isWriteLocked();         if (!readLock) {             System.out.println("当前为读锁!");         }         try {             for (int i = 0; i < 5; i++) {                 try {                     Thread.sleep(20);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 System.out.println(thread.getName() + ":正在进行读操作……");             }             System.out.println(thread.getName() + ":读操作完毕!");         } finally {             System.out.println("释放读锁!");             lock.readLock().unlock();         }     }      // 写操作     public static void writeFile(Thread thread) {         lock.writeLock().lock();         boolean writeLock = lock.isWriteLocked();         if (writeLock) {             System.out.println("当前为写锁!");         }         try {             for (int i = 0; i < 5; i++) {                 try {                     Thread.sleep(20);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 System.out.println(thread.getName() + ":正在进行写操作……");             }             System.out.println(thread.getName() + ":写操作完毕!");         } finally {             System.out.println("释放写锁!");             lock.writeLock().unlock();         }     } }  

image.png
总结
1.Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性
2.ReetrantReadWriteLock读写锁的效率明显高于synchronized关键字
3.ReetrantReadWriteLock读写锁的实现中,读锁使用共享模式;写锁使用独占模式,换句话说,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的
4.ReetrantReadWriteLock读写锁的实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁

Condition
经典用例:可以实现生产者跟消费者的实例

lock可以使用Condition,Condition是一个多线程间协调通信的工具类,要与一个lock绑定使用

3.volatile关键字的使用
①volatile变量是Java语言提供了一种稍弱的同步机制
②volatile能够保证字段的可见性
②volatile能够禁止指令重排
原理:volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值
究竟能否保证线程安全?
答案是在一定条件下能,1.写入值时不依赖于当前值,不包含其他变量
经典用法
做状态标记量

 volatile boolean flag ;     ……     while( !flag ){         ……     }     public void setFlag() {        flag = true;     }  

synchronized 跟lock的区别
synchronized是java的一个关键字,是内置的语言实现的,是用来解决多线程安全问题的,他不能去获取到锁状态,当遇到异常的时候会自动的释放掉锁,synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,Synchronized存在明显的一个性能问题就是读与读之间互斥

lock是一个接口,
lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;

Lock可以通过trylock来知道有没有获取锁,而synchronized不能;

Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
思考

为什么在分布式情况下synchronized和lock会失效?
因为他们的前提条件必须是同一进程内,而分布式服务里面,多个服务属于不同进程,所以用普通的同步锁会失效,这里就要用到分布式锁去处理分布式锁

Q.E.D.


繁华看淡即是浮云;烦恼无数,想开就是晴天