Java多线程

Posted hetaoyuan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程相关的知识,希望对你有一定的参考价值。

进程和线程的区别?

进程就是正在执行的程序,是操作系统控制的基本运行单元

线程是程序执行最小单元

为何要引入线程的概念?

一个服务器通常需要接收大量不确定数量用户的并发请求,为每一个请求都创建一个进程显然行不通(开销太大用户响应效率低),因此操作系统中线程概念被引进。


Java中多线程的实现方式?

1、继承Thread类

2、runnable

3、callable

 

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;


class CallableTask implements Callable<String> 
    private int ticket = 20;
    @Override
    public String call() throws Exception 
        for (int i = 0; i < 20; i++) 
            if (ticket > 0) 
                System.out.println(Thread.currentThread().getName()+
                        "还剩下"+ticket--+"票");
            
        
        return "票已卖完,客官下次见~";
    

class FutureTaskDemo 
    public static void main(String[] args) 
        CallableTask callableTask = new CallableTask();
        FutureTask<String> futureTask = new FutureTask<>(callableTask);
        Thread thread = new Thread(futureTask,"黄牛A");
        Thread thread1 = new Thread(futureTask,"黄牛B");
        Thread thread2 = new Thread(futureTask,"黄牛C");
        thread.start();
        thread1.start();
        thread2.start();
    

 

技术图片

 

 

4、线程池


多线程的几种操作方法?

  • sleep(),将线程由运行态到阻塞态,不会释放锁,立即交出CPU

 

  • yield():线程让步,运行态 -> 就绪态,不会释放对象锁,交出cpu时间不确定,由系统调度,如果交出,那些线程可以获得呢?

         只会让具有相同优先级的线程有获取cpu的机会

 

  • join():当前线程等待另一个线程执行完毕后,在恢复执行,运行态->阻塞态(主方法调用主方法也会阻塞),是否会释放锁?会释放锁。join()实际上是wait()方法的一个包装

线程有几种状态?  新建 -> 就绪 -> 运行->阻塞 ->终止

  • 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
  • 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,

         随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

  • 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就

         绪状态是进入到运行状态的入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

  • 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。

         根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。


 

多线程的等待与唤醒机制
wait()/notify():synchronized
要使用wait和notify方法必须要在同步方法和同步代码块中使用
wait方法等待一个资源把当前线程阻塞,如果调用,会一直阻塞

几个线程一起整一把锁,成功的获取锁,失败的进入同步队列,获取锁成功的线程之后执行wait方法
变为阻塞态进入等待队列,直到被notify方法唤醒进入到同步队列的末尾,等待下一次重新获取锁

补充:被notify唤醒后什么时候释放锁?
并不会立即释放锁,而是notify线程将自己同步代码块中的代码块执行完毕之后才会释放锁,而wait方法会立即释放锁

任意一个monitor只有一个同步队列和一个等待队列(生产消费者模型调用wait方法后都进入一个队列),当生产数量为0的时候,notifyAll唤醒所有线程,包括生产者模型,所以当只有一个等待
队列的时候,会造成不该唤醒的线程又被唤醒,造成阻塞和性能开销

wait()/notify() 方法只能和synchronized 一起使用
await()/signal 和lock一起使用
任意一个lock对象有一个同步队列和多个等待队列(lock,condition()),每一个new condition对象产生就相当于绑定了一个新的等待队列

Object对象及其子类每个对象都拥有两个队列
同步队列:获取该对象锁失败的线程进入到同步队列
等待队列:调用wait方法进入到等待队列,等待被notify方法重新唤醒

wait()从运行态 -> 阻塞态 立即释放锁
notfy() 从阻塞态 -> 就绪态 先执行完同步代码块或者同步方法中的代码再释放锁
这两个方法必须在同步代码块或者同步方法中使用,会释放对象锁

什么是守护线程?
Java只有两类线程,用户线程和守护线程
创建的线程默认都是用户线程,包括主线程
守护线程:后台线程,只有当前JVM进程中最后一个用户线程终止,守护线程会随着JVM一起终止
比如:GC线程就是典型的守护线程
在Thread类中有一个方法setDeamn()将用户线程设置为守护线程

