java--多线程基础

进程:正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源
 
多进程:在同一个时间段内执行多个任务
 
多线程的意义:多线程的存在,不是提高程序的执行速度,其实是为了提高应用程序的使用率(一个进程中更多的线程更容易抢到CPU资源,CPU执行权),就会使进程有更高的几率抢到CPU的执行权
 
注意:不能保证哪一个线程能够在哪一个时刻抢到,所以线程的执行有随机性
并行和并发的区别:
并行:逻辑上同时发生,指在某一个时间内同时发生
并发:物理上同时发生,指在某一个时间点运行多个程序
 
java程序运行原理:java命令会启动java虚拟机,启动JVM,等于启动一个应用程序,也就是启动了一个进行。该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法。所以main方法允许在主线程中。在此之前的程序都是运行在单线程中的。
问题:JVM虚拟机的启动是单线程还是多线程?
   JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的
 
如何实现多线程的程序?
由于线程是依赖于进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该调用系统功能创建一个进程。
java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
但是java可以去调用C/C++写好的程序来实现多线程程序。即利用C/C++去调用系统功能创建进程,java对于这些C/C++的进程程序进行了封装,包装成一些类,这样就可以直接调用这些类去实现多线程程序。
 
java提供了Thread类中的run()用来包含哪些被线程执行的代码
 

 
主要内容:线程概述、多线程实现方案、线程调度和线程控制、线程生命周期、线程同步、死锁、线程间的通信、定时器的使用
 
多线程实现方案:基本是两种方式,还有一种作为扩展方式(实现Callable接口,依赖于线程池)
线程调度和控制:调度:设置线程的优先级    控制:线程的运行状态控制(具体涵盖:start/sleep/yield/join/...)
线程生命周期:创建、就绪、运行、阻塞、死亡(状态)
线程同步:防止多线程带来的安全问题
死锁:因多个不同种类的线程对同一个资源的抢占而产生的循环等待现象
线程间通信:java中的等待唤醒机制
 
1  实现多线程的方式:
方式一:继承Thread类
A:自定义类MyThread继承Thread类
B:在MyThread类中重写run( )方法
C:创建MyThread对象
D:启动线程对象
问题:
1、为什么要重写run( )方法?
     run()方法里封装的是被线程执行的代码
2、启动线程对象的方法?
     start()
3、run()和start()方法的区别?
     run()直接调用仅仅是普通方法
     start():先启动线程,再由JVM调用run( )方法
 
方式二:
A:自定义类MyRunnable实现Runnable接口
B:在MyRunnable里面重写run()方法
C:创建MyRunnable对象
D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
 
问题:
1、方式1 方式2为什么有两种实现机制?
     方式二解决了单继承的局限性
     适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离,较好体现面向对象的设计思想
 
线程的状态转换:
 
 

线程的生命周期图:

2  线程的安全问题:
判断一个 程序是否有线程安全的标准?
1)是否有多线程环境
2)是否有共享数据
3)是否对共享数据进行操作
 
java提供了同步机制:
     格式:synchronized(对象){
                   需要同步的代码......
               }
     注意:同步问题可以解决的根本原因就在那个对象上,该对象如同锁的功能。
               多个线程要求必须是同一把锁。
 
同步解决线程安全问题:
A:同步代码块:
          synchronized(对象){
                   需要同步的代码......
          }
 
B:同步方法
把同步加在方法上
这里的锁对象是this
 
C: 静态同步方法
 把同步加在方法上
这里的锁对象是当前类的字节码文件对象(Class对象)

注意:当时在集合中提到,Vector是线程安全的类,但是即使在多线程条件下也不使用该类,这是因为什么呢?
主要是因为Collections这个集合工具类的存在:它有很多静态方法可以将一个其他不线程安全的集合例如ArrayList/LinkedList集合转成一个线程安全的集合:
例如:
     ArrayList<String> list1 = new ArrayList<String>( );    //线程不安全的ArrayList集合
     ArrayList<Stirng> list2 = Collections.synchronizedList(new ArrayList<String>( ));   //线程安全的ArrayList集合
 
Lock锁:
虽然可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock接口:java.util.concurrent.locks.Lock
提供了两个方法:void  lock( )和void  unlock( )
其实现子类:
     ReentrantLock
 
格式:
ReentrantLock lock = new ReentrantLock( );//创建ReentrantLock对象
lock.lock( );  //加锁
     需要同步的代码;
