java多线程

进程:指在 系统中能独立运行并作为资源分配 的基本单位.
线程:线程是进程中的一个实体,作为 系统调度和分派的基本单位.

一、创建线程和启动
(1)继承Thread类创建线程类
1、 定义一个继承Thread类的子类,并重写该类的run()方法;
2、创建Thread子类的实例,即创建了线程对象;
3、调用该线程对象的start()方法启动线程。
class SomeThead extends Thraad   { 
    public void run()   { 
     //do something here  
    }  
 } 
public static void main(String[] args){
 SomeThread oneThread = new SomeThread();   
  步骤3:启动线程:   
 oneThread.start(); 
}
(2)实现Runnable接口创建线程类
1、定义Runnable接口的实现类,并重写该接口的run()方法;
2、创建Runnable实现类的实例,并以此实例作为Thread的target对象,即该Thread对象才是真正的线程对象。
class SomeRunnable implements Runnable   { 
  public void run()   { 
  //do something here  
  }  
Runnable oneRunnable = new SomeRunnable();   
Thread oneThread = new Thread(oneRunnable);   
oneThread.start(); 
(3)使用ExecutorService、Callable、Future实现有返回结果的多线程。

创建自定义类实现Callable接口,通过Executors.newFixedThreadPool创建线程池,调用线程池实例对象的submit(new MyThread("Thread" + i))方法向里边仍任务(自定义子类实例化对象作为参数传入)。调用shutdown()关闭线程池。

其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。

public class ThreadPoolTest4 {

    // 具有返回值的测试线程

    class MyThread implements Callable<String> {

        private String name;

        public MyThread(String name) {

            this.name = name;

        }

        @Override

        public String call() {

            int sleepTime = new Random().nextInt(1000);

            try {

                Thread.sleep(sleepTime);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            // 返回给调用者的值

            String str = name + " sleep time:" + sleepTime;

            System.out.println(name + " finished...");

            return str;

        }

    }

    private final int POOL_SIZE = 5;

    private final int TOTAL_TASK = 20;

    // 方法一,自己写集合来实现获取线程池中任务的返回结果

    public void testByQueue() throws Exception {

        // 创建线程池

        ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);

        BlockingQueue<Future<String>> queue = new LinkedBlockingQueue<Future<String>>();

        // 向里面扔任务

        for (int i = 0; i < TOTAL_TASK; i++) {

            Future<String> future = pool.submit(new MyThread("Thread" + i));

            queue.add(future);

        }

        // 检查线程池任务执行结果

        for (int i = 0; i < TOTAL_TASK; i++) {

            System.out.println("method1:" + queue.take().get());

        }

        // 关闭线程池

        pool.shutdown();

    }

    // 方法二,通过CompletionService来实现获取线程池中任务的返回结果

    public void testByCompetion() throws Exception {

        // 创建线程池

        ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);

        CompletionService<String> cService = new ExecutorCompletionService<String>(pool);

        // 向里面扔任务

        for (int i = 0; i < TOTAL_TASK; i++) {

            cService.submit(new MyThread("Thread" + i));

        }

        // 检查线程池任务执行结果

        for (int i = 0; i < TOTAL_TASK; i++) {

            Future<String> future = cService.take();

            System.out.println("method2:" + future.get());

        }

        // 关闭线程池

        pool.shutdown();

    }

    public static void main(String[] args) throws Exception {

        ThreadPoolTest4 t = new ThreadPoolTest4();

        t.testByQueue();

        t.testByCompetion();

    }

}

二、线程的生命周期
1、新建状态
 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)
注意:不能对已经启动的线程再次调用start()方法,否则会出Java.lang.IllegalThreadStateException异常。
2、就绪状态
处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列,一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
3、运行状态
      处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
处于就绪状态的线程,如果获得了cpu的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。如果该线程失去了cpu资源,就会又从运行状态变为就绪状态。重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。

: 当发生如下情况是,线程会从运行状态变为阻塞状态:

     ①、线程调用sleep方法主动放弃所占用的系统资源

     ②、线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞

     ③、线程试图获得一个同步监视器,但更改同步监视器正被其他线程所持有

     ④、线程在等待某个通知(notify)

     ⑤、程序调用了线程的suspend方法将线程挂起。不过该方法容易导致死锁,所以程序应该尽量避免使用该方法。

当线程的run()方法执行完,或者被强制性地终止,例如出现异常,或者调用了stop()、desyory()方法等等,就会从运行状态转变为死亡状态。
4、阻塞状态

      处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。 

在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。
5、死亡状态
      当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

线程管理:
关于sleep()方法和yield()方的区别如下:
yield()方法和sleep()方法有点相似,都是Thread类提供的一个静态的方法,
①、sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态,所以有可能刚进入就绪状态,又被调度到运行状态。

②、sleep方法声明抛出了InterruptedException,所以调用sleep方法的时候要捕获该异常,或者显示声明抛出该异常。而yield方法则没有声明抛出任务异常。

③、sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法来控制并发线程的执行。

四、线程同步
     java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
1、同步方法     
      即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
  2、同步代码块     
     即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
3、使用特殊域变量(volatile)实现线程同步      
   volatile关键字为域变量的访问提供了一种免锁机制;
   使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新;
   因此每次使用该域就要重新计算,而不是使用寄存器中的值;
   volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。
ReentrantLock() : 创建一个ReentrantLock实例         
 lock() : 获得锁        
 unlock() : 释放锁
五、线程通信
1、借助于Object类的wait()、notify()和notifyAll()实现通信
      线程执行wait()后,就放弃了运行资格,处于冻结状态;
     线程运行时,内存中会建立一个线程池,冻结状态的线程都存在于线程池中,notify()执行时唤醒的也是线程池中的线程,线程池中有多个线程时唤醒第一个被冻结的线程
      notifyall(), 唤醒线程池中所有线程。
注: (1) wait(), notify(),notifyall()都用在同步里面,因为这3个函数是对持有锁的线程进行操作,而只有同步才有锁,所以要使用在同步中;
       ( 2) wait(),notify(),notifyall(),  在使用时必须标识它们所操作的线程持有的锁,因为等待和唤醒必须是同一锁下的线程;而锁可以是任意对象,所以这3个方法都是Object类中的方法。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值