JAVA多线程

半世苍凉 提交于 2020-02-27 08:50:52

JAVA多线程

1.进程与线程介绍

  •  进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)。
  •  线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)。
  •  进程的栈内存和堆内存都是独立的,而线程的栈内存是独立的,堆内存是共享的。

 

2.实现多线程的两种方式 

  • 继承Thread类,重写该类的run()方法。
  • 实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。
  • 区别:

    实现Runnable接口比继承Thread类所具有的优势:

    1):适合多个相同的程序代码的线程去处理同一个资源

    2):可以避免java中的单继承的限制

    3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

    4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

 

3.线程池

  Jdk1.5后提供了Executor为我们管理Thread对象,从而简化了并发编程。Executor允许你管理异步任务的执行,而无须显式地管理线程的生命周期且这是jdk1.5后启动任务的优选方法。

  Executors身上有几个静态方法创建线程池:

  •   CachedThreadPool可以一次性预先执行高昂的线程分配,因而也就可以限制线程的数量了。这样可以节约时 间,因为你不用为每个任务都固定的付出创建线程的开销。
  •   FixedThreadPool在执行程序过程中通常会创建与所需要量相同的线程,然后在它回收旧线程时停止新建 线程,因此它是合理的Excutor的首选。
  •   SingleThreadExcutor就像线程数量为1的FixedThreadPool。对于你希望在另一个线程中连续的运行的任何事物(长期存活的任务)来说,都是很有用的。
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
        for(int i=0; i<5; i++){        
            newFixedThreadPool.execute(new ExceThree(i));
        }
        newFixedThreadPool.shutdown();

 

4.从任务中返回值

  Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在jdk1.5中引入的Callable是一种具有类型参数的泛型,他的类型参数表示的是从任务方法call()中返回的值,并且必须使用ExecutorService.submit()方法调用它。通过get()获取返回结果。

public class CallableTest implements Callable {
    
    private int id;
    
    public CallableTest(int id){
        this.id = id;
    }

    public String call() throws Exception {    
        return "This test's id = " + id;
    }

    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        List<Future<String>> list =new ArrayList<Future<String>>();
        for(int i=0; i<5; i++){
            list.add(service.submit(new CallableTest(i)));
        }
        for (Future<String> future : list) {
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

 5. synchronized

  为了解决共享资源竞争的问题,每个对象和类都会有一把锁分别为对象锁和类锁,Java以提供关键字synchronized的形式,为防止资源冲突提供了内置支持。当任务要执行被synchronized关键字保护的代码片段的时候,它会检查锁是否可用,然后获取锁,执行代码。释放锁。

  synchronized修饰的对象有以下几种:

   1)修饰一个代码块,被修饰的代码称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象(对象锁)。

    

/**
 * 同步线程
 */
class SyncThread implements Runnable {
   private static int count;

   public SyncThread() {
      count = 0;
   }

   public  void run() {
      synchronized(this) {
         for (int i = 0; i < 5; i++) {
            try {
               System.out.println(Thread.currentThread().getName() + ":" + (count++));
               Thread.sleep(100);
            } catch (InterruptedException e) {
               e.printStackTrace();
            }
         }
      }
   }

   public int getCount() {
      return count;
   }
}

  SyncThread的调用:

SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();

  结果如下:

  

SyncThread1:0 
SyncThread1:1 
SyncThread1:2 
SyncThread1:3 
SyncThread1:4 
SyncThread2:5 
SyncThread2:6 
SyncThread2:7 
SyncThread2:8 
SyncThread2:9*

 

  2)修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象(对象锁)。

  

public synchronized void method()
{
   // todo
}

  等同与:

  

public void method()
{
   synchronized(this) {
      // todo
   }
}

 

  

  3)修饰一个静态方法,其作用范围是整个静态方法,作用的对象是这个类的所有对象(类锁)。

  

/**
 * 同步线程
 */
class SyncThread implements Runnable {
   private static int count;

   public SyncThread() {
      count = 0;
   }

   public synchronized static void method() {
      for (int i = 0; i < 5; i ++) {
         try {
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }

   public synchronized void run() {
      method();
   }
}

  调用代码:

  

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

  结果如下:

  

SyncThread1:0 
SyncThread1:1 
SyncThread1:2 
SyncThread1:3 
SyncThread1:4 
SyncThread2:5 
SyncThread2:6 
SyncThread2:7 
SyncThread2:8 
SyncThread2:9

 

 4)修饰一个类,其作用范围是{}里面的部分,作用的对象是这个类的所有对象(类锁)。

  

class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}

  synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。

 

6. volatile

  先引入两个概念:

  •   原子性:在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
  •        可见性:当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取                         时,此时内存中可能还是原来的旧值,因此无法保证可见性。

  volatile修饰的变量是能保证可见性但是不能保证原子性的。

  当一个域的值依赖于它之前的值时(如n=n+1,n++),volatile就无法工作了,如果某个域的值受到其他域的限制时(如n=m+1),volatile也无法工作。

  使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。

  volatile的典型应用是单例模式的双重检查,为处理原版非延迟加载方式瓶颈问题,我们需要对 instance 进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同获取锁了),但在Java中行不通,因为同步块外面的if (instance == null)可能看到已存在,但不完整的实例。JDK5.0以后版本若instance为volatile则可行:

public class Singleton {  
 private volatile static Singleton instance = null;  
 private Singleton() {}  
 public static Singleton getInstance() {  
  if (instance == null) {  
   synchronized (Singleton.class) {// 1  
    if (instance == null) {// 2  
     instance = new Singleton();// 3  
    }  
   }  
  }  
  return instance;  
 }  
}  

 

7.线程状态

 

线程在Running的过程中可能会遇到阻塞(Blocked)情况

  1. 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
  2. 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
  3. 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!