← 返回文章列表

ARTICLE

Java线程学习笔记

在操作系统中,每个独立运行的程序就是一个进程,当一个程序进入内存运行时,即变成一个进程。线程是进程的组成部分,一个线程必须在一个进程之内,而一个进程可以拥有多个线程,一个进程中至少有一个线程。线程是最小的处理单位,线程可以拥有自己的堆栈,计数器和局部变量,但不能拥有系统资源,多个线程共享其所在进程的系统资源。

线程概述

在操作系统中,每个独立运行的程序就是一个进程,当一个程序进入内存运行时,即变成一个进程。

线程是进程的组成部分,一个线程必须在一个进程之内,而一个进程可以拥有多个线程,一个进程中至少有一个线程。线程是最小的处理单位,线程可以拥有自己的堆栈,计数器和局部变量,但不能拥有系统资源,多个线程共享其所在进程的系统资源。

Java线程模型提供线程所必需的功能指出,基本的Java线程模型有Thread类、Runnable接口、Callable接口和Future接口等,这些线程模型都是面向对象的。

创建线程

基于Java线程模型,创建线程的方式有三种:

  1. 继承Thead类,重写Thread类中的run()方法,直接创建线程;
  2. 实现runnable接口,再通过Thread 类和Runnbale的实现类间接创建一个线程;
  3. 使用Callbable和Future接口间接创建线程。

前面两种较为常见,也比较简单,第三种的创建方式的样例如下:

package me.huding.study.thread;

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

public class CallableTask implements Callable<Integer> {

@Override
public Integer call() throws Exception {
    int i = 0;
    for(;i &lt; 100;i ++){
        System.out.println(Thread.currentThread().getName() + " " + i);
    }
    return i;
}



public static void main(String[] args){
    FutureTask&lt;Integer&gt; task = new FutureTask&lt;&gt;(new CallableTask());

    new Thread(task).start();

    try {
        task.get();
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    for(int i = 1000;i &lt; 1020;i ++){
        System.out.println(Thread.currentThread().getName() + " " + i);
    }
}

}

线程生命周期

线程具有生命周期,当线程被创建并启动后,不会立即进入执行状态,也不会一直处于执行状态。在线程的生命周期中,线程要经过5种状态,新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。线程状态的转换如下图所示:

线程优先级

每个线程执行时都具有一定的优先级,线程的优先级代表该线程的重要程度,当有多个线程同时处于可执行状态并等待获得CPU处理器时,系统将根据各个线程的优先级来调度各线程,优先级高的线程获得CPU时间的机会多,而优先级低的线程则获得较少的执行机会。

每个线程都有默认的优先级,其优先级都与创建该线程的父线程的优先级相同。在默认情况下,主线程具有普通优先级,由主线程创建的子线程也具有普通优先级。

Thread类提供三个静态常量来标识线程的优先级:

  1. MAX_PRIORITY——最高优先级,其值为10;
  2. NORM_PRIORITY——普通优先级,其值为5;
  3. MIN_PRIORITY——最低优先级,其值为1;

Thread类提供了setPriority()方法来对线程的优先级进行设置,而getPriority()方法可以获取线程的优先级。setPriority()方法的参数时一个整数(范围是1~10),也可以使用Thread类提供的三个优先级静态变量。

线程同步

Java使用监控器(也称对象锁)实现同步,每个对象都有一个监视器,使用监控器可以保证一次只允许一个线程执行对象的同步语句。线程通常采用三种方式:同步代码块、同步方法、同步锁。

同步代码块:

使用同步代码块实现同步功能,只需将对实例的访问语句放入一个同步块中,其语法格式为:

synchronized(object){
    //需要同步的代码块
}

其中:

