java并发编程之美之并发编程线程基础(代码片段)

lpfalpd lpfalpd     2022-12-01     112

关键词:

什么是线程

  进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,线程则是进程的一个执行路径,一个进程至少有一个线程进程的多个线程共享进程的资源

  java启动main函数其实就是启动了一个JVM的进程,而main函数所在的线程就是这个进程的一个线程,也称主线程。

进程和线程关系

  技术图片

  一个进程有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器栈区域

  程序计数器是一块内存区域用来记录线程当前要执行的指令地址。如果执行的是native方法,那么pc计数器记录的是undefined地址,只有执行java代码时pc计数器记录的才是下一条指令的地址;

  进程的栈资源存储该线程的局部变量,局部变量是该线程私有的,其它线程访问不了,除此之外栈还用来存放线程的调用栈帧

  是进程中最大的一块内存,堆是被进程中的所有线程共享的,是进程创建时分配的,堆里面存放使用new操作创建的对象实例。

  方法区则用来存放JVM加载的类,常量及静态变量等信息,也是线程共享的

线程创建和运行

  创建方式:继承Thread类    实现Runnable接口,使用FutureTask方式

    1.继承Thread类

public class ThreadTest 
     public static class MyThread extends Thread
        @Override
       public void run()
            System.out.println(" I am a child thread!") ;             
                
         
     public static void main(String[] args)
          MyThread  t = new MyThread();
          t.start();
            
     

 

    2.实现Runnable接口

public class RunnableTest 
    
    public static class MyRunnable implements Runnable
    
        @Override
        public void run() 
            System.out.println("I am a child thread!");
        
    
    
    public static void main(String[] args) 
        new Thread(new MyRunnable()).start();
    
 

        3.使用FutureTask

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

public class CallableTest implements Callable<Boolean>

    @Override
    public Boolean call() throws Exception 
        
        return true;
    
    
    public static void main(String[] args) 
        FutureTask<Boolean> f = new FutureTask<>(new CallableTest());
        new Thread(f).start();
        try
            Boolean  r = f.get();
            System.out.println(r);
        catch(Exception e)
        
            e.printStackTrace();
        
    

 小结:

  使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者构造函数传递参数,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread,那么子类不能再继承其它类,而Runnale则没有这个限制。前两种方式都没办法拿到任务的返回值,但是Futuretask方式可以。

 

线程通知与等待

  java中的Object类是所有类的父类,鉴于继承机制,java把所有类都需要的方法放到了Object类里面,其中就包含通知与等待系列函数。

       wait()函数

    当一个函数调用共享变量的wait()方法时,该线程会被阻塞挂起,直到发生下面几件事情之一才返回:1)其它线程调用了该共享对象的notify()或者notifyAll()方法;2)其它线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回;

    如果调用wait()方法的线程事先没有获取该对象的监视器锁,会抛出IllegalMonitorStateException异常;

    获取共享变量的监视器锁

      synchronized(共享变量)doSomething

      synchronized  void add(int a,int b)doSomething

    虚假唤醒

      一个线程可以从挂起状态变为可以运行状态(也就是被唤醒),即时该线程没有被其它线程调用notify(),notifyAll()方法进行通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒。

生产者和消费者

/**
 * 容器
 * @author pc
 *
 */
public class Box 
    
    public final int MAX_SIZE = 10;
    
    public LinkedList<Integer>   box = new  LinkedList<>();
    
    
    public void add() throws Exception
    
        while(true)
        
            synchronized(box)
            
                while(box.size() == MAX_SIZE)
                
                    box.wait();
                
                box.push(1);
                System.out.println("add ele and notifyall:"+box.size());
                box.notifyAll();
            
        
    
    
    public void remove() throws Exception
    
        while(true)
        
            synchronized(box)
            
                while(box.size() == 0)
                
                    box.wait();
                
                
                box.pop();
                System.out.println("remove ele and notifyall:"+box.size());
                box.notifyAll();
            
        
        

/**
 * 生产者和消费者
 * @author pc
 *
 */
