多线程讲解

作者:gqk


1,本次预计讲解的知识点:

1、 多线程的操作中,对于线程功能的开发并不要求,但是其基本概念及各个操作语句必须熟练

2、 掌握多线程的两种实现方式及区别

3、 了解多线程的主要操作方法

4、 掌握线程的同步与死锁的概念

 


 

2、具体内容:

2.1、进程与线程(了解)

  传统的 DOS 系统有一个很明显的特点,一旦程序中出现了病毒的话,则整个电脑将处于瘫痪的状态,这是因为传统 的 DOS 操作系统采用的是单进程的处理方式,即:在同一个时间段上只能有一个进程在运行着。

  到了 windows 时代可以发现电脑中即使有了病毒也照样可以使用。因为 windows 本身属于多进程的操作系统,可以 在同一个时间段上运行多个程序,所有的程序都是并发运行的,但是在同一个时间点上只能有一个进程在执行。

  线程实际上是在进程基础上的进一步划分,一个进程可以划分成多个线程。

举例:

  计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。等工厂供电不足时:

  进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

  一个车间里,可以有很多工人。他们协同完成一个任务。线程就好比车间里的工人。一个进程可以包括多个线程,车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存

操作系统的设计,因此可以归结为三点:

(1)以多进程形式,允许多个任务同时运行;

(2)以多线程形式,允许单个任务分成不同的部分运行;

(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

2.2、Java 的线程实现:

在 Java 中如果要想进行多线程代码的实现有两种方式:
  1,继承 Thread 类 
  2,实现 Runnable接口  下面通过代码分别来验证以上的两种方式及区别。

 2.2.1、继承 Thread 类:

 当一个类需要按照多线程的方式处理时,可以让这个类直接继承自 Thread 类即可,而且继承的时候要覆写好 Thread 类中提供的run()方法:

public void run(){} 

  范例:按照要求定义一个线程类

package com.xkrj.gqk;

public class MyThread extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("i=="+i);
		}
	}
}

 如果 要想启动一个线程并不是依靠run()方法而是 start()方法。

package com.xkrj.gqk;

public class MyThread extends Thread{
	private String name;
	public MyThread(String name){
		this.name = name;
	}
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName()+"i=="+i);
		}
	}
	public static void main(String[] args) {
		MyThread thread1 = new MyThread("线程A");
		MyThread thread2 = new MyThread("线程B");
		MyThread thread3 = new MyThread("线程C");
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

 那个线程抢到了 CPU 资源,那个线程就执行:

 2.2.2、实现 Runnable 接口 :

  

package com.xkrj.gqk;

public class MyThread implements Runnable{
	private String name;
	public MyThread(String name){
		this.name = name;
	}
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.name+"运行,i="+i);
		}
		
	}
	
}

  调用线程:观察Thread的构造方法:

 此时,通过 Thread 类进行了线程的启动。

package com.xkrj.gqk;

public class MyThread implements Runnable{
	private String name;
	public MyThread(String name){
		this.name = name;
	}
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.name+"运行,i="+i);
		}
		
	}
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程 A") ;
		MyThread mt2 = new MyThread("线程 B") ; 
		MyThread mt3 = new MyThread("线程 C") ; 
		Thread t1 = new Thread(mt1);
		Thread t2 = new Thread(mt2);
		Thread t3 = new Thread(mt3);
		t1.start();
		t2.start();
		t3.start();
	}
	
}

 2.2.3、两种实现方式的区别:

对于 Thread类和 Runnable 接口本身都是可以进行多线程的实现,那么两者到底该使用谁更好呢?

 1,继承局限:使用 Runnable接口可以避免单继承的局限,而 Thread 类则有此局限; 

 2,资源共享:使用 Runnable 接口实现多线程,可以实现资源(对象属性)的共享,而 Thread 类却无法实现 

 范例:观察资源共享

继承Thread类资源不共享

现在的程序中每一个线程都各自占有各自的 count 属性,所以并没有达到资源共享的目的

package com.xkrj.gqk;

public class MyThread extends Thread{
	private int count = 5;
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("count===="+this.count--);
		}
		
	}
	public static void main(String[] args) {
		new MyThread().start();
		new MyThread().start();
		new MyThread().start();
	}
	
}

 实现Runable接口资源共享:

package com.xkrj.gqk;

public class MyThread implements Runnable{
	private int count = 5;
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName()+"count===="+this.count--);
		}
		
	}
	public static void main(String[] args) {
		MyThread mt = new MyThread();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start();
	
	}
	
}

 2.2.4、线程的状态:

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.3、线程的操作方法(理解)

3.3.1、命名和取得 

每一个线程实际上都可以为其设置名字,而且也可以取得每一个线程的名字:   ·
 设置线程名称:public final void setName(String name)   ·
 取得线程名称:public final String getName()  但是有一点也非常的麻烦,由于线程的操作不属于固定的状态,所以对于取得线程名称的操作,应该是取得的当前 正在运行的线程名称才合适,那么可以通过如下的方法取得一个当前正在运行的线程对象:   ·
 取得当前线程:public static Thread currentThread()  除了以上的设置名称的方法外,在 Thread 类中也提供了两个构造方法:   · public Thread(String name)   · public Thread(Runnable target,String name)  注意:一般都在线程启动前设置好名字,当然也可以为已经启动的线程修改名字或设置重名线程,不过这样不好。

