黑马程序员—java基础—多线程

本文详细介绍了Java多线程的基础知识,包括进程与线程的概念、多线程的意义及其实现方式,通过具体案例深入浅出地讲解了线程同步、锁机制、线程池等关键技术。

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

 

多线程是java基础里一块非常重要的知识点

1.之前我们的程序都是从上至下,一行一行执行;
2.很明显的特征就是:下面的代码总要等待上面的代码执行完毕,才能获得执行;
  这种程序只有一条执行路径,被称为:单线程程序;
一.进程:
   1.什么是进程:它是"操作系统"中的概念;对于操作系统来说,每个单独运行的"程序"就是一个"进程";
   2.什么是多进程:是指操作系统可以同时维护多个应用程序的同时运行;统一分配资源;
   3.多进程的意义:
   1).充分的利用系统资源;
   2).可以提高用户的体验度;使用户有非常好的使用体验;
二.线程:
   1.什么是线程:线程是指,由一个"进程"中启动的一个可以独立运行的代码块;它是某个进程的一部分;
             线程可以跟"主进程"一同抢夺系统资源;一个主进程可以开出任意多的线程;
   2.什么是多线程:是指一个程序可以开出多个"线程"同时执行;
   3.多线程的意义:
     1).充分的利用系统资源;
     2).对于一个软件来说,就好像同时在有多个功能在一起运行;
三.多线程程序和单线程程序:
   1.单线程程序:只有一条执行路径;后面的代码必须等待前面的代码执行完毕才能获得执行;
   2.多线程程序:可以有多条执行路径,就好像多个功能在同时运行;
四.并行和并发:
   1.并行:是指两个线程在"某个时间段内",同时在运行;
   2.并发:是指多个线程在"某个时间点上",同时访问同一资源;
实现多线程的方式1:

实现线程的方式一:Java中的线程,使用:Thread类表示;

1.自定义线程类,继承自Thread;
2.重写run();
3.启动线程:
  1).实例化自定义线程类对象;
  2).调用线程对象的start()方法启动线程;

注意:
 1.对于同一个Thread(包括子类)对象,不能多次调用start()方法;
 2.对于同一个Thread(包括子类)类,可以产生多个对象,每个对象都可以单独start(),
   成为一个独立的线程;

我们知道,对于同一线程类,可以实例化多个对象,同时start()作为线程运行;
由于每个线程内的代码都是一样的

1.实际上,每个线程,都有一个"线程名称";默认名称:Thread-(索引)
  getName():获取线程名;
  setName(String n):设置线程名称;
 
2.我们可以在任何的线程中使用:Thread.currentThread()获取当前正在运行的线程对象;并调用getName()即可以获取此线程对象的线程名称;
案例:线程类:

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

测试类:


public class Demo {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		
		t1.setName("张学友");
		t2.setName("章子怡");
		t3.setName("周海媚");

		t1.start();
		t2.start();
		t3.start();
		for(int k = 0;k < 100 ; k++){
			System.out.println( Thread.currentThread().getName() + "k = " + k);
		}
		
	}
}

线程的优先级:

线程的优先级:

1.Java中线程优先级的范围:1--10(从低到高);
2.设置优先级:
  getPriority():获取优先级;
  setPriority():设置优先级;(范围一定从1--10)
3.注意:我们不要依赖于"线程优先级"的技术,期望某个线程先执行完毕;因为它不保证线程优先级高的
      就一定先执行完毕,这个要由"操作系统"来统一调度;"高优先级"的线程只代表:具有更多的机会
      被操作系统执行而已,并不代表一定会先执行完毕;另外,如果线程中所做的事情比较简单,线程"优先级"
      的效果会不明显;

线程类的一些方法:

线程休眠 public static void sleep(long millis):

 线程加入
public final void join():调用此方法的线程,将会保证先执行完毕,其它线程才可以被执行;

public static void yield():使调用的线程退回到"就绪"状态,等待操作系统再次分配,很有可能被操作系统再次分配到执行权;

后台线程
public final void setDaemon(boolean on):如果on为true,则表示为:守护线程

1.我们之前写的线程都是"非守护线程":当主线程执行完毕时,程序不会立即结束,会等待所有开出的线程执行完毕,主进程才结束;
2.守护线程:当主线程结束时,开出的"守护线程"也会随着终止(但是不会立即终止,会有个小缓冲)