lock.unlock( );//释放锁
 
通常考虑到同步的代码块出错的可能性,所以使用以下方式:
try{
     lock.lock( );
     需要同步的代码块...............
}finally{
     lock.unlock( );
}

同步锁的弊端:
1)效率低:安全
2)如果出现同步嵌套,就容易产生死锁问题:相互等待
死锁问题及其代码:
是指两个或者两个以上的线程在执行过程中,因争夺资源产生的一种相互等待的现象
同步嵌套代码的:最少两个锁
同步嵌套代码的举例:
Class myThread extends Thread{
     private boolean flag;
     private static final Object objA = new Object( );
     private static final Object objB = new Object( );
 
     public myThread( boolean flag ){
          this.flag = flag;
     }
     
     public void run( ){
          if(flag){
               synchronized(objA){
                   System.out.println("if ObjA"); 
                    synchronized(objB){
                         System.out.println("if ObjB");
                    }
               }
          }else{         
                synchronized(objB){
                   System.out.println("else ObjB"); 
                    synchronized(objA){
                         System.out.println("else ObjA");
                    }
               }
          }
 
     }
}
在线程测试类中,创建两个线程,给与不同的flag值
可能就会出现死锁现象

线程之间的通信问题:不同种类的线程间针对同一个资源的操作
生产者和消费者:
不同种类的线程在操作时需要加锁
不同种类的线程加同一把锁
正常消费者和生产者的思路:
A:生产者:先看是否有数据,有就等待,没有就生产,生产完之后通知消费者来消费
B:消费者:先看有没有数据,有就消费,没有就等待,通知生产者生产数据
 
为了处理这样的问题,java就提供了一种机制:等待唤醒机制 保证一人一次
Object类中提供的方法:
1)wait( ):等待,该方法的特点是:一旦等待就释放锁,并且一旦被唤醒,就是在哪里开始等待的就从哪里开始继续
2)notify( ):唤醒单个线程。 被唤醒并不代表你可以立刻执行,还得去抢CPU执行权
3)notifyAll( ):唤醒相关的所有线程
问题:
 为什么这些方法不定义在Thread类中呢??
因为这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象,所以这些方法必须定义在Object中。

 
线程组:ThreadGroup,java.lang.ThreadGroup
线程默认情况下都属于main线程组
如何修改线程所在的组???
1)创建一个线程组
2)创建线程的时候,把其他线程的组指定为新建的线程租
 
ThreadGroup tg = new ThreadGroup("producerThreadGroup");//创建一个线程组
Thread t1 = new Thread(tg,runnable,"thread1");                        //创建一个线程对象1,其所属的线程组是新创建的
Thread t2 = new Thread(tg,runnable,"thread2");                        //创建一个线程对象2,其所属的线程组是新创建的
//现在改线程组就包含了两个线程
 
但是线程组出现的意义在哪里呢???
可以实现统一管理整个线程组中所有的线程,
例如通过设置该组的线程优先级:tg.setMaxPriority( );可以使组内的所有线程都是最高优先级
 

线程池:
程序启动一个新线程成本是比较高的,因为它涉及到与操作系统进行交互,而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存周期很短的线程时,更应该考虑使用线程池
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲 状态。等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,java内置支持线程池
 
位于:java.util.concurrent.Executors
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:
  • public  static  ExecutorService  newCachedThreadPool( )    //并没有指定创建几个线程
  • public  static  ExecutorService  newFixedThreadPool( int  nThreads ) //可以指定在线程池放几个线程
  • public  static  ExecutorService  newSingleThreadExecutor( )             //该方法就是nThreads=1的情况下
这些方法的返回值都是ExecutorService接口(实现的父接口:Executor)对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下的方法:
  • Future<?>  submit( Runnable  task )
  • <T> Future<T>  submit( Callable<T>  task)
例如:
1)创建线程池对象:控制要创建几个线程对象
2)创建Runnable实例(创建一个实现Runnable接口或者Callable接口的类)
3)提交Runnable实例
4)关闭线程池(如果在使用线程后想关掉线程池,使用shutdown( )方法)
 