  1. synchronizeds是同步关键字;
  2. object 是同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。

同步方法:

[访问修饰符] synchronized 返回类型 方法名([参数列表]) {
    // 方法体
}

synchronized 关键字修饰的实例方法无需显式地指定同步监视器,同步方法的同步监视器是this,即该方法所属的对象。
一旦一个线程进入一个实例的任何同步方法,其他线程将不能进入该实例的任何同步方法,但该实例的非同步方法仍然能够被调用。

synchronized 锁定是对象,而不是方法或代码块;synchronized也可以修饰类,当用synchronized修饰类时,表示这个类的所有方法都是synchronized的。

同步锁Lock是一种更强大的线程同步机制,通过显式定义同步锁对象来实现线程同步。同步锁提供了,比同步块、同步方法更加广泛的锁定操作,实现更灵活。

Lock是控制多个线程对共享资源进行访问的工具,能够对共享资源进行独占访问。每次只能有一个线程对Lock对象加锁,线程访问共享资源之前需要先获得Lock对象。某些锁可能允许对共享资源并发访问,如ReadWriterLock(读写锁)。Lock和ReadWriterLock是Java 5 提供的关于锁的两个根接口,并为Lock提供了ReentrantLock实现类,为ReadWriterLock实现了ReentrantReadWriterLock实现类。

ReentrantLock类是常用的可重入同步锁,该类对象可以显式地加锁、释放锁。通常使用ReentrantLock的步骤如下:

1.定义一个ReentrantLock锁对象,该对象是final常量。

private final ReentrantLock lock = new ReentrantLock();

2.在需要保证线程安全的代码之前增加“加锁”操作。

lock.lock();

3.在执行完线程安全的代码后“释放锁”。

lock.unlock();

For example:

class Demo {
    private final ReentrantLock lock = new ReentrantLock();
public void run {
    lock.lock();

    try {



    } finally {
      lock.unlock();
    }

} }

线程通信

线程通信可以使用Object类中定义的wait(),notify()和notifyAll()方法,使线程之间互相进行事件通知。执行这些方法时,必须拥有相关对象的锁。

  • wait()方法:让当前线程等待,并释放对象锁,直到其他线程调用该监视器的notify()或者notifyAll()来唤醒该线程。wait()方法也可以带一个参数,用于指明等待的时间,使用此种方式不需要noitify()或者notifyAll()的唤醒。wait方法只能在同步方法中调用。
  • notify()方法:唤醒在此同步监视器上等待的单个线程,解除该线程的阻塞状态。
  • notifyAll()方法:唤醒在此同步方法上等待的所有线程,唤醒次序完全由系统来控制。

notify和notifyAll方法只能在同步方法或同步块中使用。wait方法区别sleep方法之处:wait方法调用时会释放对象锁,而sleep不会。

For example:

package me.huding.study.thread;

public class Product {

int n;
// 为true时表示有值可取,false表示需要放入新值
boolean valueSet = false;
// 生产方法
public synchronized void put(int n){
    if(valueSet){
        try {
            wait();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    this.n = n;
    valueSet = true;
    System.out.println(Thread.currentThread().getName() +"-生产:" + n);
    notify();
}

// 消费方法
public synchronized void get(){
    if(!valueSet){
        try {
            wait();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    valueSet = false;
    System.out.println(Thread.currentThread().getName() +"-消费:" + n);
    notify();
}

}

package me.huding.study.thread;

public class Producer implements Runnable { Product product; public Producer(Product product) { this.product = product; } @Override public void run() { for(int i = 0;i < 10;i ++){ product.put(i); } }

}

package me.huding.study.thread;

public class Consumer implements Runnable { Product product; public Consumer(Product product) { super(); this.product = product; } @Override public void run() { for(int i = 0;i < 10;i ++) product.get();

}

}

package me.huding.study.thread;

public class WaitNotifyDemo {

public static void main(String[] args) {
    Product product = new Product();

    Producer  producer = new Producer(product);

    Consumer consumer = new Consumer(product);

    new Thread(producer,"Producer").start();

    new Thread(consumer,"Consumer").start();

}

}