中断线程
public final void stop():不建议使用
public void interrupt():前提条件:线程的内部,一定要处于以下三种阻塞状态时:
      1.Object-->wait()
      2.Thread-->sleep()
      3.Thread-->yield()
      
      当在外部调用线程的interrupt()方法时,将促使上述方法抛出异常;

实现线程的方式2:

 * 实现线程的方式二:
 * 
 * 1.自定义类,实现:Runnable接口;
 * 2.重写run()方法;
 * 3.启动线程:
 * 		1).实例化自定义类对象;
 * 		2).实例化一个Thread对象,并将自定义对象传递给Thread的构造方法;
 * 		3).调用Thread对象的start()方法启动线程;
 */
public class Demo {
	public static void main(String[] args) {
		MyRunnable myRun = new MyRunnable();
		Thread t = new Thread(myRun);
		t.start();//启动线程
		for(int j = 0;j < 100 ;j++){
			System.out.println("j = " + j);
		}
	}
}

线程类:

public class MyRunnable implements Runnable{
	@Override
	public void run() {
		for(int i = 0;i < 100 ;i++){
			System.out.println(Thread.currentThread().getName() + " i = " + i);
		}
	}
}

两种方式的对比:
方式一:由于方式一需要"继承",而Java中只能单继承。子类将不能再继承其它类了,这就对子类形成了一种限制;
方式二:使用"接口"的方式,重写run()方法。Java中的子类可以同时实现多个接口;

经典案例,Thread实现卖票程序:

测试类:

public class Demo {
	public static void main(String[] args) {
		//1.需要一个票池对象
		TicketPool tp = new TicketPool();
		
		//2.需要三个线程对象,模拟三个窗口售票
		MyThread t1 = new MyThread(tp);
		MyThread t2 = new MyThread(tp);
		MyThread t3 = new MyThread(tp);
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
		
	}
}

抢票线程:

public class MyThread extends Thread{
	private TicketPool tp = null;
	private int count = 0;
	public MyThread(TicketPool p){
		this.tp = p;
	}
	@Override
	public void run() {
		while(true){
			int n = this.tp.getTicket();
			if(n == 0){//没票了
				break;
			}else{
				count++;
				System.out.println(this.getName() + " 抢到票:" + n);
			}
		}
		System.out.println(this.getName() + " 停止抢票了! 共抢到:" + count + "张票");
	}
}

票池类:

public class TicketPool {
	private int tickets = 100;
	
	//获取一张票
	public int getTicket(){//窗口1,窗口2
		
		if(this.tickets > 0 ){//窗口1,窗口2
			return tickets--;
		}else{
			return 0;
		}
	}
}

由于线程并发访问一个票池对象,会造成数据失真。于是,需要对共享数据加锁,才能保证结果的数字准确:对共享数据加锁的方式是使用synchronized 关键字,上锁的代码块也叫同步代码块

public class TicketPool {
	private int tickets = 100;
	//获取一张票
	public int getTicket(){//窗口2
		//同步代码块
		synchronized (this) {//窗口1(锁)
			if(this.tickets > 0 ){
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				return tickets--;
			}else{
				return 0;
			}
		}
		
	}
}

同步的好处和弊端:

同步的语法:
  1. 同步代码块
   synchronized(被锁的对象){
    //同步的代码
   }
     被锁的对象:表示如果当前线程访问"被锁对象"的synchronized的代码块时,其它线程
               不能访问此代码块,另外,也不能访问"被锁对象"中的其它synchronized的代码块;
     2.同步的好处:解决了多线程并发访问的问题,使多线程并发访问的共享数据能够保持一致性;
     3.同步的弊端:
      1).使当前的对象的工作效率降低;因为要处理线程同步的问题;

经典案例:银行账户存取款问题

/*
 * 同步代码的形式:
 * 
 * 1.同步代码块:
 * 		synchronized(被锁的对象){
 * 			//同步的代码
 * 		}
 * 2.同步的方法:(比较常见的方式)(默认锁的就是当前对象)
 * 		public synchronized void setMoney(int m){
 * 			//同步的代码
 * 		}
 * 3.静态方法能否包含同步代码块?可以,一般"锁的对象"是某个类的Class对象;
 * 4.静态方法是否可以被声明为同步的?可以的。
 */
