线程与进程:
一个程序至少有一个进程,一个进程至少有一个线程.
进程 | 是一个正在执行的程序,每个进程执行都有一个执行顺序,也叫执行路径,或者控制单元。 | 进程在执行过程中拥有独立的内存单元 |
线程 | 是一个进程中的一个控制单元,线程控制着进程的执行 | 多个线程共享内存,极大地提高了运行效率 |
在windows环境下JVM启动的时候会有一java.exe进程。该进程至少有一线程负责java程序的执行,该线程的代码存在于main方法中,main方法作为java程序初试线程的起点,其他的任何线程都是由这个初始线程启动的。
JVM有两种线程:守护线程和非守护线程,守护线程是有JVM自己使用的,比如垃圾回收机制。Java的初试线程是非守护线程。
多线程的意义在于,在一个应用程序中,可以有多个部分同时执行,而操作系统并没有将多个线程看做独立的应用,以实现进程的调度、管理和资源的分配。
线程的创建:
1) 方法一:定义类继承java.lang.Thread,复写 run() 方法,通过实例化对象调用 start() 方法。
public void start() l 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 l 结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。 l 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。 |
代码示例:
class ThreadDemo extends Thread { public void run() { for(int i=0; i<1000; i++) { System.out.println("run---"); } } } public class TestThread { public static void main(String [] args) { ThreadDemo td = new ThreadDemo(); td.start(); for(int i=0; i<1000; i++) { System.out.println("main---"); } } } |
|
2) 方法二:
声明实现 java.lang.Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递给一个新的Thread对象,然后通过start()方法启动一个线程。
public Thread(Runnable target) 分配新的 Thread 对象。这种构造方法与 Thread(null, target,gname) 具有相同的作用,其中的 gname 是一个新生成的名称。自动生成的名称的形式为 “Thread-”+n,其中的 n 为整数。 |
代码示例:
class ThreadDemo implements Runnable { public void run() { for(int i=0; i<100; i++) { System.out.println(Thread.currentThread()+"running"); } } } public class Test { public static void main(String [] args) { new Thread(new ThreadDemo()).start(); } } |
两种线程的创建方式的对比:
方法一:继承Thread类 | 只能单继承Thread | 存放在Thread子类的run()方法中 |
方法二:实现Runable接口 | 避免了单继承的局限性 | 存放在实现Runable接口类的run()方法中 |
线程的状态:
线程安全问题:
问题原因:
当多条语句在操作一个线程共享数据时,其中一个线程只对多条数据执行了一部分,还未执行完,另一线程启动,参与到共享数据中来,就会造成共享数据的错误。
解决办法:
操作线程,将操作共享数据的多条语句,在一个线程中全部执行完,执行过程中,其他线程不能参与。
Java提供了“同步代码块”来解决线程安全问题。
Synchronized (对象) { //多条语句 } |
语句中的对象如同锁,持有锁的线程可以再同步中执行,没有取得锁的线程即使获得cpu执行权,也无法执行同步代码。
同步方法:
public void synchronized run() {
} |
静态方法与成员方法的对象锁:
函数需要被对象调用,那么函数都有一个所属对象的引用,同步函数的锁是this。
同步函数被静态修饰时,使用的线程锁是该类的字节码文件对象,类.class
线程安全前提:
至少两个以上线程。
是否用的同一个线程。
单例模式:懒汉式
class Single { private static Single s = null; private Single() {} public static Single getInstance() { if(s==null) { synchronized(Single.class) { if(s==null) { s = new Single(); } } } return s; } } |
线程间通信:
多个线程操作同一资源,但是操作的动作不同。
线程死锁问题:
两个线程同时使用相同的两个线程锁,但是每一个线程只拥有其中的一个线程锁,两个线程不放开自己的线程锁,于是便产生了线程死锁的问题。
等待唤醒机制:都使用在线程同步中,因为都要对持有对象锁的那个线程操作
l publicfinal void wait() throwsInterruptedException
导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。 当前的线程必须拥有此对象锁。
l public final void notify()
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
易错点: 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。并不是使用notify();之后就执行新的线程,只有在当前线程放弃当前线程锁的时候,才随即选择等待在当前对象上的线程。
JDK1.5新特性
将同步关键字synchronized替换成现实Lock操作。Lock 替代了synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
注意:一定要进行线程解锁。
void await() 造成当前线程在接到信号或被中断之前一直处于等待状态。 |
假定调用此方法时,当前线程保持了与此 Condition 有关联的锁定。这取决于确定是否为这种情况以及不是时,如何对此作出响应的实现。通常,将抛出一个异常(比如 IllegalMonitorStateException)并且该实现必须对此进行记录。 |
void signal() 唤醒一个等待线程。 如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁定。 |
void signalAll() 唤醒所有等待线程。 如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁定。 |
javaAPI示例:
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
|
守卫线程:
守卫线程是JVM的后台线程,例如垃圾回收机制,随着JVM的线程启动而启动,JVM的所有非守卫线程结束而结束。
public final void setDaemon(boolean on) |
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。 on - 如果为 true,则将该线程标记为守护线程。 |
|
线程的常用方法:
public final int getPriority() 返回线程的优先级。 |
public final void setPriority(int newPriority) 更改线程的优先级。 首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。 在其他情况下,线程优先级被设定为指定的 newPriority 和该线程的线程组的最大允许优先级相比较小的一个。 优先级:public static final int MIN_PRIORITY/NORM_PRIORITY/MAX_PRIORITY |
public void interrupt() 中断线程。 |
public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 |
使用Executor(扩展)
通过使用java.util.concurrent包中的执行器(Executor)管理Thread对象,简化多线程编程。
Executor 在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。Excutor允许你管理异步任务的执行,而无须显示的管理线程的生命周期。
Excutor是启动任务的优选方法。
java.util.concurrent 下 接口 Executor |
执行已提交的 Runnable 任务的对象。 此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。通常使用 Executor 而不是显式地创建线程。 例如,可能会使用以下方法,而不是为一组任务中的每个任务调用 new Thread(new(RunnableTask())).start(): Executor executor = anExecutor; executor.execute(new RunnableTask1()); executor.execute(new RunnableTask2()); ... |
构造方法 |
void execute(Runnable command) 在未来某个时间执行给定的命令。该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定。 |
ExecutorService是具有生命周期的Executor。
代码示例
Import java.util.cincurrent.*; class Thre implements Runnable { public void run() {} } class ThreadTest { public static void main(String[] args) { ExecutorService exec = Exectors.newCachedThreadPool(); ; for(int i=0; i<5; i++) { exec.execute(new Thre()); exec.shutdown(); } } } |
java.util.concurrent
类 Executors:
此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。此类支持以下各种方法:
创建并返回设置有常用配置字符串的ExecutorService 的方法。
创建并返回设置有常用配置字符串的ScheduledExecutorService 的方法。
创建并返回“包装的”ExecutorService方法,它通过使特定于实现的方法不可访问来禁用重新配置。
创建并返回 ThreadFactory 的方法,它可将新创建的线程设置为已知的状态。
创建并返回非闭包形式的 Callable的方法,这样可将其用于需要 Callable 的执行方法中