黑马程序员_基础_多线程

-------android培训java培训、期待与您交流!----------

1、概述

进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:进程中的一个独立的控制单元,线程在控制着进程的执行。
一个进程中至少有一个线程。
Java VM 启动的时候会有一个进程java.exe。该进程中至少有一个线程负责Java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。


2、继承Thread类创建线程

/*
 *创建线程的第一种方式:继承Thread类。步骤:
 *1、定义类并继承Thread
 *2、重写Thread类中的run方法。
 *	目的:将自定义的代码存储在run方法中,让线程运行。
 *3、调用线程的start方法:该方法两个作用:启动线程,调用run方法。
 */
class CreateThread extends Thread{
	
	/*
	 * 为什么要覆盖run方法呢?
	 * Thread类用于描述线程。
	 * 该类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
	 * 也就是说Thread类中的run方法,用于存储线程要运行的代码。
	 */
	public void run(){
		
		for(int i = 0; i < 60; i++){
			P.rintln("我是run() -- " + i);
		}
	}
}
3、run和start的区别

public static void testMultiThread1(){
		
		/*
		 * 发现运行结果每一次都不同。因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。
		 * 明确一点,在某一个时刻,只能有一个线程在运行(多核除外)。
		 * cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以形象把多线程的运行行为称为在互相抢夺cpu的执行权。
		 * 这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说了算。
		 */
		CreateThread ct = new CreateThread();	//new一个对象其实就是创建好一个线程
		ct.start();		//启动线程,调用run()。
//		ct.run();	//run()不启动线程,只是执行run中的代码,因此会将run中的输出全部打印之后才执行下面的代码
		for(int i = 0;  i < 60; i++){
			P.rintln("Hello World! -- " + i);
		}
	}
4、实现Runnable接口创建线程、多线程的安全及同步函数

/**
 * 黑马入学学习视频之多线程:实现Runnable接口,多线程的安全,同步函数
 * 
 * 创建线程的第二种方式:实现Runnable接口。步骤:
 * 1、定义类实现Runnable接口
 * 2、覆盖Runnable接口中的run方法
 *    将线程要运行的代码存放在该run方法中。
 * 3、通过Thread类建立线程对象。
 * 4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
 *    自定义的run方法所属的对象是Runnable接口的子类对象。
 *    所以要让线程去运行指定对象的run方法,就必须明确该run方法所属对象。
 * 5、调用Thread类的start方法启动线程并调用实现Runnable接口子类的run方法。
 * 
 * 实现方式和继承方式的区别:
 * 实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。
 * 继承Thread:线程代码存放在Thread子类run方法中。
 * 实现Runnable:线程代码存放在接口的子类的run方法。
 */
public class MultiThread_03_ImpleRunnable {
	
	public static void main(String[] args){
		
		ticket();
	}
	
	public static void bank(){
		
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		
		t1.start();
		t2.start();
	}
	
	public static void ticket(){
		
		//使用此种方法创建多线程并启动只共用一个tick
		Ticket2 t = new Ticket2();	
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		
		t1.start();
		t2.start();
		t3.start();
	}
}

/*
 * 通过分析,发现打印出0、-1等错票。多线程的运行出现了安全问题。
 * 问题的原因:
 *     当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,
 *     还没有执行完,另一个线程参与进来执行,导致共享数据出错。
 * 解决办法:
 *     对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
 * 
 * Java对于多线程的安全问题提供了专业的解决方式。就是同步代码块:
 * synchronized(对象){
 *     需要被同步的代码
 * }
 * 对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有锁。
 * 
 * 同步的前提:
 * 1、必须要有两个或者两个以上的线程。
 * 2、必须是多个线程使用同一个锁
 * 必须保证同步中只能有一个线程运行。
 * 
 * 好处:解决了多线程的安全问题
 * 弊端:判断锁消耗了更多的资源
 */
class Ticket2 implements Runnable{
	
	private int tick = 100;	//设置为静态的,所有线程共用一个tick
	Object obj = new Object();
	
	public void run(){
		
		while(true){
			synchronized(obj){
				if(tick > 0){
					//run方法是重写的接口的方法,接口方法没有抛异常,因此这里的异常只能获取,不能抛出。
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					//没有继承Thread类,需要使用Thread.currentThread()方法获取当前运行的线程
					P.rintln(Thread.currentThread().getName() + " sale : " + tick--);
				}
			}
		}
	}
}