public class Program 
    
    public Box box;

    public Program(Box box) 
        super();
        this.box = box;
    
    
    
    public  class Consumer implements Runnable
    
        @Override
        public void run() 
            try 
                box.remove();
             catch (Exception e) 
                e.printStackTrace();
            
        
    
    
    public class Producer implements Runnable 
    

        @Override
        public void run() 
            try 
                box.add();
             catch (Exception e) 
                e.printStackTrace();
            
        
    
    
    public static void main(String[] args) 
        Program p = new Program(new Box());
        Program.Consumer c = p.new Consumer();
        Program.Producer pd = p.new Producer();
        
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(pd);
        t1.start();
        t2.start();
    

 当前线程调用共享变量的wait()方法后只会释放当前共享变量上的锁,如果当前线程还持有其它共享变量的锁,则这些锁时不会被释放的。

public class Test 
    
    private static  volatile Object resourceA = new Object();
    private static  volatile Object resourceB = new Object();
    
    public static void main(String[] args) throws Exception 
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                System.out.println("t1 try  get resourceA lock...");
                synchronized(resourceA)
                
                    System.out.println("t1 get resourceA lock...");
                    System.out.println("t1 try get resourceB lock...");
                    synchronized(resourceB)
                    
                        System.out.println("t1  get resourceB lock...");
                        System.out.println("t1 unlock resourceA...");
                        try 
                            resourceA.wait();
                         catch (InterruptedException e) 
                            e.printStackTrace();
                        
                    
                
            
        );
        
        Thread t2 = new Thread(new Runnable() 
            @Override
            public void run() 
                try 
                    Thread.sleep(1000L);//等待1s保证t1将resourceA释放出来
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println("t2 try  get resourceA lock...");
                synchronized(resourceA)
                
                    System.out.println("t2 get resourceA lock...");
                    System.out.println("t2 try get resourceB lock...");
                    synchronized(resourceB)
                    
                        System.out.println("t2  get resourceB lock...");
                        System.out.println("t2 unlock resourceA...");
                        try 
                            resourceA.wait();
                         catch (InterruptedException e) 
                            e.printStackTrace();
                        
                    
                
            
                
        );
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        
        System.out.println("main thread is over");
    

 当一个线程调用共享对象的wait()方法被阻塞挂起后,如果其它线程中断了该线程,则该线程会抛出InterruptedException异常并返回   

public class InterruptedExceptionTest 
    
    private static volatile Object resouce = new Object();
    
    
    public static void main(String[] args) throws InterruptedException 
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                synchronized(resouce)
                
                    try 
                        resouce.wait();
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
            
        );
        
        t1.start();
        System.out.println(" start interupt==");
        t1.interrupt();
        System.out.println(" end interupt==");
        System.out.println("main is over");
    

 wait(long timeout)函数

  该方法相比wait()函数多了一个超时参数,如果线程调用共享变量过程中,没有在指定的timeout时间内被其它线程调用该共享变量的notify()或者notifyAll()方法唤醒,那么该函数还是会因为超时返回

  timeout等于0就相当于wait()函数

  timeout小于0会报错IllegalArgumentException异常

public class WaitTimeOutTest 
    
    private static Object resource = new Object();
    
    public static void main(String[] args)throws Exception 
        Thread t1 = new Thread(new Runnable() 
            
            @Override
            public void run() 
                synchronized(resource)
                
                    System.out.println("start wait()"+System.currentTimeMillis()/1000);
                    try 
                        resource.wait(1000L);
                     catch (InterruptedException e) 
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    
                    System.out.println("end   wait()"+System.currentTimeMillis()/1000);
                
            
        );
        
        t1.start();
        t1.join();
    

 

wait(long timeout,int nanos)

  内部调用的时wait(long timeout),只有在nanos>0时才使参数timeout递增1;

notify()函数

  一个线程调用共享变量的notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程时随机的。

  被唤醒的线程不会立即返回,需要先获取共享变量的锁

  类似wait系列方法,只有当前线程获取到共享变量的监视器锁之后,才可以调用共享变量的notify()方法,否则会抛出IllegalMonitorStateException异常。

notifyAll()函数

  唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程

  如果调用notifyAll()之后有线程调用了wait()方法,则该线程不能被唤醒。

