黑马程序员--多线程

来源:互联网 发布:我好想你网络歌手 编辑:程序博客网 时间:2024/05/19 15:41

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流!-------


1、概念

1.1、进程是一个正在执行的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

1.2、线程就是进程中的一个独立的控制单元。线程在控制着进程的执行,只要进程中有一个线程在执行,进程就不会结束。一个进程中至少有一个线程。

1.3、多线程

java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程,该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像种在一个进程中有多个线程执行的方式,就叫做多线程。

1.4、多线程存在的意义

      多线程的出现能让程序产生同时运行效果,可以提高程序执行效率。例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效,而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。

1.5、计算机CPU的运行原理

         我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。但是,在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。而cpu执行哪个程序,是毫无规律性的。这也是多线程的一个特性:随机性。哪个线程被cpu执行,或者说抢到了cpu的执行权,哪个线程就执行。而cpu不会只执行一个,当执行一个一会后,又会去执行另一个,或者说另一个抢走了cpu的执行权。至于究竟是怎么样执行的,只能由cpu决定。


2、创建线程的两种方式

2.1、继承Thread类 启动线程是调用start方法,start方法是启动线程,并调用run方法,单独调用run方法是无法启动线程的

2.2、实现Runnable接口,避免了继承Thread类之后无法再继承其他类的局限性。为什么要将Runnable接口的子类对象传递给Thread的构造函数?

        因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。


3、线程状态

3.1 线程状态类型

1. 新建状态(New):新创建了一个线程对象。
2. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3. 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

3.2 线程状态图:



4、线程安全问题

4.1 导致线程出现安全问题的原因

当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。简单的说就两点:1、多个线程访问出现延迟。2、线程随机性    

4.2 解决办法

    用关键字synchronized实现代码块同步或者函数同步

卖票的代码块同步示例:

/**  *给卖票程序示例加上同步代码块。  */public class Ticket implements Runnable {private int ticket= 100;Object obj = new Object();public void run() {while (true) {synchronized (obj) {if (ticket> 0) {try {Thread.sleep(10);} catch (Exception e) {}// 显示线程名及余票数System.out.println(Thread.currentThread().getName()+ "剩余ticket=" + ticket--);}}}}}

卖票的函数同步示例:

public class Ticket implements Runnable {private int ticket = 100;Object obj = new Object();public void run() {while (true) {show();}}// 在函数上用synchronized修饰public synchronized void show() {if (ticket > 0) {try {Thread.sleep(10);} catch (Exception e) {}// 显示线程名及余票数System.out.println(Thread.currentThread().getName() + "剩余ticket="+ ticket--);}}}

4.3静态函数的同步方式

  如果同步函数被静态修饰后,使用的锁不在是this。因为静态方法中不可以使用this,静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象, 类名.class才是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。

经典的DCL单例模式:

/** * * 这里用到的DCL,在JDK1.5之后用volatile可以解决DCL的并发问题 * @author guoqiang */public class Singleton {private static volatile Singleton INSTANCE;private Singleton(){}public static Singleton getInstance() {if(null == INSTANCE) {synchronized (INSTANCE) {if(null == INSTANCE) {INSTANCE = new Singleton();}}}return INSTANCE;}}


5.线程间通信
5.1采用wait()、notify()/notifyAll()方法实现线程间通信

示例:

/** *有一个资源 一个线程往里存东西,如果里边没有的话 一个线程往里取东西,如果里面有得话 * @author guoqiang * */public class ResourceDemo2 {public static void main(String[] args) {Resource r = new Resource();// 表示操作的是同一个资源new Thread(new Input(r)).start();// 开启存线程new Thread(new Output(r)).start();// 开启取线程}}//资源class Resource {private String name;private String sex;private boolean flag = false;public synchronized void setInput(String name, String sex) {if (flag) {try {wait();} catch (Exception e) {}// 如果有资源时,等待资源取出}this.name = name;this.sex = sex;flag = true;// 表示有资源notify();// 唤醒等待}public synchronized void getOutput() {if (!flag) {try {wait();} catch (Exception e) {}// 如果木有资源,等待存入资源}System.out.println("name=" + name + "---sex=" + sex);// 这里用打印表示取出flag = false;// 资源已取出notify();// 唤醒等待}}// 存线程class Input implements Runnable {private Resource r;Input(Resource r) {this.r = r;}public void run()// 复写run方法{int x = 0;while (true) {if (x == 0)// 交替打印张三和王羲之{r.setInput("张三", ".....man");} else {r.setInput("王羲之", "..woman");}x = (x + 1) % 2;// 控制交替打印}}}// 取线程class Output implements Runnable {private Resource r;Output(Resource r) {this.r = r;}public void run()// 复写run方法{while (true) {r.getOutput();}}}

5.2 JDK1.5中提供了多线程升级解决方案。

        将同步synchronized替换成显示的Lock操作,将ObjectwaitnotifynotifyAll,替换成了Condition对象,该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。

采用Lock和Condition实现的生产者消费者示例:

/** *  生产者生产商品,供消费者使用 *有两个或者多个生产者,生产一次就等待消费一次 *有两个或者多个消费者,等待生产者生产一次就消费掉 */import java.util.concurrent.locks.*;public class ProducerConsumer {public static void main(String[] args) {Resource res = new Resource();new Thread(new Producer(res)).start();// 第一个生产线程 p1new Thread(new Consumer(res)).start();// 第一个消费线程 c1new Thread(new Producer(res)).start();// 第二个生产线程 p2new Thread(new Consumer(res)).start();// 第二个消费线程 c2}}class Resource {private String name;private int count = 1;private boolean flag = false;// 多态private Lock lock = new ReentrantLock();// 创建两Condition对象,分别来控制等待或唤醒本方和对方线程Condition condition_pro = lock.newCondition();Condition condition_con = lock.newCondition();// p1、p2共享此方法public void setProducer(String name) throws InterruptedException {lock.lock();// 锁try {while (flag)// 重复判断标识,确认是否生产condition_pro.await();// 本方等待this.name = name + "......" + count++;// 生产System.out.println(Thread.currentThread().getName() + "...生产..."+ this.name);// 打印生产flag = true;// 控制生产\消费标识condition_con.signal();// 唤醒对方} finally {lock.unlock();// 解锁,这个动作一定执行}}// c1、c2共享此方法public void getConsumer() throws InterruptedException {lock.lock();try {while (!flag)// 重复判断标识,确认是否可以消费condition_con.await();System.out.println(Thread.currentThread().getName() + ".消费."+ this.name);// 打印消费flag = false;// 控制生产\消费标识condition_pro.signal();} finally {lock.unlock();}}}// 生产者线程class Producer implements Runnable {private Resource res;Producer(Resource res) {this.res = res;}// 复写run方法public void run() {while (true) {try {res.setProducer("商品");} catch (InterruptedException e) {}}}}// 消费者线程class Consumer implements Runnable {private Resource res;Consumer(Resource res) {this.res = res;}// 复写runpublic void run() {while (true) {try {res.getConsumer();} catch (InterruptedException e) {}}}}

6、 常见问题说明
6.1 wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
                这些方法存在与同步中。
                使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
                锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。

6.2 wait(),sleep()有什么区别?
              wait():释放cpu执行权,释放锁。
              sleep():释放cpu执行权,不释放锁。
        6.3为什么要定义notifyAll?
             因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
6.4 为什么wait(), notify()和notifyAll()必须在同步方法或者同步块中被调用?
当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直 到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。

6.5 为什么Thread类的sleep()和yield()方法是静态的?
    Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

0 0
原创粉丝点击