/*
 * 需求:银行有一个金库,有两个储户分别存300元,每次存100,存3次。
 * 如何找多线程安全问题:
 * 1、明确哪些代码是多线程运行代码
 * 2、明确共享数据
 * 3、明确多线程运行代码中哪些语句是操作共享数据的。
 * 只同步需要同步的代码。
 */
class Bank{	
	
	private int sum;
	Object obj = new Object();
	
	//使用synchronized修饰的函数具有同步功能
	public synchronized void add(int n){
		
//		synchronized(obj){
			sum = sum + n;
			//此处有可能出现异常
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			P.rintln(Thread.currentThread() + "sum = " + sum);
//		}
	}
}

class Cus implements Runnable{
	
	private Bank b = new Bank();
	public void run(){
		
		for(int x = 0; x < 3; x++){
			b.add(100);
		}
	}
}
class Ticket1 extends Thread{
	
	private static int tick = 100;	//当继承Thread时,设置为静态的,所有线程共用一个tick
	
	public void run(){
		
		while(tick > 0){
			//继承自Thread,可以直接使用currentThread()方法获取当前执行的线程
			P.rintln(currentThread().getName() + " sale : " + tick--);
		}
	}
}

class Ticket3 implements Runnable{
	
	private int tick = 100;
	Object obj = new Object();
	boolean flag = true;
	
	public void run(){
		
//		if(flag){
//			while(true){
//				synchronized(obj){
//					if(tick > 0){
//					try{
//						Thread.sleep(5);
//					} catch(InterruptedException e){}
//					
//					P.rintln(Thread.currentThread() + " obj-sale : " + tick--);
//					}
//				}
//			}
//		}
//		else{
//			while(true){
//				show();
//			}
//		}
		while(true){
			synchronized(this){
				if(tick > 0){
				try{
					Thread.sleep(5);
				} catch(InterruptedException e){}
				
				P.rintln(Thread.currentThread() + " obj-sale : " + tick--);
				}
			}
			show();
		}
	}
	
	/*
	 * 同步函数用的是哪一个锁:
	 * 函数需要被对象调用,那么函数都有一个所属对象引用。就是this。所以同步函数使用的锁是this。
	 */
	public synchronized void show(){
		if(tick > 0){
			try{
				Thread.sleep(5);
			} catch(InterruptedException e){}
			
			P.rintln(Thread.currentThread() + " show-sale : " + tick--);
		}
	}
}

静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class,该对象的类型就是Class。
当同步函数被static修饰后,使用的锁是该方法所在类的字节码文件对象:类名.class。

5、单例设计模式

/**
 * 黑马入学学习视频之多线程:单例设计模式
 * 所谓“懒汉式”与“饿汉式”的区别,是在建立单例对象的时间不同。
 * @author xiaogh
 */
public class MultiThread_04_Singleton {

	public static void main(String[] args) {
		
		HungrySingleton h1 = HungrySingleton.getInstance();
		HungrySingleton h2 = HungrySingleton.getInstance();
		P.rintln(h1 == h2);
		
		SafeLazySingleton s1 = SafeLazySingleton.getInstance();
		SafeLazySingleton s2 = SafeLazySingleton.getInstance();
		P.rintln(s1 == s2);
	}

}

/**
 * 单例设计模式:饿汉式
 * “饿汉式”是不管你用不用的上,一开始就创建这个单例对象
 */
class HungrySingleton{
	
	//static修饰符,jvm保证single只能被初始化一次,加上final修饰符使程序更加严谨
	private static final HungrySingleton single = new HungrySingleton();
	
	//组织外部使用new实例化对象
	private HungrySingleton(){}
	
	//获取实例,返回唯一的single对象
	public static HungrySingleton getInstance(){
		return single;
	}
}

/**
 * 单例设计模式:懒汉式
 * “懒汉式”是在你真正用到的时候才去创建这个单例对象。即延迟加载。
 * “懒汉式”在多线程访问时会出现安全隐患
 * @author xiaogh
 */
class LazySingleton{
	
