JAVA第十二章 JAVA多线程机制

第十二章 多线程机制

小结
1:多线程(理解)
(1)多线程:一个应用程序有多条执行路径
进程:正在执行的应用程序
线程:进程的执行单元,执行路径
单线程:一个应用程序只有一条执行路径
多线程:一个应用程序有多条执行路径
多进程的意义?
提高CPU的使用率
多线程的意义?
提高应用程序的使用率
(2)Java程序的运行原理及JVM的启动是多线程的吗?
A:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
(3)多线程的实现方案(自己补齐步骤及代码 掌握)
A:继承Thread类
B:实现Runnable接口
(4)线程的调度和优先级问题
A:线程的调度
a:分时调度
b:抢占式调度 (Java采用的是该调度方式)
B:获取和设置线程优先级
a:默认是5
b:范围是1-10
(5)线程的控制(常见方法)
A:休眠线程
B:加入线程
C:礼让线程
D:后台线程
E:终止线程(掌握)
(6)线程的生命周期(参照 线程生命周期图解.bmp)
A:新建
B:就绪
C:运行
D:阻塞
E:死亡
(7)电影院卖票程序的实现
A:继承Thread类
B:实现Runnable接口
(8)电影院卖票程序出问题
A:为了更符合真实的场景,加入了休眠100毫秒。
B:卖票问题
a:同票多次
b:负数票
(9)多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)
A:是否有多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
(10)同步解决线程安全问题
A:同步代码块
synchronized(对象) {
需要被同步的代码;
}
这里的锁对象可以是任意对象。
B:同步方法
把同步加在方法上。
这里的锁对象是this
C:静态同步方法
把同步加在方法上。
这里的锁对象是当前类的字节码文件对象(反射再讲字节码文件对象)
(11)回顾以前的线程安全的类
A:StringBuffer
B:Vector
C:Hashtable
D:如何把一个线程不安全的集合类变成一个线程安全的集合类
用Collections工具类的方法即可。

1.进程与线程

程序
是一段静态的代码,它是应用软件执行的蓝本。
进程
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。

  • 多进程的意义:

    • 单进程的计算机只能做一件事情,而我们现在的计算机都可以做很多件事情。
      举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
    • 也就是说在的计算机都是支持多进程的,就可以在一个时间段内执行多个任务。
      并且可以提高CPU的使用率
    • 问题:
      一边玩游戏,一边听音乐是同时进行的吗?
      不是,因为CPU在某一个时间点上只能做一件事情。
      而我们在玩游戏,或者听音乐的时候,是CPU在走着程序间的高校切换让我们觉得是同时进行的。

    在这里插入图片描述
    线程
    是程序的执行单元,执行路径,是程序使用CPU的最基本单位。
    线程间可以共享进程中的某些内存单元(包括代码与数据),线程的中断与恢复可以更加节省系统的开销。

在这里插入图片描述

(1) java中的多线程机制

Java语言的一大特性点就是内置对多线程的支持。
Java虚拟机快速地把控制从一个线程切换到另一个线程。这些线程将被轮流执行,使得每个线程都有机会使用CPU资源。
在这里插入图片描述

  • 多线程的意义:
    • 多线程的存在,不是提高程序的执行速度,其实是为了提高应用程序的 使用率
    • 程序的执行其实都是在抢CPU的资源,CPU的执行权。
    • 多进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
    • 我们是不敢保证哪一个线程能够在那个时刻抢到,所以线程的执行有 随机性
(2)主线程(main线程)

每个Java应用程序都有一个缺省的主线程。
当JVM(Java Virtual Machine 虚拟机)加载代码,发现main方法之后,就会启动一个线程,这个线程称为“主线程”(main线程),该线程负责执行main方法。
JVM一直要等到Java应用程序中的所有线程都结束之后,才结束Java应用程序 。
在这里插入图片描述
(3)线程的常用方法
1.start() :
线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。
2.run():
Thread类的run()方法与Runnable接口中的run()方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。
3.sleep(int millsecond):
优先级高的线程可以在它的run()方法中调用sleep方法来使自己放弃CPU资源,休眠一段时间。
4.isAlive():
线程处于“新建”状态时,线程调用isAlive()方法返回false。在线程的run()方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true。
5.currentThread():
该方法是Thread类中的类方法,可以用类名调用,该方法返回当前正在使用CPU资源的线程。
6.interrupt() :
一个占有CPU资源的线程可以让休眠的线程调用interrupt()方法“吵醒”自己,即导致休眠的线程发生InterruptedException异常,从而结束休眠,重新排队等待CPU资源。