例如:
1)创建一个实现Runnable接口的类
public class MyRunnable implements Runnable {  
     @Override
     public void run() {
           for( int i=0; i<50; i++){
               System. out.println(Thread. currentThread().getName()+":"+i);
          }
     }
}
2)创建测试类
public class ThreadPoolDemo {
     public static void main(String[] args) {
           //newFixedThreadPool()返回的是ThreadPoolExecutor:ExecutorService的子类
          ExecutorService pool = Executors.newFixedThreadPool(3);//在线程池中创建三个线程
           pool.submit( new MyRunnable()); //启动线程
           pool.submit( new MyRunnable()); //启动线程
 
           pool.shutdown(); //启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
     }
}
 
在之前提到创建线程的方式:
1)继承Thread类
2)实现Runnable接口
而在线程池中有一种实现Callable接口创建线程的方式,但是这种方式只能依赖线程池,该接口有个特点,线程运行后有返回值。
而其他两种方式中的run( )方法的返回值是void。这也是该种方法的特点所在。
 
3)实现Callable接口
使用的好处:可以有返回值,可以抛出异常(而在run方法中的异常只能try----catch处理)
弊端:代码复杂,一般不用
 

匿名内部类实现多线程:

第一种方式:
new Thread(){
               @Override
               public void run() {
                    for( int i=1; i<50; i++){
                        System. out.println(Thread. currentThread().getName()+"----"+i);
                   }
              }
          }.start();

第二种方式
new Thread(new Runnable(){
               @Override
               public void run() {
                    for( int i=1; i<50; i++){
                        System. out.println( "java"+ "----"+ i);
                   }
              }
          }).start();

第三种方式:开发中不会出现,但是在一般面试时会遇到:
1)该方式会报错吗?  不会
2)线程会执行哪段代码?  会执行 new Thread中{ }中的代码,即下面用红色标注的部分有效
new Thread(new Runnable(){
               @Override
               public void run() {
                    for( int i=1; i<50; i++){
                        System. out.println(Thread. currentThread().getName()+"----"+i);
                   }
              }
          }){
              @Override
              public void run() {
                    for( int i=1; i<50; i++){
                        System. out.println( "hello"+ "----"+ i);
                   }
              }
          }.start();

定时器:
在java中可以通过Timer和TimerTask类来实现定义调度的功能
 
Timer:  java.util.Timer(具体类)
  • public  Timer( )
  • public void schedule( TimerTask  task, long  delay)
  • public void schedule( TimerTask  task, long  delay,long  period)
 
TimerTask:java.util.TimerTask(抽象类),实现了Runnable接口
  • public abstract void run( )
  • public boolean cancel( )
 
开发中,Quariz是一个完全由java编写的开源调度框架
 

多线程相关的问题:
1、多线程有几种实现方案,分别有哪几种?
     实现方式有两种:
          1)继承Thread类:
          2)实现Runnable接口
     
2、同步有几种方式,分别是什么?
     有两种方式。
     1)同步块的方式          (锁的是任意对象)
     2)同步方法的方式   (锁的是当前类对象)
 
3、启动一个线程是run( )还是start( )?他们的区别是?
     启动线程用start( )。
     run方法封装了线程执行的代码,直接调用仅仅是普通方法的调用
     start方法启动线程,并由JVM自动调用run方法
 
4、sleep( )和wait( )方法的区别?(sleep是在Thread类中定义的,而wait方法定义在Object类)
     首先:sleep方法必须指定时间,而wait可以指定时间也可以不指定时间
     其二:sleep方法在线程休眠期间不释放资源(锁),而wait方法在进入等待状态时释放自己持有的锁。
 
5、为什么wait( ) notify( ) notifyAll( )等方法都定义在Object类中?
     因为wait notify notifyAll等方法的调用依赖于锁对象,而同步代码块的锁对象是任意锁。
     由因为Object对象代表是任意一个对象,所以这些方法的定义必须在Object类中

银联笔试看到这样一道题:
class NewThread extends Thread implements Runnable(){
   public void run(){
    System.out.println("run()");
  }
}
 
class Demo{
  public static void main(String[] args){
    Thread t1 = new Thread(new NewThread());
    t1.start();
  }
}
试问这道题会正常运行吗?如果不能运行,会在第几行报错?
 经过测试这是可以的,NewThread 既继承了Thread又实现了Runnable接口,new NewThread()就可以看成一个Thread或者一个Runable接口的实现类,在测试类中
 Thread t1 = new Thread(new NewThread());Thread构造函数的参数是一个实现Runnable接口的实现类,而NewThread类正好实现类Runnable接口,故可以当做参数传入。
 
 
原文地址:https://www.cnblogs.com/zwbg/p/5902774.html