	private static LazySingleton single = null;
	
	private LazySingleton(){}
	
	public static LazySingleton getInstance(){
		if(single == null){
                         //若第一个进来的线程在此处丢失执行权,进入阻塞状态,会使其他线程进来,这样子进来的所有线程会创建不同的实例
			single = new LazySingleton();
		}
		return single;
	}
}

/**
 * 使用同步锁解决“懒汉式”的多线程安全问题
 * @author xiaogh
 */
class SafeLazySingleton{
	
	private static SafeLazySingleton single = null;
	
	private SafeLazySingleton(){}
	
	public static SafeLazySingleton getInstance(){
		
		if(single == null){	//外层嵌套一层判断,减少获取锁的次数,
			synchronized(SafeLazySingleton.class){		//使用该类的class字节码对象作为同步锁
				if(single == null){
					single = new SafeLazySingleton();
				}
			}
		}
		return single;
	}
}

6、等待唤醒机制

wait(),notify(),notifyAll()都使用在同步中。
因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在Object中呢?因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中

/**
 * 下面这个例子演示多线程等待唤醒机制
 * @author xiaogh
 */
class Source1{
	public String name;
	public String sex; 
	public boolean flag;
}

class Input1 implements Runnable{
	
	private Source1 source;
	Input1(Source1 source){
		this.source = source;
	}
	public void run(){
		boolean flag = true;
		while(true){
			synchronized(source){
				if(source.flag)
					try {
						source.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				if(flag){
					source.name = "jake";
					source.sex = "male";
				}
				else{
					source.name = "小莉";
					source.sex = "女女女";
				}
				flag = !flag;
				source.flag = true;
				source.notify();
			}
		}
	}
}

class Output1 implements Runnable{
	
	private Source1 source;
	
	public Output1(Source1 source){
		this.source = source;
	}
	
	public void run(){
		while(true){
			synchronized(source){
				if(!source.flag)
					try {
						source.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				P.rintln(source.name + " --- " + source.sex);
				source.flag = false;
				source.notify();
			}
		}
	}
}
7、生产消费者模式 

使用synchronized控制多线程安全

public class MultiThread_08_ProducerConsumer {
	