(4)线程的状态与生命周期

建的线程在它的一个完整的生命周期中通常要经历如下的四种状态:
1.新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
2.运行 :线程必须调用start()方法(从父类继承的方法)通知JVM,这样JVM就会知道又有一个新一个线程排队等候切换了。一旦轮到它来享用CPU资源时,此线程的就可以脱离创建它的主线程独立开始自己的生命周期了。
3.中断:有4种原因的中断:
JVM将CPU资源从当前线程切换给其他线程,使本线程让出CPU的使用权处于中断状态。
线程使用CPU资源期间,执行了sleep(int millsecond)方法,使当前线程进入休眠状。
线程使用CPU资源期间,执行了wait()方法。
线程使用CPU资源期间,执行某个操作进入阻塞状态。
4.死亡 :处于死亡状态的线程不具有继续运行的能力。线程释放了实体。
在这里插入图片描述
例子(一般来说,执行线程的代码肯定是比较耗时的,所以我们用循环改进)


public class Example12_1 {

	public  static void main(String[] args) {    //主线程
		
		 SpeakElephant speakelephant;  
		 SpeakCar speakcar;	
		 	     
		 speakelephant=new SpeakElephant();  //创建线程	         
		 speakcar=speakcar=new SpeakCar();  //创建线程
					
		 speakelephant.start();   //启动线程
		 speakcar.start();    //启动线程
		 for(int i=1;i<15;i++) {
			System.out.println("主人"+i+" ");
		}				
	}

	public static class SpeakElephant extends Thread{     //Thread类的子类
		public void run() {
			for(int i=1;i<20;i++) {
				System.out.println("大象"+i+" ");//如果要获取线程的名称	
			}				//System.out.println(getName()+"大象"+i+" ");
		}					//这个类是继承了Thread类了的
	}	
	public  static class SpeakCar extends Thread{        //Thread类的子类
		public void run() {
			for(int i=1;i<20;i++) {
				System.out.print("轿车"+i+" ");
			}
	}
  }
	
}	


运行结果(注意最后一个用的是print不是println,每次运行的结果都不带一样的,真的很好体现了线程执行的随机性啊,附上三张图)在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

4)线程调度与优先级
  • 处于就绪状态的线程首先进入就绪队列排队等候CPU资源,同一时刻在就绪队列中的线程可能有多个。Java虚拟机(JVM)中的线程调度器负责管理线程,调度器把线程的优先级分为10个级别,分别用Thread类中的类常量表示。
  • Java使用的是抢占式调度模型。 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

设置和获取线程优先级
public final int getPriority()
默认优先级的值是5
public final void setPriority(int newPriority)
参数范围 1-10

(5)实现线程的控制
  • 线程休眠
    (在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。)
    public static voi sleeplong (millis)

  • 线程加入
    (等待该线程终止。)
    public final void join()

  • 线程礼让
    (暂停当前正在执行的线程对象,并执行其他线程。让多个线程的执行更和谐,近似于一人执行一次,但是不能靠它保证一人一次)
    public static void yield()

  • 后台线程
    (将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。)
    public final void setDaemon(boolean on)

  • 中断线程
    (让线程停止,过时了,但是还可以使用。只是不太安全,太暴力了)
    public final void stop()

2.Thread类与线程的创建

(1)如何实现多线程的程序
  • 由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来,
    而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程
    JAVA是不能世界调用系统功能的,所以我们没有办法直接实现多线程程序。
    但是呢?JAVA可以调用C/C++写好的程序来实现多线程程序。
    由C/C++去调用系统功能创建进程,然后由JAVA去调用这样的东西,然后提供一些类供我们使用,我们就可以实现多线程程序了。
  • 那么JAVA提供的类是什么呢?
    Thread类!

方式一 继承Thread类

  • 自定义MyThread继承Thread类
  • MyThread类里面重写run()方法
  • 创建对象
  • 启动线程

方式二 实现Runnable接口

  • 自定义类MyRunnable实现Runnable接口
  • 重写run()方法
  • 创建MyRunnable类的对象
  • 创建Thread类的对象,并把第三步的对象作为构造参数传递
	public class MyRunnable implements Runnable {
	
		@Override
		public void run() {
			for (int x = 0; x < 100; x++) {
				// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
				System.out.println(Thread.currentThread().getName() + ":" + x);
			}
		}
	
	}

	public class MyRunnableDemo {
		public static void main(String[] args) {
			// 创建MyRunnable类的对象
			MyRunnable my = new MyRunnable();
	
			// 创建Thread类的对象,并把C步骤的对象作为构造参数传递,参数要的是接口实质上要的是实现类对象
			// Thread(Runnable target)
			// Thread t1 = new Thread(my);
			// Thread t2 = new Thread(my);
			// t1.setName("林青霞");
			// t2.setName("刘意");
	
			// Thread(Runnable target, String name)
			Thread t1 = new Thread(my, "zjl");
			Thread t2 = new Thread(my, "zyf");
	
			t1.start();
			t2.start();
		}
	}

