黑马程序员——多线程

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

 

一、多线程——线程相关概念

1基本概念

A线程:是依赖于进程的执行绪(执行路径/控制单元),是程序使用CPU的基本单位。

B进程:当前正在执行的程序,代表一个应用程序在内存中的执行区域。

C多进程:同一时间段内执行多个任务。同一时刻只能执行一个任务。如Windows为代表的操作系统。

多进程并不提高某个程序的执行速度,仅仅是提高了CPU的使用率。真正的多进程执行是指多核同时计算。

D单线程:一个进程中,只有一个线程执行。

E多线程:同一个进程中,多个线程执行。这多个线程共享该进程资源(堆内存与方法区),单栈内存独立,即每一个线程占用一个栈。

 

2线程两种调度模型:

A分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片。

B抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个。(线程随机性)

CJava使用的为抢占调度模型。

3线程并行与线程并发

A线程并行:正常的多线程执行就是线程并行。即逻辑上同一时间同时运行。

B线程并发:由于线程抢占而不应出现的某一时刻的线程及相关数据状态。如并发修改异常的产生。

4JVM的启动支持多线程:

JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程”,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。

5、线程生命周期

二、多线程——多线程实现方式

1多线程实现方式

方式一:继承Thread

自定义线程类继承Thread类。

重写run方法。run方法内为该线程执行代码。将其理解为其他线程的main方法,即该线程的执行入口。

A使用:

创建线程对象

开启线程,即调用start方法,该方法会自动调用这个线程的run方法。

B、代码体现:

new Thread() {
	public void run() {
		for(int x=0; x<100; x++) {
			System.out.println(x);
		}
	}
}.start();

方式二:实现Runnable接口

自定义Runnable 的子类(非线程类)

重写run方法。run方法内为该类对象所在线程的执行代码。同样可将其理解为其他线程的main方法,即该线程的执行入口。

A使用:

创建Runnable的子类对象。

使用Runnable的子类对象创建线程对象。

开启线程,即调用start方法,该方法会自动调用这个线程的run方法。

B、代码体现:

new Thread(new Runnable(){
	public void run() {
		for(int x=0; x<100; x++) {
			System.out.println(x);
		}
	}
}).start();

2方式一与方式二的区别

方式一:

A当类去描述事物,事物中有属性和行为。如果行为中有部分代码需要被多线程所执行,同时还在操作属性。就需要该类继承Thread类,产生该类的对象作为线程对象。可是这样做会导致每一个对象中都存储一份属性数据。无法在多个线程中共享该数据。加上静态,虽然实现了共享但是生命周期过长。

B如果一个类明确了自己的父类,那么它就不可以再继承Thread。因为java不允许类的多继承。

方式二:

A将线程与运行的业务逻辑分离,可以让多个线程共享业务逻辑中的数据。

B可以让业务类不再继承Thread而专注于业务继承其他类,避免了单继承的局限性。

三、多线程——线程优先级

线程优先级代表了抢占CPU的能力。优先级越高,抢到CPU执行的可能性越大。(一般环境下效果不明显,优先级并非绝对的执行顺序。)

优先级相关方法:

public final void setPriority(int newPriority) 优先级取值:1-10

public final int getPriority()

 

四、多线程——线程休眠

指定线程休眠一定时间,进入等待状态。在该段时间结束后,线程重新可执行。

线程休眠相关方法:

public static void sleep(long millis) throws InterruptedException

 

五、多线程——加入线程

等待某加入的线程终止后再执行。

加入线程相关方法:

public final void join() throws InterruptedException

 

六、多线程——线程礼让

暂停当前正在执行的线程对象,并执行其他线程。

线程礼让相关方法:

public static void yield()

 

七、多线程——守护线程

将该线程标记为守护线程。被守护的线程执行完毕时,程序即停止运行。守护线程执行完毕不影响被守护线程。(如:坦克大战)

注意:必须在线程开启前设置守护线程。

守护线程设置相关方法:

public final void setDaemon(boolean on)

 

八、多线程——线程中断

中断线程。

中断线程相关方法:

public void interrupt()   被中断的线程会报被中断异常,这时需要使用try/catch语句解决相关问题,线程后代码仍然可以继续执行

public final void stop()  (已过时)直接停止线程,线程后代码无法被执行

 

 

九、多线程——简单线程安全问题

在多个线程同时运行时发生的异常情况统称为线程安全问题。

线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

1产生原因&前提:

A线程随机访问性

B有多个线程并行

C多个线程有共享数据

D多个线程操作了共享数据