JVM内存模型(JMM):描述并发程序的内存模型  描述共享变量(类成员变量,静态变量)如何存储

工作内存:变量在线程中的操作(读写)必须在工作内存中进行,工作内存中保存了所有变量的副本

主内存:所有变量必须在主内存中存储

线程安全:
原子性:一组操作要么同时发生,要么一个都不发生 关于基本类型的读写操作都属于原子性操作(变量不算,只发生一次)
可见性:线程对于变量的修改,对于其他线程立即可见synchronized(lock),volatile,final
有序性:在单线程场景下,所有代码执行的顺序就是我们代码的书写顺序,多线程场景下,所有代码都是乱序的

线程安全指的是以上三个特性同时满足

如何解决同步问题?
使用synchronized关键字来解决同步问题
同步
保护的对象是谁 锁的是
同步代码块
synchronized(对象)
任意类的对象
类.class\\

同步方法:
修饰类成员方法: 锁的是当前对象this
修饰类成员方法(static)锁的是当前类的反射对象
synchronized底层实现:对象的Monitor机制

任意Object及其子类对象都会在JVM中附加Monitor,获取一个对象锁,实际上就是获取该对象的Monitor(对象的监视器)
当一个线程尝试获取对象的Monitor(本质上是一个计数器)时:
1.若此时Monitor的值为0,表示该对象未被任何线程获取,当前线程获取Monitor,将持有线程置为当前线程,Monitor的值+1

