一、Java多线程和高并发

时间:2021-11-16 15:24:25
什么是线程,什么是进程:
线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指 运行 中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为 多线程
进程是正在运行的程序的实例,或者:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。
 
一、Java线程的周期:
五个基本状态 

一、Java多线程和高并发

 线程有五个基本状态描述
New: 新建线程(创建一个线程,但是没有任何可运行的实体)
Runnable 线程就绪(将程序变量实体放入线程中,)
Running  运行(运行放入的程序)
Blocked  阻塞(程序暂停,等待自动唤醒或者被动唤醒)
Dead  结束(程序运行结束或者异常退出)
 
形象点我们可以这么理解:
一个线程就是一个抽水机抽水,
线程创建可以看成这个抽水机
就绪就是已经架设好的抽水机
运行:抽水机抽水行为
阻塞:就是正在抽水时因为各种原因抽不上水了
结束:抽水结束并将抽水机搬走
================================================= 
 
二、Java线程的实现
Java线程方式主要有三种实现方式 1.集成Thread 类 重写run方法 
class  MyFirstThread  extends  Thread{
         private   int  i=0;
        @Override
         public   void  run() {
                //  TODO  Auto-generated method stub
                 for (i=0;i<100;i++){
                        System. out .println(Thread. currentThread ().getName()+" "+i);
                }                
        }
}
 
2.实现Runnable方法 
class  MyFirstRunnable  implements  Runnable{
         private   int  i=0;
        @Override
         public   void  run() {
                //  TODO  Auto-generated method stub
                 for (i=0;i<10;i++){
                        System. out .println(Thread. currentThread ().getName()+" "+ i);
                }
        }
}
 
Thread 和 Runnable两种实现方式
继承 Thread接口  或者实现Runnable两种方式
具体实现的时候
Thread :
Thread firstThread= new MyFirstThread();
firstThread.start();
Runnable: 
Runnable second= new MyFirstRunnable();
firstT.start();
注意: 类的单继承和多实现在很大程度上决定了线程的实现方法
3.使用Callable 和Future 接口创建线程。(具有返回值的线程)
Callbale: 创建Callable接口的实现类并实现clall() 方法;
并使用FutureTask  类来包装callable实现类的对象,并以此FutureTask对象作为Thread对象的Target来创建线程
 
class  MyCallable  implements  Callable<Integer> {
         private   int  i=0;        
        @Override
         public  Integer call()  throws  Exception {
                 int  sum=0;
                 for (i=0;i<10;i++){
                        System. out .println(Thread. currentThread ().getName()+" "+i);
                        sum++;
                }
                 return  sum;
        }
}
 
