黑马程序员——多线程
2015-12-11 14:23
281 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
概述:
要理解多线程,首先就要明白什么是进程,然后再明白什么是线程,就可以理解多线程了。
1、进程:就是正在执行的应用程序,比如:QQ,360等等。
2、线程:是进程的执行单元和进行路径,如QQ不只可以聊天,还可以访问空间或者邮箱等等。
当我们理解了进程和线程,就要把进程和线程进行细分:
进程:
1、单进程:计算机只能做一个事情,只能操作一个任务。
2、多进程:计算机能做多个事情,同一时间段能执行多个任务,如边玩游戏边听音乐。
多进程的意义:是为了提高CPU的使用率。
线程:
1、单线程:一个应用程序只有一条执行路径,只能执行一个任务。
2、多线程:一个应用程序有多条执行路径,可以执行多个任务。
多线程的意义:线程的意义是为了提高应用程序的使用率。
那么Java程序的运行原理及JVM的启动是多线程的吗?
1:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
2:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
注意事项:
线程是程序(进程)使用CPU的最基本单位,程序(进程)的执行其实都是在抢CPU的执行权。多个进程在抢CPU的执行权,而其中的某一个进程的执行路径比较多,就会有更高的机率抢到执行权。但哪一个线程在哪个时刻抢到执行权,是无法保证的,所以线程的执行有“随机性”。
内容:
一、多线程的实现方案:
先要知道启动一个线程是start()方法,而真正要运行的是继承Thread类或者实现Runnable接口的run()方法里面的代码(注:不推荐第三种方法,实现Callable接口)。
1、继承Thread类,重写run()方法
public class MyThread extends Thread {
public void run(){
System.out.println("run方法里面的代码是线程执行的任务内容");
}
}
2、实现Runnable接口,重写run()方法
public class MyThread implements Runnable {
public void run(){
System.out.println("run方法里面的代码是线程执行的任务内容");
}
}
二、线程的调度和优先级:
1、调度:
A、分时调度,就是每一个线程都在规定的时间内执行,执行完毕轮到下一个线程,以此循环,这是一种公平的调度模式。
B、抢占式调度,没有什么规定可言,是看哪个线程的生命力强大,哪个就能抢到执行权,这是弱肉强食的调度模式,而Java就是采用这种调度模式。
2、优先级:
A、所有线程默认都是5。
B、范围是1-10。
C、程序员可以根据自己的需要设置线程的优先级。
三、线程的控制(常见方法):
1、休眠线程:sleep();
意义:是让线程在执行过程中,按规定的时间休眠,时间一结束,就会继续往下执行。
方法调用:因为是静态方法可以类名直接调用,调用时会抛出InterruptedException(当线程在获得之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出此异常)。要使线程休眠,就要放在run()方法里面。
2、加入线程:join();
意义:让一个线程先独立执行完之后,再让其它的线程加入进来抢执行权。
调用方法:是非静态方法,哪个线程要先独立执行,那么就该线程对象调此方法。调用时会抛出InterruptedException(当线程在获得之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出此异常)。
3、礼让线程:yield();
意义:只是让线程看起来更和谐的轮流获得执行权,但并不能完全保证每一次多个线程都轮流执行。
调用方法:静态方法,直接类名调用。要放在run()方法里面,因为是每个线程都要礼让。
4、后台线程:setDaemon(boolean on);
意义:当一个线程被设置为守护线程时,只要主线程执行完之后,那么该线程就会被消灭。
调用方法:非静态方法,哪个线程要被设置为守护线程,那么就该线程对象调此方法。
5、终止线程:
两种方法:
1、stop()方法:该方法已过时,不推荐使用。因为只要被stop()后,程序后面的代码就无法继续执行,具有不安全性。
2、interrupt()方法:此方法是具有安全性的,当前线程被终止后,程序后面的代码仍将继续执行。
方法调用:两种方法都非静态,要终止哪个线程,那么就该线程对象调用此方法。调用时会抛出InterruptedException(当线程在获得之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出此异常)。
6、等待唤醒机制:
此机制有wait()、notify()和notifyAll()三个方法,虽然都是Object类里的方法,
但是都可以对线程造成控制,所以都是与线程相关的方法。
1、wait()方法:等待获取执行权
2、notify()方法:唤醒单个线程
3、notifyAll()方法:唤醒全部线程
下面以生产者与消费者来理解等待唤醒机制比较容易:
生产者-->先看是否有数据,有就等待,没有就生产数据
消费者-->先看是否有数据,有就消费数据,没有就等待
四、多线程的安全问题:
我们在判断线程是否会出现安全问题,那就要依据以下三点:
1、是否有多线程环境。
2、是否有共享数据。
3、是否有多条语句操作共享数据。
那么当条件满足其中一点或者全部都满足的时候,我们要怎么处理呢?
第一种办法,加synchronized同步关键字:
方法1:
创建同步代码块,将需要被同步的代码包裹在代码块中:
public class MyThread implements Runnable {
// 共性数据
private int num = 00;
public void run() {
while (num > 0) {
//同步代码块,这里的锁对象可以任意对象,为了方便使用this对象
synchronized (this) {
//把线程对数据进行操作的语句放到同步代码块中
System.out.println(Thread.currentThread().getName() + (num--));
}
}
}
}
方法2:
创建同步方法,将需要被同步的代码放到方法中:
public class MyThread implements Runnable {
// 共性数据
private int num = 00;
public void run() {
while (num > 0) {
// 调用同步方法
method();
}
}
//同步方法,把同步关键字加在方法申明中,这里的锁对象是this
public synchronized void method() {
System.out.println(Thread.currentThread().getName() + (num--));
}
}
方法3:
创建静态同步方法,将需要被同步的代码放到方法中:
public class MyThread implements Runnable {
// 共性数据,也要是静态的
private static int num = 00;
public void run() {
while (num > 0) {
method();
}
}
//静态同步方法,锁对象是当前类的字节码文件对象,xxx.class
public static synchronized void method() {
System.out.println(Thread.currentThread().getName() + (num--));
}
}
观察上面三个方法,我们并没有直接看到在哪里加上锁,在哪里释放锁,为了更清晰的表达加锁和释放锁,JDK5以后提供了一个新的锁对象:Lock。
加锁:在被同步的代码前面加锁。
释放锁:在同步代码结束后释放锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread implements Runnable {
// 共性数据
private int num = 00;
// 创建Lock锁对象,Lock是接口,接收的是子类对象
private Lock lock = new ReentrantLock();
public void run() {
while (num > 0) {
// 在操作共享数据前加锁
lock.lock();
System.out.println(Thread.currentThread().getName() + "--" + (num--));
// 在结束后释放锁
lock.unlock();
}
}
}
五、线程的生命周期:
线程常见的状态:
1、新建-->就绪-->运行-->死亡
2、新建-->就绪-->运行-->就绪-->运行-->死亡
3、新建-->就绪-->运行-->其它阻塞-->就绪-->运行-->死亡
4、新建-->就绪-->运行-->同步阻塞-->就绪-->运行-->死亡
5、新建-->就绪-->运行-->等待阻塞-->同步阻塞-->就绪-->运行-->死亡
具体结构如下图:
六、线程池:
因为程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
好处:
线程池里的没有哥线程代码结束后并不会死亡,而是再次会到线程池中称为空闲状态,等待下一个对象来使用。
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
概述:
要理解多线程,首先就要明白什么是进程,然后再明白什么是线程,就可以理解多线程了。
1、进程:就是正在执行的应用程序,比如:QQ,360等等。
2、线程:是进程的执行单元和进行路径,如QQ不只可以聊天,还可以访问空间或者邮箱等等。
当我们理解了进程和线程,就要把进程和线程进行细分:
进程:
1、单进程:计算机只能做一个事情,只能操作一个任务。
2、多进程:计算机能做多个事情,同一时间段能执行多个任务,如边玩游戏边听音乐。
多进程的意义:是为了提高CPU的使用率。
线程:
1、单线程:一个应用程序只有一条执行路径,只能执行一个任务。
2、多线程:一个应用程序有多条执行路径,可以执行多个任务。
多线程的意义:线程的意义是为了提高应用程序的使用率。
那么Java程序的运行原理及JVM的启动是多线程的吗?
1:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
2:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
注意事项:
线程是程序(进程)使用CPU的最基本单位,程序(进程)的执行其实都是在抢CPU的执行权。多个进程在抢CPU的执行权,而其中的某一个进程的执行路径比较多,就会有更高的机率抢到执行权。但哪一个线程在哪个时刻抢到执行权,是无法保证的,所以线程的执行有“随机性”。
内容:
一、多线程的实现方案:
先要知道启动一个线程是start()方法,而真正要运行的是继承Thread类或者实现Runnable接口的run()方法里面的代码(注:不推荐第三种方法,实现Callable接口)。
1、继承Thread类,重写run()方法
public class MyThread extends Thread {
public void run(){
System.out.println("run方法里面的代码是线程执行的任务内容");
}
}
2、实现Runnable接口,重写run()方法
public class MyThread implements Runnable {
public void run(){
System.out.println("run方法里面的代码是线程执行的任务内容");
}
}
二、线程的调度和优先级:
1、调度:
A、分时调度,就是每一个线程都在规定的时间内执行,执行完毕轮到下一个线程,以此循环,这是一种公平的调度模式。
B、抢占式调度,没有什么规定可言,是看哪个线程的生命力强大,哪个就能抢到执行权,这是弱肉强食的调度模式,而Java就是采用这种调度模式。
2、优先级:
A、所有线程默认都是5。
B、范围是1-10。
C、程序员可以根据自己的需要设置线程的优先级。
三、线程的控制(常见方法):
1、休眠线程:sleep();
意义:是让线程在执行过程中,按规定的时间休眠,时间一结束,就会继续往下执行。
方法调用:因为是静态方法可以类名直接调用,调用时会抛出InterruptedException(当线程在获得之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出此异常)。要使线程休眠,就要放在run()方法里面。
2、加入线程:join();
意义:让一个线程先独立执行完之后,再让其它的线程加入进来抢执行权。
调用方法:是非静态方法,哪个线程要先独立执行,那么就该线程对象调此方法。调用时会抛出InterruptedException(当线程在获得之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出此异常)。
3、礼让线程:yield();
意义:只是让线程看起来更和谐的轮流获得执行权,但并不能完全保证每一次多个线程都轮流执行。
调用方法:静态方法,直接类名调用。要放在run()方法里面,因为是每个线程都要礼让。
4、后台线程:setDaemon(boolean on);
意义:当一个线程被设置为守护线程时,只要主线程执行完之后,那么该线程就会被消灭。
调用方法:非静态方法,哪个线程要被设置为守护线程,那么就该线程对象调此方法。
5、终止线程:
两种方法:
1、stop()方法:该方法已过时,不推荐使用。因为只要被stop()后,程序后面的代码就无法继续执行,具有不安全性。
2、interrupt()方法:此方法是具有安全性的,当前线程被终止后,程序后面的代码仍将继续执行。
方法调用:两种方法都非静态,要终止哪个线程,那么就该线程对象调用此方法。调用时会抛出InterruptedException(当线程在获得之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出此异常)。
6、等待唤醒机制:
此机制有wait()、notify()和notifyAll()三个方法,虽然都是Object类里的方法,
但是都可以对线程造成控制,所以都是与线程相关的方法。
1、wait()方法:等待获取执行权
2、notify()方法:唤醒单个线程
3、notifyAll()方法:唤醒全部线程
下面以生产者与消费者来理解等待唤醒机制比较容易:
生产者-->先看是否有数据,有就等待,没有就生产数据
消费者-->先看是否有数据,有就消费数据,没有就等待
四、多线程的安全问题:
我们在判断线程是否会出现安全问题,那就要依据以下三点:
1、是否有多线程环境。
2、是否有共享数据。
3、是否有多条语句操作共享数据。
那么当条件满足其中一点或者全部都满足的时候,我们要怎么处理呢?
第一种办法,加synchronized同步关键字:
方法1:
创建同步代码块,将需要被同步的代码包裹在代码块中:
public class MyThread implements Runnable {
// 共性数据
private int num = 00;
public void run() {
while (num > 0) {
//同步代码块,这里的锁对象可以任意对象,为了方便使用this对象
synchronized (this) {
//把线程对数据进行操作的语句放到同步代码块中
System.out.println(Thread.currentThread().getName() + (num--));
}
}
}
}
方法2:
创建同步方法,将需要被同步的代码放到方法中:
public class MyThread implements Runnable {
// 共性数据
private int num = 00;
public void run() {
while (num > 0) {
// 调用同步方法
method();
}
}
//同步方法,把同步关键字加在方法申明中,这里的锁对象是this
public synchronized void method() {
System.out.println(Thread.currentThread().getName() + (num--));
}
}
方法3:
创建静态同步方法,将需要被同步的代码放到方法中:
public class MyThread implements Runnable {
// 共性数据,也要是静态的
private static int num = 00;
public void run() {
while (num > 0) {
method();
}
}
//静态同步方法,锁对象是当前类的字节码文件对象,xxx.class
public static synchronized void method() {
System.out.println(Thread.currentThread().getName() + (num--));
}
}
观察上面三个方法,我们并没有直接看到在哪里加上锁,在哪里释放锁,为了更清晰的表达加锁和释放锁,JDK5以后提供了一个新的锁对象:Lock。
加锁:在被同步的代码前面加锁。
释放锁:在同步代码结束后释放锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread implements Runnable {
// 共性数据
private int num = 00;
// 创建Lock锁对象,Lock是接口,接收的是子类对象
private Lock lock = new ReentrantLock();
public void run() {
while (num > 0) {
// 在操作共享数据前加锁
lock.lock();
System.out.println(Thread.currentThread().getName() + "--" + (num--));
// 在结束后释放锁
lock.unlock();
}
}
}
五、线程的生命周期:
线程常见的状态:
1、新建-->就绪-->运行-->死亡
2、新建-->就绪-->运行-->就绪-->运行-->死亡
3、新建-->就绪-->运行-->其它阻塞-->就绪-->运行-->死亡
4、新建-->就绪-->运行-->同步阻塞-->就绪-->运行-->死亡
5、新建-->就绪-->运行-->等待阻塞-->同步阻塞-->就绪-->运行-->死亡
具体结构如下图:
六、线程池:
因为程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
好处:
线程池里的没有哥线程代码结束后并不会死亡,而是再次会到线程池中称为空闲状态,等待下一个对象来使用。
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------