2.若此时Monitor的值不为0,表示该Monitor的对象已被持有
a、若当前线程恰好是持有线程,Monitor的值再次+1,当前线程进入同步块(锁的可重入性
b、若持有线程不是当前线程,当前线程进入到阻塞态(放到同步队列)等待Monitor的值再次减为0(获取锁失败的线程放入到同步队列),也就是Monitor的锁重新释放

加锁对应的是 monitorenter+1的指令
解锁对应的是 monitorexit -1 的指令

任意时刻只有当Monitor的值为0,表示无锁状态

jdk1.6之后synchronized的优化问题
CAS:Compare and Swap 无锁保证线程安全
CAS(O,V, N)
O:当前线程认为的变量值
V:主内存存放的实际变量值
N:希望将变量替换的值

以卖票为例:

主内存中 ticket:1
线程1 ticket = 1 ,1->0,CAS(1,1,0) 此时O==V ,认为此时没有线程修改主内存的值
线程2 ticket = 1 ,CAS(1,0,0)此时O != V,认为此时已经有别的线程修改了主内存的值,修改失败,失败主内存的最新值
线程3 ticket = 1

当O == V时候,认为此时没有线程修改变量值,成功将N值替换回主内存
当O != V 时候,此时已经有线程修改变量值,替换失败,返回主内存的最新值再次尝试

联想?
mysql中也有类似CAS操作,行锁

这么做的隐患?
线程2与3并发,其中一个修改了主内存中的值,那么另一个会发生脏读,如何解决?ABA问题:
num = 0
解决问题:添加版本号
每次修改值,需要传入初始值和初始版本号,
当AtomicStampedReference设置对象值时,对象值以及状态戳都必须满足期望值,写入才会成功
例如:
1、初始值100,初始版本号1
2、线程t1和t2拿到一样的初始版本号
3、线程t1完成ABA操作,版本号递增到3
4、线程t2完成CAS操作,最新版本号已经变成3,跟线程t2之前拿到的版本号1不相等,操作失败

锁的等级:
偏向锁 > 轻量级锁 > 重量级锁(在jdk1.6之前synchronized就是重量级锁,但是随着Java SE 1.6对synchronized进行了各种优化之后, 有些情况下它就并不那么重了)

先说说什么叫重量级锁
重量级锁:也叫悲观锁,认为只要当前线程去竞争锁,就一定有其他线程去和它抢锁,获取Monitor失败的线程进入到同步队列,状态置为阻塞态

什么叫偏向锁(乐观锁, 认为只要当前线程去获取锁,就没有其他线程去和它竞争):偏向锁认为只有一个线程在来回进入同步代码块,从而直接将加锁和解锁的过程全部省略,
每次进入同步代码块之前只是判断一下同步代码线程是否是当前线程(相当于绿灯)

什么是轻量级锁:(相等于黄灯)
不同时刻有不同线程进入同步块
每次线程在进入到同步块的时候,都需要加锁和解锁

随着竞争的激烈,
红灯,重量级锁:
同一时刻有不同线程进入到代码块
随着竞争的不断升级,锁也会升级,但锁不会降级

在jdk1.6之后,synchronized获得了优化,
1、自适应自旋:重量级锁的优化
获取锁失败的线程不会立即阻塞,而是在CPU空跑一段无用的代码,若在此时间段重新获得锁,则下次再获取锁失败的时候,空跑时间适当延长,
否则下次空炮时间缩短(开销减小)

2、锁粗化
StringBuffer(加了synchronized,线程安全,效率低)和StringBuilder(字符串+拼接,默认用了这个,线程不安全,但是效率高,append方法)
StringBuffer的append方法, 将多次连续的append加减锁过程粗化为一次大的加减锁过程,减少无用的加减锁过程,提高效率(
sb.append;sb.append;sb.append,防止每次append加减锁,一共三次)

3、锁消除
当变量为线程私有变量的时候,将原先方法上的synchronized去掉
死锁:
class Boy
class Girl
class Test7
    public static void main(String[] args) 
        Boy boy  = new Boy();
        Girl girl = new Girl();

        Thread boythread = new Thread(() ->
            synchronized (boy)
                System.out.println("my name is zs");
                synchronized (girl)
                    System.out.println("i love you");
                
            
        );

        Thread girlthread = new Thread(() ->
            synchronized (girl)
                System.out.println("my name is beauty");
                synchronized (boy)
                    System.out.println("i don‘t love you");
                
            
        );
        boythread.start();
        girlthread.start();
    

运行结果:

技术图片



产生死锁的四个条件:
1、互斥:资源x在任意一个时刻只能被一个线程持有(synchronized)
2、占有且等待:资源x被线程1占有,同时在等待资源y,但并不释放资源x
3、不可抢占:资源x一旦被线程1占有后,其他线程并不能抢占x
4、循环等待:线程1持有x,等待y:线程2持有y,等待x

死锁产生的原因:以上四个条件同时满足,(充要条件)

如何来解决死锁?
synchronized如何来解决死锁,基本没办法解决

解决方法:
jdk1.5 lock(接口)体系:

a、使用格式
try
//同步代码块
//显示加锁
lock.lock();


catch(Exception e)

finally
//显示解锁
lock.unlock;


b、常用方法
lock():加锁,语义和synchronized完全一致
unlock():解锁



import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class TicketTask implements Runnable 
    private int ticket = 20;
    //lock是接口不能直接new,需要子类
    private Lock lock = new ReentrantLock();
    @Override
    public void run() 
        for (int i = 0; i < 20; i++) 
            try 
                lock.lock();
                if (ticket > 0) 
                    try 
                        TimeUnit.MILLISECONDS.sleep(20);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    System.out.println(Thread.currentThread().getName()+
                            "还剩"+ticket--+"票");
                
            catch (Exception e)

            finally 
                lock.unlock();
            
        
    

class Ticket 

    public static void main(String[] args) 
        TicketTask task = new TicketTask();
        new Thread(task,"黄牛A").start();
        new Thread(task,"黄牛B").start();
        new Thread(task,"黄牛C").start();
    

技术图片

 




 void lockInterrupt() throws InterruptedException:响应式中断加锁  破怀条件3

 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;

 class Boy
 class Girl
 class Test7
 public static void main(String[] args) throws InterruptedException 
 Lock boylock = new ReentrantLock();
 Lock girllock = new ReentrantLock();
 Thread boythread = new Thread(() ->
 try
 boylock.lockInterruptibly();
 System.out.println("my name is zs");
 girllock.lockInterruptibly();
 System.out.println("111111111");
 
 catch (Exception e)

 
 finally 
 boylock.unlock();
 girllock.unlock();
 
 );

 Thread girlthread = new Thread(() ->
 try
 girllock.lockInterruptibly();
 System.out.println("my name is beauty");
 boylock.lockInterruptibly();
 System.out.println("2222222");
 
 catch (Exception e)

 
 finally 
 girllock.unlock();
 boylock.unlock();
 
 );
 boythread.start();
 girlthread.start();

 //主线程睡上一秒
 TimeUnit.SECONDS.sleep(1);
 //boythread不能中断,把状态改为true,并不对线程产生影响,不响应中断
 //效果:占有破怀不可抢占条件,可让其释放锁
 boythread.interrupt();
 
 

 

技术图片

 boolean tryLock() 非阻塞式获取锁,获取锁成功返回true,进入同步块
 获取锁失败返回false,线程继续执行其他代码                              破怀条件2
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;

 class Boy
 class Girl
 class Test7
 public static void main(String[] args) throws InterruptedException 
 Lock boylock = new ReentrantLock();
 Lock girllock = new ReentrantLock();
 Thread boythread = new Thread(() ->
 try
 boylock.lockInterruptibly();
 System.out.println("my name is zs");
 if (girllock.tryLock())
 System.out.println("获取girl成功");
 
 else
 System.out.println("获取girl失败,继续等待其他girl");
 
 
 catch (Exception e)
 
 finally 
 boylock.unlock();
 
 );

 Thread girlthread = new Thread(() ->
 try
 girllock.lockInterruptibly();
 System.out.println("my name is beauty");
 if(boylock.tryLock())
 System.out.println("获取boy失败");
 
 else 
 System.out.println("获取boy失败,继续尝试其他boy...");
 
 
 catch (Exception e)
 
 finally 
 girllock.unlock();
 
 );
 boythread.start();
 girlthread.start();

 
 

技术图片

 


 boolean tryLock(long time,TimeUnit unit)throws InterruptedException:支持超时   破怀2,3
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;

 class Boy
 class Girl
 class Test7
 public static void main(String[] args) throws InterruptedException 
 Lock boylock = new ReentrantLock();
 Lock girllock = new ReentrantLock();
 Thread boythread = new Thread(() ->
 try
 boylock.lockInterruptibly();
 System.out.println("my name is zs");
 if (girllock.tryLock(2,TimeUnit.SECONDS))
 System.out.println("获取girl成功");
 
 else
 System.out.println("获取girl失败,继续等待其他girl");
 
 
 catch (Exception e)
 
 finally 
 boylock.unlock();
 
 );

 Thread girlthread = new Thread(() ->
 try
 girllock.lockInterruptibly();
 System.out.println("my name is beauty");
 if(boylock.tryLock())
 System.out.println("获取boy失败");
 
 else 
 System.out.println("获取boy失败,继续尝试其他boy...");
 
 TimeUnit.SECONDS.sleep(1);
 
 catch (Exception e)
 
 finally 
 girllock.unlock();
 
 );
 boythread.start();
 girlthread.start();

 
 

技术图片

 


 

synchronized和ReentrantLock(Lock的子类)的关系与区别?

1、关系:都属于独占锁(互斥,任意一个时刻只能有一个线程获取到资源)的实现

synchronized和reentrantlock都支持可重入锁

2、synchronized是关键字,jvm层面实现

reentrantlock是Java语言层面的“管程(管理代码线程安全)”

3、reentrantlock具备一些synchronized不具备的功能:

响应中断,非阻塞式获取锁,支持超时获取锁,支持公平锁,支持多个等待队列(Condition对象)

 

公平锁:等待时间最长的线程最先获取锁

 


线程停止的三种方式:

1、Thread类提供,stop()强行停止,不安全 ,不推荐

2、设置标记位,flag改成false,改为true就停了,while flag

3、interrupt 不一定停止,线程状态设置为中断状态:

示例如下:
import java.util.concurrent.TimeUnit;

class Boy
class Girl
class Test7
    public static void main(String[] args) throws InterruptedException 
        Boy boy  = new Boy();
        Girl girl = new Girl();

        Thread boythread = new Thread(() ->
            synchronized (boy)
                System.out.println("my name is zs");
                synchronized (girl)
                    System.out.println("i love you");
                
            
        );

        Thread girlthread = new Thread(() ->
            synchronized (girl)
                System.out.println("my name is beauty");
                synchronized (boy)
                    System.out.println("i don‘t love you");
                
            
        );
        boythread.start();
        girlthread.start();

        //主线程睡上一秒
        TimeUnit.SECONDS.sleep(1);
        //boythread不能中断,把状态改为true,并不对线程产生影响,*不响应中断*
        boythread.interrupt();
    

技术图片

 

 

 

import java.util.concurrent.TimeUnit;

class Boy
class Girl
class Test7
    public static void main(String[] args) throws InterruptedException 
        Boy boy  = new Boy();
        Girl girl = new Girl();

        Thread boythread = new Thread(() ->
            synchronized (boy)
                System.out.println("my name is zs");
                synchronized (girl)
                    System.out.println("i love you");
                
            
        );

        Thread girlthread = new Thread(() ->
            synchronized (girl)
                System.out.println("my name is beauty");
                synchronized (boy)
                    System.out.println("i don‘t love you");
                
            
        );
        boythread.start();
        girlthread.start();

        //主线程睡上一秒
        TimeUnit.SECONDS.sleep(1);
        //boythread不能中断,把状态改为true,并不对线程产生影响,*不响应中断*
        boythread.interrupt();
    

 

 

 技术图片

 

 


缓存:Map
HashMap + ReentrantReadWriteLock = 高效线程安全Map

public class ThreadSafeCache 
    private Map<String,String> map = new HashMap<>();
    private ReentrantReadWriteLock reentrantReadWriteLock
            = new ReentrantReadWriteLock();
    
    public String getValue(String key) 
        reentrantReadWriteLock.readLock();
        return map.get(key);
    
    
    public void setValue(String key,String value) 
        reentrantReadWriteLock.writeLock();
        map.put(key,value);
    

 


await/signal : Lock

Condition:等待队列模型
任意一个Lock对象有一个同步队列和多个等待队列(lock.condition())

Lock下另一个子类:ReentrantReadWriteLock
独占锁:任意一个时刻只有一个线程获取到锁
共享锁:任意一个能有多个线程获取到锁

读写锁:读锁:共享锁,读线程和读线程异步,同时获取到锁

写锁:独占锁: 写线程与写线程互斥,同步,只有一个写线程能够获取到锁

读与写也是互斥的

eg:生活中的共享单车


读锁 !=无锁
当写锁产生的时候,可以限制所有读线程全部阻塞

读写锁应用:线程安全实现缓存
缓存:Map

如何写一个线程安全的map?
首先底层存储,用到hashmap reentrantreadwritelock 高效线程安全map 悲观读?一个写线程工作,读线程全部阻塞
能不能用reentrantlock?可以,但是get 和set仍是互斥的,只有一个线程能获取到

StampedLock jdk1.8 乐观锁

import java.util.concurrent.TimeUnit;

class ThreadLocalTest 
    private static String commStr;
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException 
        commStr = "main";
        threadLocal.set("main");
        Thread thread = new Thread(() -> 
            commStr = "thread";
            threadLocal.set("thread");
        );
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println(commStr);
        System.out.println(threadLocal.get());
    

 

技术图片

 

以上是关于Java多线程的主要内容,如果未能解决你的问题,请参考以下文章

什么是JAVA的多线程?

Java多线程 1.认识Java线程

Java多线程 5.栅栏

java 如何实现多线程

java中啥叫做线程?啥叫多线程?多线程的特点是啥

Java多线程-Java多线程概述