(2)Thread类的基本获取和设置方法
  • public final String getName()

    public class MyThread extends Thread {
    
    public MyThread() {
     }
     
     public MyThread(String name){
     	super(name);
     }
    
     @Override
     public void run() {
     	for (int x = 0; x < 100; x++) {
     		System.out.println(getName() + ":" + x);
     	}
       }
     }
    
  • public final void setName(String name)
    其实通过构造方法也可以给线程起名字,当然我觉得最方便的还是用set方法设置名字。

	  public class MyThreadDemo {
		public static void main(String[] args) {
			// 创建线程对象
			//无参构造+setXxx()
			// MyThread my1 = new MyThread();
			// MyThread my2 = new MyThread();
			// //调用方法设置名称
			// my1.setName("林青霞");
			// my2.setName("刘意");
			// my1.start();
			// my2.start();
			
			//带参构造方法给线程起名字
			// MyThread my1 = new MyThread("zjl");
			// MyThread my2 = new MyThread("zrf");
			// my1.start();
			// my2.start();			
		}
	}

  • 获取main方法所在的线程名称
    public static Thread currentThread()
//我要获取main方法所在的线程对象的名称,该怎么办呢?
System.out.println(Thread.currentThread().getName());

(3) 关于run方法启动的次数
对于具有相同目标对象的线程,当其中一个线程享用CPU资源时,目标对象自动调用接口中的run方法,这时,run方法中的局部变量被分配内存空间,当轮到另一个线程享用CPU资源时,目标对象会再次调用接口中的run方法,那么,run()方法中的局部变量会再次分配内存空间。也就是说run()方法已经启动运行了两次,分别运行在不同的线程中,即运行在不同的时间片内。

3.线程同步

  • 在处理多线程问题时,我们必须注意这样一个问题:
    当两个或多个线程同时访问同一个变量,并且一个线程需要修改这个变量。我们应对这样的问题作出处理。
  • 同步可以解决安全问题的根本原因就在那个锁对象上。该对象如同锁的功能。当线程开始执行同步代码块前,必须先获得对同步代码块的锁定。并且任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定
  • 在处理线程同步时,要做的第一件事就是要把修改数据的方法用关键字synchronized来修饰。
(1) 线程的安全问题
  • 出现安全问题的原因
    (1)是否是多线程环境
    (2)是否有共享数据
    (3)是否有多条语句操作共享数据
  • 解决问题
    把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可—> synchronized
    ( 一般都是解决第三个问题,你都已经在做多线程的东西了,自然也是有共享数据的,所以一二问题不考虑了,当然,这是一般)

同步代码块
synchronized(对象)
{
需要同步的代码;
}

注意:锁对象不一样的三种情况
(1) 同步代码块的锁对象:任意对象

	public class SellTicket implements Runnable {
		// 定义100张票
		private int tickets = 100;
		//创建锁对象,得是同一个锁对象,这个对象我们可以认为是任意一个对象,同步方法锁对象是 this
		private Object obj = new Object();
	
		@Override
		public void run() {
			while (true) {
				synchronized (obj) {
					if (tickets > 0) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "正在出售第" + (tickets--) + "张票");
					}
				}
			}
		}
	}


	private  void sellTicket() {
			if (tickets > 0) {
			try {
					Thread.sleep(100);
			} catch (InterruptedException e) {
					e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()
						+ "正在出售第" + (tickets--) + "张票 ");
			}
		}

(2)如果一个方法一进去就看到了代码被同步,就能把同步加在方法上。
格式:把同步关键字加在方法上
这时的锁对象:this

	public class SellTicket implements Runnable {
		// 定义100张票
		private int tickets = 100;
		//创建锁对象,得是同一个锁对象,这个对象我们可以认为是任意一个对象,同步方法锁对象是 this
	//	private Object obj = new Object();
	
		@Override
		public void run() {
			while (true) {
				synchronized (this) {
					if (tickets > 0) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "正在出售第" + (tickets--) + "张票");
					}
				}
			}
		}
	}

	// 同步方法
	private  synchronized void sellTicket() {
			if (tickets > 0) {
			try {
					Thread.sleep(100);
			} catch (InterruptedException e) {
					e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()
						+ "正在出售第" + (tickets--) + "张票 ");
			}
		}