public class NotifyTest 
    
    private static Object resource = new Object();
    
    public static void main(String[] args) 
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                synchronized(resource)
                
                    System.out.println("t1  start wait()");
                    try 
                        resource.wait();
                        System.out.println("t1 end wait()");
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
            
        );
        
        Thread t2 = new Thread(new Runnable() 
            @Override
            public void run() 
                synchronized(resource)
                
                    System.out.println("t2  start wait()");
                    try 
                        resource.wait();
                        System.out.println("t2 end wait()");
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
            
        );
        
        Thread t3 = new Thread(new Runnable() 
            @Override
            public void run() 
                try 
                    Thread.sleep(1000L);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                //等待1s
                synchronized(resource)
                
                    System.out.println("t3 start notify()");
                    resource.notify();
            //resource.notifyAll(); ); t1.start(); t2.start(); t3.start(); try t1.join(); t2.join(); t3.join(); catch (InterruptedException e) e.printStackTrace();

 

 等待线程执行终止的join方法

ublic class JoinTest 
    
    public static void main(String[] args) throws InterruptedException 
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                try 
                    Thread.sleep(2000L);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println("t1 end");
            
        );
        
        Thread t2 = new Thread(new Runnable() 
            @Override
            public void run() 
                try 
                    Thread.sleep(1000L);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println("t2 end");
            
        );
        
        t1.start();t2.start();
        System.out.println("wait t1,t2 over");
        t1.join();t2.join();
    

 

 

 

让线程睡眠的sleep方法

  Thread类中的一个静态sleep方法,当一个执行中的线程调用Thread的sleep方法后,调用线程会暂时让出指定时间的使用权,也就是不参数与CPU调度,但是该线程拥有的监视器资源,比如锁还是持有不让出的。指定时间结束后该函数会正常返回。

   如果在睡眠期间调用了该线程的interrupt()方法中断了该线程,则线程会在调用sleep方法的地方抛出InterruptedException异常而返回。

 

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

public class SleepTest 
    
    private static Lock lock = new ReentrantLock();
    
    public static void main(String[] args) throws InterruptedException 
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                try
                
                    lock.lock();
                    System.out.println("t1 start sleep");
                    Thread.sleep(1000);
                    System.out.println("t1 end sleep");
                catch(Exception e)
                
                    e.printStackTrace();
                finally
                
                    lock.unlock();
                
            
        );
        
        Thread t2 = new Thread(new Runnable() 
            @Override
            public void run() 
                try
                
                    lock.lock();
                    System.out.println("t2 start sleep");
                    Thread.sleep(1000);
                    System.out.println("t2 end sleep");
                catch(Exception e)
                
                    e.printStackTrace();
                finally
                
                    lock.unlock();
                
            
        );
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    

输出结果一直是t1或t2结束其中之一结束之后才开始执行下一线程,证明线程睡眠期间不会释放锁;

让出CPU执行权的yield方法

  Thread类中的静态yield方法,当一个线程调用yield方法时,其实就是在暗示线程调度器当前线程请求让出自己的CPU使用,但是线程调度器可以无条件忽略这个暗示

  当一个线程调用yield方法时,当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级高的线程,当然也有可能调度到刚刚让出CPU的线程

线程中断

  java中线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理

  void interrupt():中断线程

  boolean isInterrupted():检测当前线程是否被中断

  boolean interrupted():检测当前线程是否被中断,中断返回true,并清除中断标志

线程死锁

  死锁是指两个或两个以上的线程执行过程中,因争夺资源而做成的互相等待的现象。无外力作用的情况下,这些线程会一直互相等待下去而无法继续运行下去

  技术图片

死锁出现的条件

  1)互斥条件:线程堆已经获取的资源进行排他性使用,即该资源同时只能有一条线程占用。如果此时还有其它线程请求获取该资源,请求者只能等待,直至占有的线程释放该资源;

  2)请求并持有条件:指一个线程已经持有了至少一个资源,但是又提出了新的资源请求,而新资源被其它线程占有,所以当前线程会被阻塞,但阻塞的同事并不释放自己已经获取的资源。

  3)不可剥夺条件:指线程获取到的资源在自己使用完之前不能被其它线程抢占,只有在自己使用完毕后才由自己释放资源

  4)环路等待条件:发生死锁时,必然存在一个线程-资源环形链,即线程集合T0,T1,T2,...Tn中T0等待T1的资源,T1等待T2的资源,...Tn正在等待T0占用的资源