public class Demo {
	public static void main(String[] args) {
		Account acc = new Account();
		
		SetThread setThread = new SetThread(acc);
		GetThread getThread = new GetThread(acc);
		
		setThread.start();
		getThread.start();
		
		System.out.println("主线程等待1秒......");//确保存钱、取钱两个线程执行完毕
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("主线程醒来......");
		System.out.println("最终余额:" + acc.getBalance());
	}
}
public class Account {
	private int balance = 10000;

	//同步方法
	public synchronized void setMoney(int m){
		this.balance += m;
	}
	
	public synchronized void getMoney(int m){
		
		this.balance -= m;
		
	}
	
	public int getBalance(){
		return this.balance;
	}
	
	//静态方法能否包含同步代码块?可以,一般"锁的对象"是某个类的Class对象;
	public static void show(){
		synchronized (Account.class) {
			System.out.println("静态方法中的同步代码块");
		}
	}
	//静态方法是否可以被声明为同步的?可以的。
	public synchronized static void show2(){
		
	}
}

 

public class GetThread extends Thread{
	private Account acc;
	public GetThread(Account acc){
		this.acc = acc;
	}
	
	public void run() {
		for(int i = 0;i < 1000 ;i++){
			this.acc.getMoney(1000);
		}
	}
}
public class SetThread extends Thread {
	private Account acc;
	public SetThread(Account acc){
		this.acc = acc;
	}
	@Override
	public void run() {
		for(int i = 0;i < 1000 ; i++){
			acc.setMoney(1000);
		}
	}
}

lock锁:

1.在JDK5之前我们使用synchronized定义同步代码块,同步方法;这是一种锁的方式;
2.在JDK5之后,出现了一种新的锁的方式,需要使用的:Lock(接口),Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
3.使用方式:JDK的帮助文件中:
    Lock l = ...;
       l.lock();//加锁
      try {
          // access the resource protected by this lock
      } finally {
          l.unlock();//释放锁
线程死锁:

 

单生产单消费案例:

1.共享资源:包子铺
2.生产线程:SetThread-->添加包子
3.消费线程:GetThread-->买包子

由于两个线程无序访问,所以,很可能"消费者"会得到一个null值,这不是我们想看到的。
我们希望做到的:不论是否有包子,消费者都能得到一个包子;

1.在包子铺:获取包子的方法:getBaozi(),判断,如果没有包子了,让消费者"等待(Object-->wait())"
         设置包子的方法:setBaozi(),没设置一个包子,都会"唤醒(Object-->notifyAll()/notify())"所有等待的线程;
2.注意:
  1).此例只使用与"单生产"与"单消费",不适用"多生产"和"多消费";
  2).wait()方法和notify()/notifyAll()方法的调用,一定要在"同步方法/代码块"内,否则会抛异常;

public class Demo {
	public static void main(String[] args) {
		BaoZiPu bzp = new BaoZiPu();
		
		SetThread set = new SetThread(bzp);
		GetThread get = new GetThread(bzp);
		
		set.start();
		get.start();
		
	}
}

 

import java.util.ArrayList;

public class BaoZiPu {
	private ArrayList<String> bzList = new ArrayList<>();
	
	//添加包子
	public synchronized void setBaozi(String s){
		this.bzList.add(s);
		//唤醒所有等待的线程
		notifyAll();//或者notify()
	}
	//获取包子
	public synchronized String getBaozi(){
		if(this.bzList.size() <= 0){//锅里面没有包子了
			//让当前访问的线程等待
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//取第一个包子
		String s = bzList.get(0);
		//移除第一个包子
		bzList.remove(0);
		//返回
		return s;
	}
}

 

public class GetThread extends Thread{
	private BaoZiPu bzp;
	public GetThread(BaoZiPu b){
		this.bzp = b;
	}
	@Override
	public void run() {
		while(true){
			String s = bzp.getBaozi();
			System.out.println("我得到了一个:" + s);
		}
	}
}
public class SetThread extends Thread{
	private BaoZiPu bzp ;
	public SetThread(BaoZiPu bzp){
		this.bzp = bzp;
	}
	public void run() {
		while(true){
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			bzp.setBaozi("包子");
		}
	}
}

匿名内部类的方式实现多线程:

1.new Thread(){匿名的Thread子类};
2.new Thread(匿名的Runnable的子类){};
3.new Thread(匿名的Runnable的子类){匿名的Thread子类};

线程组:

1.线程组就是将一些线程统一分组管理;
2.分组后,可以对组内的所有线程做统一的操作:比如:停止线程;
3.线程组:使用:ThreadGroup
4.在Thread类中:
  ThreadGroup getThreadGroup():获取线程所在的线程组;
5.一个线程,在默认情况下,都属于"main"线程组;
6.我们可以自行设定线程组:
  1).实例化一个ThreadGroup对象;
  2).使用Thread的构造方法,指定线程组和相应的线程:
   Thread(ThreadGroup group, Runnable target) 分配新的 Thread 对象。