(3)如果方法是静态方法,锁对象:类的class对象(当前类.class)

	public class SellTicket implements Runnable {
		// 定义100张票
		private  static int tickets = 100;
		//创建锁对象,得是同一个锁对象,这个对象我们可以认为是任意一个对象,同步方法锁对象是 this
	//	private Object obj = new Object();
	
		@Override
		public void run() {
			while (true) {
				synchronized (SellTicket.class) {
					if (tickets > 0) {
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println(Thread.currentThread().getName()
								+ "正在出售第" + (tickets--) + "张票");
					}
				}
			}
		}
	}

	// 同步方法
	private static synchronized void sellTicket() {
			if (tickets > 0) {
			try {
					Thread.sleep(100);
			} catch (InterruptedException e) {
					e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()
						+ "正在出售第" + (tickets--) + "张票 ");
			}
		}

同步的前提

程序中需要多个线程
多个线程使用的是同一个锁对象

同步的好处

同步的出现解决了多线程的安全问题。

同步的弊端

当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

4.死锁问题

是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象

	public class DieLock extends Thread {
	
		private boolean flag;	
		public DieLock(boolean flag) {
			this.flag = flag;
		}
	
		@Override
		public void run() {
			if (flag) {
				synchronized (MyLock.objA) {
					System.out.println("if objA");
					synchronized (MyLock.objB) {
						System.out.println("if objB");
					}
				}
			} else {
				synchronized (MyLock.objB) {
					System.out.println("else objB");
					synchronized (MyLock.objA) {
						System.out.println("else objA");
					}
				}
			}
		}
	}

5.协调同步的线程

在这里插入图片描述

6.线程间通信

  • 针对同一个资源的操作有不同种类的线程
    举例:卖票有进的,也有出的。
  • 通过设置线程(生产者)和获取线程(消费者)针对同一个学生对象进行操作
    在这里插入图片描述
    线程间通信的代码改进
    A:通过等待唤醒机制实现数据依次出现
    B:把同步代码块改进为同步方法实现

7.线程状态转换图

在这里插入图片描述

8.线程组

Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup()
我们也可以给线程设置分组
Thread(ThreadGroup group, Runnable target, String name)

9.线程池

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

  • 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
  • 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法

  • public static ExecutorService newCachedThreadPool()
  • public static ExecutorService newFixedThreadPool(int nThreads)
  • public static ExecutorService newSingleThreadExecutor()
  • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
    Future<?> submit(Runnable task)
    Future submit(Callable task)

10.匿名内部类方式使用多线程

  • 匿名内部类方式使用多线程
  • new Thread(){代码…}.start();
  • New Thread(new Runnable(){代码…}).start();

11.线程联合

一个线程A在占有CPU资源期间,可以让其它线程调用join()和本线程联合,如:
B.join();
称A在运行期间联合了B。如果线程A在占有CPU资源期间一旦联合B线程,那么A线程将立刻中断执行,一直等到它联合的线程B执行完毕,A线程再重新排队等待CPU资源,以便恢复执行。如果A准备联合的B线程已经结束,那么B.join()不会产生任何效果。

12.GUI线程

当Java程序包含图形用户界面(GUI)时,Java虚拟机在运行应用程序时会自动启动更多的线程,其中有两个重要的线程:AWT-EventQuecueAWT-Windows。AWT-EventQuecue线程负责处理GUI事件,AWT-Windows线程负责将窗体或组件绘制到桌面。JVM要保证各个线程都有使用CPU资源的机会,比如,程序中发生GUI界面事件时,JVM就会将CPU资源切换给AWT-EventQuecue线程,AWT-EventQuecue线程就会来处理这个事件,比如,你单击了程序中的按钮,触发ActionEvent事件,AWT-EventQuecue线程就立刻排队等候执行处理事件的代码

13.计时器线程

  • 定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能

  • Timer

    • public Timer()
    • public void schedule(TimerTask task, long delay)
    • public void schedule(TimerTask task,long delay,long period)
  • TimerTask

    • public abstract void run()
    • public boolean cancel()

14.守护线程

一个线程调用void setDaemon(boolean on)方法可以将自己设置成一个守护(Daemon)线程,例如:
thread.setDaemon(true);
当程序中的所有用户线程都已结束运行时,即使守护线程的run方法中还有需要执行的语句,守护线程也立刻结束运行。

内容有点多了,过两天回家,祈祷自己回家还学得进去

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值