public class DeadThreadTest 
    
    private volatile static Object resourceA = new Object();
    private volatile static Object resourceB = new Object();
    
    public static void main(String[] args) 
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                
                System.out.println("t1 try get resourceA lock...");
                synchronized(resourceA)
                
                    System.out.println("t1  get resourceA lock...");
                    try 
                        Thread.sleep(1000L);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    System.out.println("t1 try get resourceB lock...");
                    synchronized(resourceB)
                    
                        System.out.println("t1 get resourceB lock...");
                    
                
            
        );
        
        
        Thread t2 = new Thread(new Runnable() 
            @Override
            public void run() 
                System.out.println("t2 try get resourceB lock...");
                synchronized(resourceB)
                
                    System.out.println("t2  get resourceB lock...");
                    try 
                        Thread.sleep(1000L);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                    System.out.println("t2 try get resourceA lock...");
                    synchronized(resourceA)
                    
                        System.out.println("t2 get resourceA lock...");
                    
                
            
        );
        
        t1.start();
        t2.start();
    



输出:
  

t2 try get resourceB lock...
t1 try get resourceA lock...
t1 get resourceA lock...
t2 get resourceB lock...
t1 try get resourceB lock...
t2 try get resourceA lock...

 

 

如何避免死锁的发生

  1)破坏掉至少一个构造死锁的必要条件即可

  2)使用资源的有序性原则

守护线程与用户线程

  线程分类:守护线程【daemon】,用户线程【user】

  main函数就是用户线程

  当最后一个用户线程结束时,JVM会正常退出,只要有一个用户线程没有结束,正常情况下JVM就不会退出

   

public class DamonThreadTest 
    
    public static void main(String[] args) 
        Thread t1 = new Thread(new Runnable() 
            
            @Override
            public void run() 
                for(;;)
                
                    
                
            
        );
        
        t1.setDaemon(false); 
        t1.start();
        System.out.println("main is over");
    
技术图片

 

 

 ThreadLocal

  多线程访问同一共享变量时特别容易出现并发问题,特别时在多个线程需要对一个共享变量进行写入时,为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步

  同步措施一般是加锁,这就需要使用者对锁有一定的了解

  ThreadLocal提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。

 

public class ThreadLocalTest 
    
    private static ThreadLocal<String> local = new ThreadLocal<>();
    
    public static void print(String name)
    
        System.out.println(name+"--"+local.get());
    
    
    
    public static void main(String[] args) 
        Thread t1 = new Thread(new  Runnable() 
            
            @Override
            public void run() 
                local.set("1234");
                print(Thread.currentThread().getName());
            
        ,"t1");
        Thread t2 = new Thread(new  Runnable() 
            
            @Override
            public void run() 
                local.set("5678");
                print(Thread.currentThread().getName());
            
        ,"t2");
        
        t1.start();
        t2.start();
        


output:
  

t1--1234
t2--5678

 

 线程上下文切换

  多线程编程中,线程个数一般都大于CPU个数,而每个CPU同一时刻只能被一个线程使用,为了让用户感觉多个线程在同时运行,CPU采用采用了时间片轮转的策略,也就是给每个线程分配一个时间片,线程在时间片内占用CPU执行任务,

当前线程使用完时间片后,就会处于就绪状态并让出CPU让其它线程占用,这就是上下文切换。

切换时机:1)当前线程使用完CPU分配的时间片处于就绪状态  2)当前线程被其它线程中断

java修炼之道--并发编程(代码片段)

...s://github.com/frank-lam/2019_campus_apply前言在本文将总结多线程并发编程中的常见面试题,主要核心线程生命周期、线程通信、并发包部分。主要分成“并发编程”和“面试指南”两部分,在面试指南中将讨论并发相关面经。参考资料... 查看详情

并发编程系列之线程基础知识回顾(代码片段)

并发线程的知识是很重要而且比较杂的知识点,所以需要花不少时间用于整理。本博客整理线程的一些比较重要而且比较基础的知识点,帮忙读者入门,注意只是学习并发编程的一些基础点,要系统学习的是需要... 查看详情

java并发编程系列之二线程基础

上篇文章对并发的理论基础进行了回顾,主要是为什么使用多线程、多线程会引发什么问题及引发的原因,和怎么使用Java中的多线程去解决这些问题。正所谓,知其然知其所以然,这是学习一个知识遵循的原则。推荐读者先行... 查看详情

13.并发编程之协程(代码片段)

目录一、协程基础二、gevent模块三、asyncio模块一、协程基础cpython下多个线程不能利用多核:规避了所有的io操作的单线程。协程操作系统不可见协程本质就是一条线程,多个任务在一条线程上来回切换,来规避io操作,降低了线... 查看详情

juc并发编程之completablefuture基础用法(代码片段)