7.统一停止线程组内的所有线程:
  ThreadGroup-->interrupt():

线程池:

1.我们知道,对于一个线程对象,不能反复的调用start()再次运行;
  要想再次运行,就要再次创建线程对象,如果创建线程对象比较耗时;
2.所以,我们期望将一些线程对象缓存起来,可以被多次的反复调用;
3.JDK5提供了一种线程池:
  JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
  public static ExecutorService newCachedThreadPool():创建一个可根据需要创建新线程的线程池
  public static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用固定线程数的线程池
  public static ExecutorService newSingleThreadExecutor():创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
 返回值:ExecutorService--线程池
   执行线程,并缓存线程对象:
   Future<?> submit(Runnable task)
  <T> Future<T> submit(Callable<T> task)

public class Demo {
	public static void main(String[] args) {
		/*MyThread t1 = new MyThread();//5秒
		t1.start();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("想再次运行线程......");
		
		t1 = new MyThread();//5秒
		t1.start();*/
		
		MyThread t1 = new MyThread();
		//获取一个线程池
		ExecutorService service = Executors.newFixedThreadPool(2);
		service.submit(t1);
		System.out.println("主线程休息2秒钟......");
		try {
			Thread.sleep(1000 * 2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("主线程醒来,再次调用线程......");
		service.submit(t1);
		
	}
}


多线程实现方式之3:

回顾之前实现线程的方式:
  1).继承Thread;
  2).实现Runnable接口
今天: 3).
   1>.实现:Callable接口;
   2>.重写call()方法
   3>.启动线程:
    1.获取线程池;
    2.调用线程池的submit()方法启动并缓存线程;

public class Demo {
	public static void main(String[] args) {
		//1.获取线程池
		ExecutorService service = Executors.newFixedThreadPool(2);
		//2.调用线程池的submit()方法执行线程
		MyCallable myCall = new MyCallable();
		service.submit(myCall);
		for(int k = 0 ; k < 100 ; k++){
			System.out.println("k = " + k);
		}
		service.shutdown();
	}
}
import java.util.concurrent.Callable;

public class MyCallable implements Callable{
	@Override
	public Object call() throws Exception {
		for(int i = 0;i < 100 ; i++){
			System.out.println("i = " + i);
		}
		return null;
	}

}

定时器:

import java.util.Timer;
import java.util.TimerTask;

/*
 * Java中的定时器:
 * 
 * 1.可以在指定的时间,开始做某件事情;
 * 2.可以从指定的时间开始,并且间隔多长时间,会重复的做某件事情;
 * 
 * 实现定时器:
 * 1.java.util.TimerTask(抽象类):定义执行的任务
 * 		1).自定义类,继承自TimerTask;
 * 		2).重写run()方法;(将要执行的任务定义在这里)
 * 2.java.util.Timer(类):计时,并启动任务;
 * 		构造方法:
 * 			Timer();
 * 		成员方法:
 * 			public void schedule(TimerTask task, long delay):在指定的delay延迟之后,启动任务task;
 * 			public void schedule(TimerTask task,long delay,long period):在指定的延迟之后,启动任务,并间隔指定时间重复的执行任务;
 */