2处理方式:

使用java提供的同步机制,使某一线程的完整动作执行完毕,其他线程再进行操作

 

3Java同步机制:为解决同步问题而提供的工具

A原子性操作:

在执行操作时,我们把一个完整动作可以称为一个原子性操作,是一个不可切割的动作。即不可被线程打断的操作。

Bsynchronized 关键字:

a同步代码块格式:

       synchronized(锁对象){//该对象可以是任意对象

                需要同步的代码;

       }

b锁:几个线程需要使用相同的锁对象进行同步操作,使用不同的锁是无法完成同步操作的。

cSynchronized内需要同步的代码即为一个原子性操作。

d同步方法:方法上声明,将所在对象作为默认锁,即this

e同步静态方法:将方法所在类作为默认锁,即XX.class

C优点:解决了多线程安全问题

缺点:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。对于一个简单操作,单线程速度更快。

 

案例1:模拟完成多线程卖票代码。
class Ticket implements Runnable {
	private int tickets = 100;
	private Object obj = new Object();

	@Override
	public void run() {
		while (true) {
			synchronized (obj) {
				if (tickets > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售第" + (tickets--));
				}
			}
		}
	}
}

public class TicketDemo {
	public static void main(String[] args) {
		// 创建票对象
		Ticket t = new Ticket();
		// 创建线程对象
		Thread t1 = new Thread(t, "窗口1");
		Thread t2 = new Thread(t, "窗口2");
		Thread t3 = new Thread(t, "窗口3");

		t1.start();
		t2.start();
		t3.start();
	}
}


 

十、多线程——Lock锁简介

1Lock锁可以完成代码同步的任务。

2、相较于synchronized方式,Lock锁的出现使同步操作更为灵活。无需使用限制性强的代码块。

3Lock为抽象类,需要使用其子类ReentrantLock的对象完成方法调用。

4、主要方法:

public void lock()获取锁

public void unlock() 释放锁

5、代码体现:

package thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TicketDemo2 {
//定义锁
	public static final Lock MY_LOCK = new ReentrantLock();
	public static void main(String[] args) {
		Ticket2 ticket2 = new Ticket2();
		Thread thread = new Thread(ticket2, "售票员一");
		Thread thread2 = new Thread(ticket2, "售票员二");
		thread.start();
		thread2.start();
	}
}

class Ticket2 implements Runnable {
	private int ticket = 100;
	public void run() {
		while (true) {
			//获取锁
			TicketDemo2.MY_LOCK.lock();
			try {
				Thread.sleep(10);
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (ticket > 0) {
				System.out.println(Thread.currentThread().getName() + ":"+ ticket);
				ticket--;
			}
			//释放锁
			TicketDemo2.MY_LOCK.unlock();
		}
	}
}

 

十一、多线程——线程死锁

1、在多线程的代码编辑过程中,由于考虑得不够周全,会出现死锁的情况。即:同步中,多个线程使用多把锁之间存在等待的现象。

2、原因分析:

A、线程1将锁1锁住,线程2将锁2锁住,而线程1要继续执行锁2中的代码,线程2要继续执行锁1中的代码,但是此时,两个锁均处于锁死状态。最终导致两线程相互等待,进入无限等待状态。

B、有同步代码块的嵌套动作。

3、解决方法:

不要写同步代码块嵌套。

线程死锁代码体现:

package thread;

public class DeadLockDemo {
	public static void main(String[] args) {
		DeadLock dl = new DeadLock();
		Thread thread = new Thread(dl);
		Thread thread2 = new Thread(dl);
		thread.start();
		thread2.start();
	}
}

class DeadLock implements Runnable {
	Object o1 = new Object();
	Object o2 = new Object();
	boolean flag = true;
	public void run() {
		if(flag == true) {
			synchronized(o1) {
				flag = false;
				System.out.println("if==o1");
				synchronized(o2) {
					System.out.println("if==o2");
				}
			}
		} else {
			synchronized(o2) {
				System.out.println("else==o2");
				synchronized(o1) {
					System.out.println("else==o1");
				}
			}
		}
	}
}

十二、多线程——等待唤醒机制

当出现对同一资源的生产与消费时,可以使用多线程完成对同一资源的操作。而消费者需要等待生产者生产后才能消费,生产者也需要等待消费者消费后才能生产。于是出现了生产者消费者问题。这时可以使用等待唤醒机制完成相关需求。

1、涉及方法:

涉及并非是Thread类的方法,而是Object类的两个方法:因为锁可以为共享数据本身可以是任意的对象,在runnable中进行等待唤醒当前所在线程。

2、等待:

public final void wait() throws InterruptedException

让当前线程进入等待状态,如果线程进入该状态,不唤醒或打断,不会解除等待状态。

进入等待状态时会释放锁。

3、唤醒:

public final void notify()

唤醒正在等待的线程。

继续等待之后的代码执行。

4sleepwait的区别:

sleep指定时间,wait可指定可不指定。

sleep释放执行权,不释放锁。因为一定可以醒来。

wait释放执行权与锁。

5、代码体现:

package wait.and.notify;

public class WaitAndNotify {
	public static void main(String[] args) {
		Person p = new Person();
		
		Producer product = new Producer(p);
		Consumer cumser = new Consumer(p);
		
		Thread pThread = new Thread(product);
		Thread cThread = new Thread(cumser);
		
		pThread.start();
		cThread.start();
	}
}

package wait.and.notify;
public class Person {
	private String name;
	private int age;
	public boolean flag = false;