	public static void main(String[] args){
		
		Resource res = new Resource();
		Thread t1 = new Thread(new Producer(res));
		Thread t2 = new Thread(new Consumer(res));
		Thread t3 = new Thread(new Producer(res));
		Thread t4 = new Thread(new Consumer(res));
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class Resource{
	
	private String name;
	private int count = 1;
	private boolean flag = false;
	
	public synchronized void set(String name){
		
		while(flag){	//使用while循环判断,可以在notiy之后再次判断flag标记,避免重复生产
			try{
				this.wait();	//this可写可不写
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		this.name = name + "--" + count++;
		P.rintln(Thread.currentThread().getName() + "---生产者-----------" + this.name);
		flag = true;
		this.notifyAll();	//将所有线程唤醒,使用notify有可能依然唤醒生产者线程,导致所有线程wait。
	}
	
	public synchronized void out(){
		
		while(!flag){
			try{
				this.wait();
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		P.rintln(Thread.currentThread().getName() + "...消费者..." + this.name);
		flag = false;
		this.notifyAll();
	}
}

//生产者
class Producer implements Runnable{
	
	private Resource res;
	public Producer(Resource res){
		this.res = res;
	}
	
	public void run(){
		while(true){
			this.res.set("产品");
		}
	}
}

//消费者
class Consumer implements Runnable{
	
	private Resource res;
	
	public Consumer(Resource res){
		this.res = res;
	}
	
	public void run(){
		while(true){
			this.res.out();
		}
	}
}
JDK5.0提供了多线程的升级解决方案,将同步synchronized替换成显式Lock操作。
将Object中的wait、notify、notifyAll替换成Condition对象,该对象可以使用Lock锁进行获取。
下面的示例中,实现了本方只唤醒对方操作。
class Resource2{
	
	private String name;
	private int count = 1;
	private boolean flag = false;
	private Lock lock = new ReentrantLock();
	private Condition condition_p = lock.newCondition();	//生产者的锁对象
	private Condition condition_c = lock.newCondition();	//消费者的锁对象
	
	public void set(String name) throws InterruptedException{
		
		lock.lock();
		try{
			while(flag){	
				condition_p.await();
			}
			this.name = name + "--" + count++;
			P.rintln(Thread.currentThread().getName() + "---生产者-----------" + this.name);
			flag = true;
			condition_c.signal();
		}
		finally{	//将释放锁的动作放入到finally中
			lock.unlock();
		}
	}
	
	public void out() throws InterruptedException{
		
		lock.lock();
		try{
			while(!flag){
				condition_c.await();
			}
			P.rintln(Thread.currentThread().getName() + "...消费者..." + this.name);
			flag = false;
			condition_p.signal();
		}
		finally{	
			lock.unlock();
		}
	}
}

//生产者
class Producer2 implements Runnable{
	
	private Resource2 res;
	public Producer2(Resource2 res){
		this.res = res;
	}
	
	public void run(){
		while(true){
			try{
				this.res.set("产品");
			}
			catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}

//消费者
class Consumer2 implements Runnable{
	
	private Resource2 res;
	
	public Consumer2(Resource2 res){
		this.res = res;
	}
	
	public void run(){
		while(true){
			try{
				this.res.out();
			}
			catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}

8、停止线程

stop方法已过时。
如何停止线程:只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:当线程处于冻结状态,就不会读取到标记,那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。Thread类提供该方法:interrupt()。

public class MultiThread_10_InterruptThread {
	
	public static void main(String[] args){
		
		InterruptThread it = new InterruptThread();
		Thread t1 = new Thread(it);
		Thread t2 = new Thread(it);
		
		t1.start();
		t2.start();
		
		int num = 0;
		while(true){
			if(num++ == 60){
//				it.change();
				t1.interrupt();
				t2.interrupt();
				break;
			}
			P.rintln(Thread.currentThread().getName() + "....run." + num);
		}
	}
}

class InterruptThread implements Runnable{
	
	private boolean flag = true;
	
	public synchronized void run(){
		while(flag){
			try{
				wait();
			}
			catch(InterruptedException e){
				P.rintln(Thread.currentThread().getName() + ".....Exception");
				this.flag = false;
			}
			P.rintln(Thread.currentThread().getName() + "....run");
		}
	}
	
	public void change(){
		this.flag = false;
	}
}
9、join方法、优先级和yield方法

public class MultiThread_11_JoinYieldPriority {
	
	public static void main(String[] args) throws InterruptedException{
		
		yield();
	}
	
	public static void yield(){	//临时停止
		
		JoinDemo join = new JoinDemo();
		Thread t1 = new Thread(join);
		Thread t2 = new Thread(join);
		t1.start();
		t2.start();
	}
	
	public static void priority(){
		
		JoinDemo join = new JoinDemo();
		Thread t1 = new Thread(join);
		Thread t2 = new Thread(join);
		t1.start();
		t1.setPriority(Thread.MAX_PRIORITY);	//设置优先级
		t2.start();
		t2.setPriority(Thread.MIN_PRIORITY);
		
		for(int i = 0; i < 80; i++){
			P.rintln(Thread.currentThread().toString() + "..." + i);	//toString方法可以显示线程名称、优先级、所属线程组
		}
		
		P.rintln("main over");
	}
	
	//当A线程执行到了B线程的join方法时,A就会等待,等B线程都执行完,A才会执行。
	//join用来临时加入线程执行。
	public static void join() throws InterruptedException{
		
		JoinDemo join = new JoinDemo();
		Thread t1 = new Thread(join);
		Thread t2 = new Thread(join);
		
		t1.start();
//		t1.join();	//主线程需要等待t1执行完才会继续执行
		t2.start();
		t1.join();	//主线程需要等待t1执行完才会继续执行
		
		for(int i = 0; i < 80; i++){
			P.rintln("main....." + i);
		}
		
		P.rintln("main over");
	}
}

class JoinDemo implements Runnable{
	
	public void run(){
		for(int i = 0; i < 70; i++){
			P.rintln(Thread.currentThread().toString() + "......" + i);
			Thread.yield();		//释放执行权
		}
	}
}
------- android培训java培训、期待与您交流! ----------详细请查看: www.itheima.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值