那么既然是多线程的处理机制,实际上主方法是在一个 JVM 上产生的一个线程而已,那么一个 JVM 启动的时候至 少启动几个线程呢?两个:main、GC。

3.3.2、线程的休眠(重点)

漫天繁星

package com.xkrj.gqk;

import java.awt.Color;
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class MyThread extends JPanel  implements Runnable{
	@Override
	public void run() {
		while(true){
			try {
				Thread.sleep(1000);//让图像休眠x毫秒

			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			repaint();//重新画出图片
		}
	}
	@Override
	public void paint(Graphics g) {
		super.paint(g);
		System.out.println("11111");
		g.setColor(Color.BLACK);//给下面的矩形区域上颜色
		/*g.fillRect(0, 0,1000, 1000);*/
		for(int i=1;i<=250;i++){
			 g.setColor(Color.white);//给星星上颜色
			 int x=(int )(Math.random()*1000);//星星在屏幕上的位置坐标取随机数
			 int y=(int )(Math.random()*1000);
			 g.drawString("*", x, y);//调用这个函数将星星画在画板上
		}
		 g.fillOval(650, 200,75,75);//画一个黑圆,一个白园相交成一个白色的月亮
		 g.setColor(Color.BLACK);
		 g.fillOval(675, 200, 75, 75);
	}
	
	public static void main(String[] args) {
		JFrame jf = new JFrame("漫天繁星");
		jf.setSize(1000, 1000);
		jf.setLocation(300, 0);
		MyThread m = new MyThread();
		m.setBackground(Color.black);
		jf.add(m);
		Thread t = new Thread(m);
		t.start();
		jf.setVisible(true);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
	
}

 3.3.3、线程的优先级 

 实际上所有的线程启动之后并不是立刻运行的,都需要等待 CPU 进行调度,但是调度的时候本身也是存在“优先” 级的,如果优先级高则有可能最先被执行。  如果要想设置优先级可以使用:public final void setPriority(int newPriority)  这个优先级需要接收一个整型的数字,这个数字只能设置三个内容:
 最 高优先级:public static final int MAX_PRIORITY  
 中 等 优先级:public static final int NORM_PRIORITY  
 最 低优先级:public static final int MIN_PRIORITY 

 3.4、线程的同步与死锁(重点)

由于多个线程可以对同一个资源进行操作,那么就必须进行同步的处理,但是如果过多的引入了同步的处理,也可 能造成死锁

当多个线程同时进行一种资源操作,为了保证操作的完整性,引入了同步处理

package com.xkrj.gqk;


public class MyThread  implements Runnable{
	private int tickets = 10;//定义票数
	
	public static void main(String[] args) {
		MyThread m = new MyThread();
		new Thread(m,"西安").start();
		new Thread(m,"咸阳").start();
		new Thread(m,"宝鸡").start();
	}

	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			if(this.tickets>0){
				System.out.println(Thread.currentThread().getName()+"卖票,剩余:"+this.tickets--);
			}
		}
	}
	
}

  但是,从实际的操作来看,都是使用网络卖票,既然是网络卖票的话,则有可能出现延迟

本程序中可以发现一旦加入了延迟(不加延迟也可能有问题)之后发现有的人卖出的票数是负数。  由于现在是有多个操作,那么最好的解决方法是加入一个锁的标记,锁的标记中将判断和修改同时进行,要想完成 这种锁的程序可以通过两种语句实现:同步代码块、同步方法。

 

package com.xkrj.gqk;


public class MyThread  implements Runnable{
	private int tickets = 10;//定义票数
	
	public static void main(String[] args) {
		MyThread m = new MyThread();
		new Thread(m,"西安").start();
		new Thread(m,"咸阳").start();
		new Thread(m,"宝鸡").start();
	}

	@Override
	public void run() {
		for (int i = 1; i < 20; i++) {
			synchronized (this) {//将当前对象锁定
			if(this.tickets>0){
				try {
					Thread.sleep(100);
					System.out.println(Thread.currentThread().getName()+"卖票,剩余:"+this.tickets--);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
			}
		}
	}
	
}

  同步方法也可以完成:

package com.xkrj.gqk;


public class MyThread  implements Runnable{
	private int tickets = 10;//定义票数
	
	public static void main(String[] args) {
		MyThread m = new MyThread();
		new Thread(m,"西安").start();
		new Thread(m,"咸阳").start();
		new Thread(m,"宝鸡").start();
	}

	@Override
	public void run() {
		for (int i = 1; i < 20; i++) {
			this.sale();
		}
	}
	public synchronized void sale(){
		if(this.tickets>0){
			try {
				Thread.sleep(300);
				System.out.println(Thread.currentThread().getName()+"卖票,剩余:"+this.tickets--);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}

 

posted @ 2018-10-12 14:06  少侠gqk  阅读(594)  评论(0编辑  收藏  举报