	public Person() {
		super();
	}

	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
}


package wait.and.notify;
//生产者
public class Producer implements Runnable{
	int i = 0;
	private Person p;
	public Producer(Person p) {
		this.p = p;
	}
	@Override
	public void run() {
		while(true) {
			synchronized(p) {
				if(p.flag) {
					try {
						p.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				if(i%2==0) {
					p.setName("粮食");
					p.setAge(1);
				} else {
					p.setName("衣服");
					p.setAge(2);
				}
				p.flag = true;
				p.notify();
			}
			i++;
		}
	}
}

package wait.and.notify;
//消费者
public class Consumer implements Runnable {
	private Person p;
	public Consumer(Person p) {
		this.p = p;
	}
	@Override
	public void run() {
		while(true) {
			synchronized(p) {
				if(!p.flag) {
					try {
						p.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println(p);
				p.flag = false;
				p.notify();
			}
		}
	}
}


 

十二、多线程——线程组

1、多个线程出现时,可以将线程分组,统一处理。

2、涉及线程组类:ThreadGroup

3、注意:

线程组可以包含线程或者线程组。

当前线程只能访问所在线程组或者子组。不能访问父组或者兄弟组。

如果没有指定线程组,则属于main线程组。

4、主要方法:

public final String getName()

public final void setDaemon(boolean daemon)

无线程添加方法,设置线程组在Thread构造方法中。

 

十三、多线程——线程池

1、将线程放置到同一个线程池中,其中的线程可以反复使用,无需反复创建线程而消耗过多资源。

线程池的出现降低了资源消耗。

线程池可以控制线程的某些行为,如销毁线程。

2Executors:线程池创建工厂类

返回线程池方法:

public static ExecutorService newXXXThreadPool(int n)

3ExecutorService:线程池类

A、主要方法:

Future<?> submit(Runnable task)

<T> Future<T> submit(Callable<T> task)

void shutdown()

B、注意:线程池提交方法后,程序并不终止,是因为线程池控制了线程的关闭等。

CCallable:相当于有返回值类型的runnable

DFuture是将结果抽象的接口。可以将线程执行方法的返回值返回。

 

十四、多线程——定时器

1Timer类:定时器类。

主要方法

public void schedule(TimerTask task, Date time)安排指定时间完成指定任务

public void schedule(TimerTask task, Date firstTime, long period) 安排任务重复执行

public boolean cancel()终止此计时器,丢弃所有任务

注意:如果指定了任务,但是在任务还没开始执行时就取消任务时,任务的代码将一次都不执行。

注意:定时器不可以重复schedule任务。

2TimerTask:由 Timer 安排为一次执行或重复执行的任务。

public abstract void run()  Runnable中的run方法

public boolean cancel() 取消此定时器任务

 

十五、多线程——单例设计模式

内存当中只存在一个实例对象。

1、单例设计模式要求。

A、构造方法私有化

B、定义了自己类型的静态私有成员变量

C、对外提供公共的、静态的获取实例方法

D、实例创建方式分为懒汉式与饿汉式

E、懒汉式时,需要考虑线程安全问题

2、代码体现:

A、懒汉式:(面试)(考虑多线程和效率)
	class Single{
		private static Single s = null;
		private Single(){}
		public static Single getIntance(){
			if (s == null){
				synchronized(Single.class) {
					if(s==null)
					s = new Single();
				}
			}
			return s;
		}
	}
B、饿汉式:(开发)
	class Single{
		private static Single s = new Single();
		private Single(){}
		public static Single getIntance(){
			return s;
		}
	}


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值