目录实现多线程的四种方式方式一:继承Thread类方式二:实现Runnable接口方式三:实现Callable接口方式四:线程池创建异步对象回调方法handle方法 线程串行化 任务组合组合任务单任务完成及执行实现多线程的四... 查看详情

juc并发编程之completablefuture基础用法(代码片段)

目录实现多线程的四种方式方式一:继承Thread类方式二:实现Runnable接口方式三:实现Callable接口方式四:线程池创建异步对象回调方法handle方法 线程串行化 任务组合组合任务单任务完成及执行实现多线程的四... 查看详情

java并发编程之concurrenthashmap(代码片段)

引言ConcurrentHashMap是线程安全并且高效的HashMap,在并发编程中经常可见它的使用,在开始分析它的高并发实现机制前,先讲讲废话,看看它是如何被引入jdk的。为什么引入ConcurrentHashMap?HashMap线程不安全,... 查看详情

并发编程系列之线程基础知识回顾(代码片段)

并发线程的知识是很重要而且比较杂的知识点,所以需要花不少时间用于整理。本博客整理线程的一些比较重要而且比较基础的知识点,帮忙读者入门,注意只是学习并发编程的一些基础点,要系统学习的是需要多看看书籍还是... 查看详情

并发编程并发编程中你需要知道的基础概念(代码片段)

本博客系列是学习并发编程过程中的记录总结。由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅。并发编程系列博客传送门多线程是Java编程中一块非常重要的内容,其中涉及到很多概念。这... 查看详情

java并发编程实战基础概要(代码片段)

文章目录Java并发编程实战基础概要开篇多线程问题有啥难点呢?为啥要学习并发编程?并发问题的根源是什么?CPU切换线程执导致的原子性问题是如何发生的?缓存导致的可见性问题是如何发生的?指令优化&... 查看详情

java并发编程--并发编程线程基础(线程安全问题可见性问题synchronized/volatile关键字casunsafe指令重排序伪共享java锁的概述)(代码片段)

文章目录1.并发编程线程基础(下篇)1.1什么是多线程并发编程1.2为什么要进行多线程并发编程1.3Java中的线程安全问题1.4Java中共享变量的内存可见性问题1.5Java中的synchronized关键字1.6Java中的volatile关键字1.6.1volatile关键字... 查看详情

java并发编程:并发编程基础各种锁详细介绍(代码片段)

转载自并发编程网–ifeve.com目录一、前言二、什么是线程安全问题三、什么是共享变量可见性问题四、原子性4.1介绍4.2原子变量类五CAS介绍六、什么是可重入锁七、Synchronized关键字7.1Synchronized介绍7.2Synchronized同步实例八、Reentrant... 查看详情

并发编程基础(代码片段)

并发编程基础一、创建新线程1、继承Thread类优点编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。缺点线程类已经继承了Thread类,所以不能再继承其他父类。publicclassCreateexte... 查看详情

并发编程基础(代码片段)

synchronized原理分析synchronized关键字解决的是多个线程之间访问资源的同步性问题,synchronized关键字可以保证被它修饰的⽅法或者代码块在任意时刻只能有⼀个线程执⾏。jdk1.6之前性能⽐较低,Java的线程是映射到操作系统的原⽣... 查看详情

python并发编程之线程的玩法(代码片段)

一、线程基础以及守护进程线程是CPU调度的最小单位全局解释器锁全局解释器锁GIL(globalinterpreterlock)全局解释器锁的出现主要是为了完成垃圾回收机制的回收机制,对不同线程的引用计数的变化记录的更加精准。全... 查看详情

java并发编程基础(代码片段)

并发与并行并发与并行的区别?并发:同时完成多个任务,无需等待当前任务完成即可执行其它任务。例如解决IO密集型任务。并行:同时在多个CPU中执行多个任务,将任务分为多个部分,在多个CPU中执行多个子任务。用于解决C... 查看详情

go语言基础之并发(代码片段)

Go语言中的并发编程——并发是编程里面一个非常重要的概念,Go语言在语言层面天生支持并发,这也是Go语言流行的一个很重要的原因。并发与并行并发:同一时间段内执行多个任务(你在用微信和两个女朋友聊天)。并... 查看详情

java并发编程实战基础概要(代码片段)

文章目录Java并发编程实战基础概要开篇多线程问题有啥难点呢?为啥要学习并发编程?并发问题的根源是什么?CPU切换线程执导致的原子性问题是如何发生的?缓存导致的可见性问题是如何发生的?指令优化&... 查看详情