线程具体实现的时候:
public   static   void  main(String[] args) {                
                Callable<Integer> myCallabel=  new  MyCallable();                
                FutureTask<Integer> ft= new   FutureTask<Integer>(myCallabel);                
                System. out .println(Thread. currentThread ().getName()+" "+i);
                Thread th=  new  Thread(ft);
                th.start();     
  • try { 
  •  int sum = ft.get(); //取得新创建的线程中的call()方法返回的                    System.out.println("sum = " + sum);             
  • } catch (InterruptedException e) {            
  •   e.printStackTrace();          
  •   } catch (ExecutionException e) {
  •        e.printStackTrace(); 
  • }

额外补充:
Java虚拟机线程的实现的额外说明
在jvm虚拟机中线程的实现是依托于系统来实现的
实现线程的三种方式
1.由内核线程来实现
2.使用用户线程实现
3.使用用户线程和轻量级进程混合实现
Java虚拟机线程的实现主要依托有当前系统支持怎样实现线程模型
Java线程的调度
是指系统为线程分配处理器使用权限的过程:
方式:协同试线程调度、抢占式调度

===============================================================
二、 锁提供了两种主要特性: 互斥(mutual exclusion)  和 可见性(visibility)
1.线程同步synchronized和volatile
为什么会有线程同步呢,这是因为Java线程的两个特性,可见性和有序性;
比如说,多个线程同事修改和读取同一个数字变量num的时候,可能会发生脏数据的情况,比如说两个线程a,b同事对num=1进行+1运算; 正常情况下是在其中一个线程a对num进行+1运算,然后另一个b在运算后的num再次进行+1运算,得到的是3;但是线程a,b进行运算是可以同事进行运算的,这是线程a、b拿到运算前的数据都是1,同时开始进行运算,ab得到的结果都是2,然后将结果返回给最后的结果,就得到2,与预期得到的结果是相悖的,这就是脏数据,为了解决这些问题,于是有了线程同步(线程锁,在运算的时候不让其他线程进来,造成脏数据的现象)。
给代码加上synchornized (同步锁),可以修饰:类,代码块,静态方法,方法,可以保证代码只有一个线程访问
Volatile ,
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。
使用内存模型来解释的话就是:
将内存分为两部分,一个是 主内存 (所有的变量都是存储在这里),另一个是工 作内存 (运行时临时存储);所有的 变量都存在主内存中 ,要运算的时候会将要计算使用的变量复制到工作空间中,然后进行计算,使用synchorinzed,的时候,会将主内存的变量锁定,其他线程不能去读和修改,直到结束释放
而volatile的可见性,是指将变量的可修改行锁住,不允许其他线程修改,但是可以去读,同时,还将工作内存的变量实时传递给主内存对应的变量,这样其他线程就可以获取到这个变量的实时变化数据,所有使用volatile的时候要注意,程序结束前程序变量是可见的
内存示意图
一、Java多线程和高并发

2. 常见的锁
2.1偏向锁
在JVM中使用-XX:+UseBiasedLocking
偏向锁是指同只有单独一个线程访问的时候线程安全的代码自动转变成线程不安全(效率高,而且只有一个线程访问不存在线程安全问题eg ,集合vector ,线程安全,在偏向锁的作用下变成Arrlist,这样效率可以提高,),在另外一个线程访问的情况下在有转变成线程安全,这样就存在线程安全的 锁定和解除,在频分转化的时候会造成效率降低,但是在单线程程序中这样的程序效率是提高的
2.2轻量级锁
轻量级锁(Lightweight Locking)本意是为了减少多线程进入互斥的几率,并不是要替代互斥。
它利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行补救
2.3 重量级锁
当轻量级锁失败,虚拟机就会使用重量级锁。在使用重量级锁的时,对象的重量级锁在操作过程中,线程可能会被 操作系统 层面 挂起,如果是这样,线程间的切换和调用成本就会大大提高。

2.4自旋锁
  自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。
自旋锁1.6可配,1.7以后不可配

常用锁机制
2.5 可重入锁(ReentrantLock)
可重入锁  :可重入锁的概念是自己可以再次获取自己的内部锁。举个例子,比如一条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的(如果不可重入的锁的话,此刻会造成死锁)。说的更高深一点可重入锁是一种递归无阻塞的同步机制。
ReentrantLock  :  类ReentrantLock实现了Lock,它拥有与Sychronized相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断等候的一些特性。此外,它还提供了在与激烈争用情况下更佳的性能(说白了就是ReentrantLock和Sychronized差不多,线程间都是完全互斥的,一个时刻只能有一个线程获取到锁,执行被锁住的代码,但ReentrantLock相对于Sychronized提供了更加丰富的功能并且在线程调度上做了优化,JVM调度使用ReentrantLock的线程会更快)
class Lock{
private  ReentrantLock lock =  new  ReentrantLock();  
public void sayHello() {
              /**
               * 当一条线程不释放锁的时候,第二个线程走到这里的时候就阻塞掉了
               */
              try {
              lock .lock();
                     System. out .println(Thread. currentThread ().getName()+ " locking ..." );
                     System. out .println( "Hello world!" );
                     System. out .println(Thread. currentThread ().getName()+ " unlocking ..." );
              } finally {
                  lock .unlock();
           }
    }
}


2.6读写锁( ReentrantReadWriteLock
读写锁  :读写锁拆成读锁和写锁来理解。读锁可以共享,多个线程可以同时拥有读锁,但是写锁却只能只有一个线程拥有,而且获取写锁的时候其他线程都已经释放了读锁,而且该线程获取写锁之后,其他线程不能再获取读锁。简单的说就是写锁是排他锁,读锁是共享锁。
ReentrantReadWriteLock:  类ReentrantReadWriteLock实现了ReadWirteLock接口。它和ReentrantLock是不同的两套实现,在类继承结构上并无关联。和ReentrantLock定义的互斥锁不同的是,ReentrantReadWriteLock定义了两把锁即读锁和写锁。读锁可以共享,即同一个资源可以让多个线程获取读锁。这个和ReentrantLock(或者sychronized)相比大大提高了读的性能。在需要对资源进行写入的时候在会加写锁达到互斥的目的。
class  ReadWriteLockTest {
  private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
       /**
        * 读锁 (可以多个线程同时执行程序)
        */
       private ReadLock readLock = readWriteLock .readLock();
       /**
        * 写锁(只能有一个写线程执行程序,)
        */
       private WriteLock writeLock = readWriteLock .writeLock();
/**
        * 共享资源
        */
       private String shareData = " 寂寞等待中..." ;
       public void write(String str) throws InterruptedException {
        writeLock .lock();
              System. err .println( "ThreadName:" +Thread. currentThread ().getName()+ "locking..." );
              try {
                     shareData = str;
                     System. err .println( "ThreadName:" + Thread. currentThread ().getName()+ " 修改为" +str);
                     Thread. sleep (1);
              } catch (InterruptedException e) {
                     e.printStackTrace();
              } finally {
                     System. err .println( "ThreadName:" + Thread. currentThread ().getName()+ "  unlock..." );
                     writeLock .unlock();
              }
       }
       public String read() {
              readLock .lock();
              System. out .println( "ThreadName:" + Thread. currentThread ().getName()+ "lock..." );
              try {
                     System. out .println( "ThreadName:" +Thread. currentThread ().getName()+ " 获取为:" + shareData );
                     Thread. sleep (1);
              } catch (InterruptedException e) {
                     e.printStackTrace();
              } finally {
                     System. out .println( "ThreadName:" + Thread. currentThread ().getName()+ "unlock..." );
                     readLock .unlock();
              }
              return shareData ;
       }
}

2.7 java自从5.0以来,提供了线程池,线程的目标执行对象可以共享线程池中有限数目的线程对象。
线程池对象 ExecutorService 的三种实现方式
单一线程数ExecutorService executorService1 = Executors.newSingleThreadExecutor();  
固定线程数ExecutorService executorService2 = Executors.newFixedThreadPool(10);  
缓存线程数ExecutorService executorService3 = Executors.newScheduledThreadPool(10);

1.单一线程数:就是只有一个线程开启,如果多长执行的话就是,这个线程一直轮询执行下去
2.固定线程数:在程序初始化的时候,虚拟机初始化10个线程
3. 缓存线程数:在程序初始化的时候创建 10线程线程,并随着系统的需求增加线程数量,线程数量的上限是系统资源的上线

eg:
ExecutorService exec = Executors.newFixedThreadPool(2);  
        //创建100个线程目标对象  
        for(int index=0;index<100;index++){  
            Runnable run = new Runner(index);  
            //执行线程目标对象  
            exec.execute(run);  
        }  
        //shutdown  退出线程池
        exec.shutdown(); 





未完待续(如有不足之处请指出)