class MyTimerTask extends TimerTask{
	private Timer timer;
	public MyTimerTask(Timer t){
		this.timer = t;
	}
	@Override
	public void run() {
		System.out.println("duang......");
	//	this.timer.cancel();//如果重复执行,这里就不能取消了;
	}
}
public class Demo {
	public static void main(String[] args) {
		Timer timer = new Timer();
		System.out.println("3秒后,开始执行,之后每隔1秒重复执行一次......");
	//	timer.schedule(new MyTimerTask(timer), 1000 * 3);
		timer.schedule(new MyTimerTask(timer), 1000 * 3 , 1000 );
		
	}
}


Sleep 和wait的区别:1.sleep():
  1).Thread类中定义的;
  2).都要指定时间;当休眠时间到时,会自动醒来;
  3).在同步方法中,不会释放锁;
2.wait():
  1).Object类中定义的;
  2).可以指定时间,也可以不指定时间;如果指定时间,当时间到时,会自动醒来;
        如果不指定时间,使用notify()或notifyAll()唤醒;
  3).在同步方法中,会释放锁;

设计模式之简单工厂模式:

简单工厂模式:
1.目的:让前端与后端的类脱离,不需要直接实例化后端类的对象;
2.简单工厂模式,需要单独建立一个类,叫:工厂类,这个工厂类中会提供一些方法来获取具体的对象;
简单工厂模式之核心代码:

public class Factory {
	//*********简单工厂模式:方式一***************//
	public static Cat getCat(){
		return new Cat();
	}
	public static Dog getDog(){
		return new Dog();
	}
	
	//*********简单工厂模式:方式二***************//
	public static Object getAnimal(String type){
		if(type.equals("猫")){
			return new Cat();
		}else if(type.equals("狗")){
			return new Dog();
		}
		return null;
	}
}

工厂方法简述:

工厂方法模式的概述和使用:

1.之前的"简单工厂模式"的弊端:如果增加新产品,就要修改工厂类的源码;
2.使用"工厂方法模式",可以在增加新产品的同时,不需要修改任何代码:
		1).首先要先定义两个接口:
			1>.产品:IAnimal
			2>.工厂:IFactory

工厂方法模式:
优点:增加新产品时,不需要更改源码;
弊端:创建的类太多;

单例模式:

1.单例:例:实例(对象),单:一个;单例:一个对象;
2.在整个应用程序运行期间,有些类只需要一个对象即可,这时可以将这个类设计为:单例模式;
例如:整个应用程序只需要一个Cat对象,表示:波斯猫。
    此时需要将Cat类设计为单例模式;
   
步骤:
1.Cat类的对象,不能任意的实例化;将Cat类的构造方法私有化;
2.提供一个私有的、静态本类对象的引用;
3.提供一个公有的、静态的方法,返回本类对象的引用;直接在本类实例化一个自己的对象,这种叫做饿汉式

public class Cat {
	private static final Cat cat = new Cat("波斯猫");
	String name;
	
	private Cat(String s){
		this.name = s;
	}
	
	public static Cat getCat(){
		return cat;
	}
}


单例模式:懒汉式

/*
 * 懒汉式与饿汉式的区别:
 * 
 * 相同点:
 * 1.将构造方法私有化;
 * 不同点:
 * 1.饿汉式:定义成员变量时,就直接实例化对象;
 *   懒汉式:定义成员变量时,不实例化对象;
 * 2.饿汉式:方法内,直接返回引用;
 *   懒汉式:方法内,先判断,再返回引用;
 */
public class Demo {
	public static void main(String[] args) {
		Cat c1 = Cat.getCat();//第一次访问,getCat()方内部会实例化一个Cat对象
		Cat c2 = Cat.getCat();//第二次访问,getCat()方法直接return成员属性
		
	}
}
public class Cat {
	private static  Cat cat = null ;
	private Cat(){
	}
	
	public synchronized static Cat getCat(){
		if(cat == null){
			cat = new Cat();
		}
		return cat;
	}
}	


多线程最后一个知识点:Runtime类

java.lang.Runtime:每个 Java 应用程序都有"一个" Runtime类实例,使应用程序能够与其运行的环境相连接。
       可以通过 getRuntime 方法获取当前运行时。

public class Demo {
	public static void main(String[] args) {
		Runtime rt = Runtime.getRuntime();
		try {
			rt.exec("notepad");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
}


执行的是打开记事本操作

 

多线程部分思维导图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值