【黑马程序员】多线程(一) 第十一天

-------  android培训java培训java学习型技术博客、期待与您交流! ----------

知识点

Java的重要功能之一就是内部支持多线程——在一个程序中允许同时运行多个任务。

每个任务都是Runnable接口的实例。线程就是一个便于任务执行的对象。可以通过实现Runnable接口来定义任务类,通过使用Thread构造方法包住一个任务来创建线程。

一个线程对象被创建之后,可以使用start()方法启动线程,可以使用sleep(long)方法将线程转入休眠状态,以便其它线程获得运行的机会。

线程对象从来不会直接调用run方法。到了执行某个线程的时候,Java虚拟机调用run方法。类必须覆盖run方法,告诉系统线程运行时将会做什么。

为了避免线程破坏贡献资源,可以使用同步的方法或块。同步方法在执行前需要获得一个。当同步方法是实例方法时,锁是在调用方法的对象上;当同步方法是静态(类)方法时,锁是在方法所在的类上。

在执行方法中某个代码块时,可以使用同步语句获得任何对象上的锁,而不仅是this对象上的锁。这个代码块称为同步块

可以使用显示锁和条件以及对象的内置监视器来便于进程之间的通信。

如果两个或更多的线程获取多个对象上的锁时,每个线程都有一个对象上的锁并等待其他对象上的锁,这时就有可能发生死锁现象。使用资源排序技术可以避免死锁。

可以定义个任务类来扩展SwingWorker,运行费时的任务并且使用任务产生的结果更新GUI。

可以使用JProgressBar来跟踪线程的进度。


01)多线程(概述)

/*
 * 进程:是一个正在执行的程序。
 * 		每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
 * 线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。
 * 一个进程中至少有一个线程。
 * 
 * Java虚拟机启动的时候会有一个进程java.exe。
 * 该进程中至少有一个线程在负责java程序的执行,而且这个线程运行的代码存在于main方法中。
 * 该线程称之为主线程。
 * 
 * 扩展:更细节的说明JVM,启动的不止一个线程,还有负责垃圾回收机制的线程。
 */

02)继承Thread类

/*
 * 1:如果在自定义的代码中,自定义一个线程呢?
 * 通过对API的查找,java已经提供了对线程这类事物的描述,就是Thread类。
 * 创建线程的第一种方式:继承thread类。
 * 步骤:
 * 1:定义类继承Thread类。
 * 2:复写Thread类中的run()方法。
 * 3:调用线程的start方法,该方法有两个作用。(1)启动线程。(2)调用run方法。
 * 
 * 发现运行结果每次都不同。
 * 因为多个线程都在获取CUP的执行权。CUP执行到谁,就执行谁。
 * 明确一点,在某一个时刻,只能有一个线程在运行。(多核除外)
 * cup在做着快速的切换以达到看上去是同时运行的效果。
 * 我们可以形象的把多线程的运行形容为在互相抢夺CUP的执行权。
 * 
 * 这是多线程的特性:随机性。谁抢到就执行谁,至于执行多长,cup说了算。
 */
public class ThreadDemo_2 {
	public static void main(String[] args) {
		Demo_2 d = new Demo_2();//创建好一个对象就是创建好一个线程。
		d.run();
		
		for (int i = 0; i < 10 ; i++){
			System.out.println("Hello World: " + i);
		}
	}
}
class Demo_2 extends Thread{
	public void run(){
		for (int i = 0; i < 10; i++){
			System.out.println("Demo run: " + i);
		}
	}
}

03)run和start特点

/*
 * 为什么要覆盖run方法呢?
 * 
 * Thread类用于描述线程。
 * 该类就定义了一个功能,用于存储线程要运行的代码,该存储的功能就是run方法。
 * 也就是说Thread类中的run方法,用于存储线程要运行的代码。
 * 
 */
public class ThreadDemo_3 {
	public static void main(String[] args) {
		Demo_2 d = new Demo_2();//创建好一个对象就是创建好一个线程。
		d.start();//开启线程并执行该线程的run方法。
		//d.run();//仅仅是对象的调用方法。而线程创建了,但并未运行。
		
		for (int i = 0; i < 10 ; i++){
			System.out.println("Hello World: " + i);
		}
	}
}
class Demo_3 extends Thread{
	public void run(){
		for (int i = 0; i < 10; i++){
			System.out.println("Demo run: " + i);
		}
	}
}

04)线程练习

/*
 * 创建两个线程,和主线程交替运行。
 */
public class ThreadDemoTest {
	public static void main(String[] args) {
		Test t1 = new Test("Run 1");
		Test t2 = new Test("Run 2");
		t1.start();
		t2.start();
		//t1.run();//这行代码程序将没有结果。
		//t2.run();//这行代码程序将没有结果。
		for (int i = 0; i < 50; i++){
			System.out.println("Main: " + i);
		}
	}
}
class Test extends Thread{//继承Thread线程类。
	private String name;
	Test(String name){
		this.name = name;
	}
	public void run(){
		for (int i = 0; i < 50; i++){
			System.out.println(name + " Test run: " + i);
		}
	}
}
运行随机结果如下图所示:



05)线程运行状态



06)获取线程对象及名称

/*
 * 创建两个线程,和主线程交替运行。
 * 
 * 原来线程都有自己默认的名称。
 * Thread-编号 该编号从0开始。
 * Thread.currentThread();获取当前线程对象。
 * getName();获取线程的名称。
 * 设置线程的名称:setName或者构造函数。
 */
public class ThreadDemo_6 {
	public static void main(String[] args) {
		Demo_6 t1 = new Demo_6("Run 1");
		Demo_6 t2 = new Demo_6("Run 2");
		t1.start();
		t2.start();
		for (int i = 0; i < 50; i++){
			System.out.println("Main: " + i);
		}
	}
}
class Demo_6 extends Thread{//继承Thread线程类。
//	private String name;
	Demo_6(String name){
//		this.name = name;
		super(name);
	}
	public void run(){
		for (int i = 0; i < 50; i++){
			System.out.println((Thread.currentThread() == this)//Thread.currentThread()是标准通用方法。
					+ "....." + this.getName() + " Test run: " + i);
		}
	}
}
运行随机结果如下图所示:



07)多线程(售票的例子。)

/*
 * 需求:简单的卖票程序。
 * 多个窗口同时卖票。
 */
public class ThreadMaiPiao {
	public static void main(String[] args) {
		Ticket t1 = new Ticket("窗口1: ");
		Ticket t2 = new Ticket("窗口2: ");
		Ticket t3 = new Ticket("窗口3: ");
		Ticket t4 = new Ticket("窗口4: ");
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
class Ticket extends Thread{
	Ticket(String name){
		super(name);
	}
	private static int ticket = 10;
	public void run(){
		for (int i = 0; i < ticket; i++){
			System.out.println(Thread.currentThread().getName() + " Sela: " + ticket--);
		}
	}
}

08)创建线程——实现Runnable接口

/*
 * 需求:简单的卖票程序。
 * 多个窗口同时卖票。
 * 创建第二种方式:实现Runable接口。
 * 步骤:
 * 1:定义类实现Runable接口。
 * 2:覆盖Runable接口中的run方法。
 * 		将线程要运行的代码存放在该run方法中。
 * 3:通过Thread类建立线程对象。
 * 4:将Runable接口的子类对象作为实际参数传递给Thread类的构造函数。
 * 		为什么,因为自定义的run方法所属的对象是Runable接口的子类对象,
 * 		所以要让线程去指定指定对象的run方法。就必须明确该run方法所属的对象。
 * 5:调用Thread类中的start方法开启线程并调用Runable接口子类的run方法。
 * 
 * 实现方式和继承方式的区别。
 * 实现方式好处在于避免了单继承的局限性。在定义线程时,建议使用实现接口方式。
 * 两种方式的区别:
 * 继承Thread:线程代码存放在Thread子类run方法中。
 * 实现Runable:线程代码存放在Runable接口子类的run方法中。
 */
public class ThreadMaiPiao_2 {
	public static void main(String[] args) {
		Ticket_2 t = new Ticket_2();//
		Thread t1 = new Thread(t);//3:通过Thread类建立线程对象。
		Thread t2 = new Thread(t);//4:将Runable接口的子类对象作为实际参数传递给Thread类的构造函数。
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		
		t1.start();//5:调用Thread类中的start方法开启线程并调用Runable接口子类的run方法。
		t2.start();
		t3.start();
		t4.start();
		
	}
}
class Ticket_2 implements Runnable{//extends Thread//1:定义类实现Runable接口。
	private static int ticket = 10;
	public void run(){//2:覆盖Runable接口中的run方法。
		for (int i = 0; i < ticket; i++){
			System.out.println(Thread.currentThread().getName() + " Sela: " + ticket--);
		}
	}
}

09)多线程的安全问题

/*
 * 需求:简单的卖票程序。
 * 多个窗口同时卖票。
 * 
 * 通过分析,发现,打印出0、 -1、 -2等错票。
 * 多线程的运行出现了安全问题。
 * 
 * (重点)问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,
 * 					还没有执行完,另一个线程就参与了进来。导致了共享数据的错误。
 * 解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其它线程不可以参与执行。
 * 
 * java对于多线程的安全提供了专业的解决方式。
 * 就是同步代码块。
 * synchronized(对象){
 * 		需要被同步的代码。
 * }
 */
public class ThreadMaiPiao_3 {
	public static void main(String[] args) {
		Ticket_3 t = new Ticket_3();//
		Thread t1 = new Thread(t);//3:通过Thread类建立线程对象。
		Thread t2 = new Thread(t);//4:将Runable接口的子类对象作为实际参数传递给Thread类的构造函数。
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		
		t1.start();//5:调用Thread类中的start方法开启线程并调用Runable接口子类的run方法。
		t2.start();
		t3.start();
		t4.start();
	}
}
class Ticket_3 implements Runnable{//extends Thread//1:定义类实现Runable接口。
	private int ticket = 100;
	Object obj = new Object();
	public void run(){//2:覆盖Runable接口中的run方法。
		while(true){
			synchronized(obj){//同步代码块。
				if (ticket > 0){
					try{
						Thread.sleep(1);
					}catch(Exception e){
						
					}
					System.out.println(Thread.currentThread().getName() + " Sela: " + ticket--);
				}
			}
		}
	}
}


10)多线程同步代码块

/*
 * 需求:简单的卖票程序。
 * 多个窗口同时卖票。
 * 
 * 同步代码块。
 * synchronized(对象){
 * 		需要被同步的代码。
 * }
 * 对象如同锁。持有锁的线程可以在同步中执行。
 * 没有锁的线程,即使获取了CPU的执行权,也进不去,因为没有获取锁。
 * 
 * 同步的前提:
 * 1:必须要有两个或两个以上的线程。
 * 2:必须是多个线程使用同一个锁。
 * 
 * 好处:解决了多线程的安全问题。
 * 弊端:多个线程每次都要判断锁,较为消耗资源。
 */
public class ThreadMaiPiao_4 {
	public static void main(String[] args) {
		Ticket_4 t = new Ticket_4();//
		Thread t1 = new Thread(t);//3:通过Thread类建立线程对象。
		Thread t2 = new Thread(t);//4:将Runable接口的子类对象作为实际参数传递给Thread类的构造函数。
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		
		t1.start();//5:调用Thread类中的start方法开启线程并调用Runable接口子类的run方法。
		t2.start();
		t3.start();
		t4.start();
		
	}
}
class Ticket_4 implements Runnable{//extends Thread//1:定义类实现Runable接口。
	private int ticket = 1000;
	Object obj = new Object();
	public void run(){//2:覆盖Runable接口中的run方法。
		while(true){
			synchronized(obj){//同步代码块。
				if (ticket > 0){
					System.out.println(Thread.currentThread().getName() + " Sela: " + ticket--);
				}else{
					return;
				}
			}
		}
	}
}


11)多线程——同步函数

/*
 * 需求:
 * 银行有一个金库。
 * 有两个储户,分别存300元,每次存100元,分三次存储。
 * 
 * 目的:该程序是否有安全问题?如果有,如何解决?
 * 如何找到问题:**
 * 1:明确哪些代码是多线程运行代码。
 * 2:明确共享数据。
 * 3:明确多线程代码中哪些语句是操作共享数据的。
 */
public class BankDemo {
	public static void main(String[] args) {
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}
class Back{
	private int sum;//2:共享数据
	public synchronized void add(int n){//同步函数。//1:add
			sum = sum + n;//3:
			try{
				Thread.sleep(10);
			}catch(Exception e){
			}
			System.out.println("Sum = " + sum);//3:
	}
}
class Cus implements Runnable{
	private Back b = new Back();//2:共享数据
	public void run(){//1:
		for (int i = 0; i < 3; i++){
			b.add(100);//1:
		}
	}
}

12)this——同步函数的锁

/*
 * 同步函数用的是哪一个锁?
 * 函数需要被对象调用。那么函数都有一个所属对象的引用。就是this。
 * 所以同步函数使用锁就是this。
 * 
 * 通过该程序进行验证。
 * 使用两个线程来卖票。一个线程在同步代码块中,一个线程在同步函数中,都在执行卖票动作。
 */
public class ThreadMaiPiao_5 {
	public static void main(String[] args) {
		Ticket_5 t = new Ticket_5();//
		Thread t1 = new Thread(t);//3:通过Thread类建立线程对象。
		Thread t2 = new Thread(t);//4:将Runable接口的子类对象作为实际参数传递给Thread类的构造函数。
		t1.start();//5:调用Thread类中的start方法开启线程并调用Runable接口子类的run方法。
		try{Thread.sleep(10);}catch(Exception e){}
		t.flag = false;//
		t2.start();
	}
}
class Ticket_5 implements Runnable{//extends Thread//1:定义类实现Runable接口。
	private int ticket = 1000;
	boolean flag = true;
	public void run(){//2:覆盖Runable接口中的run方法。
			if (flag){
					while(true){
						synchronized(this){//this是这个同步代码块的锁。
//						this.show();
							if (ticket > 0){try{Thread.sleep(1);}catch(Exception e){}
								System.out.println(Thread.currentThread().getName() + " Run: " + ticket--);
							}
						}
					}
			}else
				while(true)
					show();
	}
	
	public synchronized void show(){//this是这个同步函数的锁。
		if (ticket > 0){
			try{
				Thread.sleep(1);
			}catch(Exception e){
				
			}
			System.out.println(Thread.currentThread().getName() + " Show: " + ticket--);
		}
	}
}

13)class对象——是静态同步函数的锁

/*
 * 如果同步函数被静态修饰后使用的锁是什么呢?
 * 通过验证发现不在是this了。因为静态方法中不可以定义this。
 * 静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
 * 类名.class。
 * 
 * 【总结:静态的同步方法,使用的锁是该方法所在类的字节码文件对象——(类名.class)。】
 */
public class StaticThreadMaiPiao {
	public static void main(String[] args) {
		Ticket_6 t = new Ticket_6();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}
		t.flag = false;
		t2.start();
	}
}
class Ticket_6 implements Runnable{
	private static int ticket = 100;
	boolean flag = true;
	public void run(){//2:覆盖Runable接口中的run方法。
			if (flag){
					while(true){
						synchronized(Ticket_6.class){//【(类名.class)是这个同步代码块的锁。】
							if (ticket > 0){try{Thread.sleep(10);}catch(Exception e){}
								System.out.println(Thread.currentThread().getName() + " Run: " + ticket--);
							}
						}
					}
			}else
				while(true)
					show();
	}
	
	public static synchronized void show(){//【(类名.class)是这个同步函数的锁。】
		if (ticket > 0){
			try{
				Thread.sleep(1);
			}catch(Exception e){
				
			}
			System.out.println(Thread.currentThread().getName() + " Show: ..." + ticket--);
		}
	}
}

14)单设计模式——懒汉式

/*
 * 单设计模式:
 * 恶汉试:
 * class Single{
 * 		private static final Single s = new Single();
 * 		private Single();
 * 		public static Single getInstance(){
 * 			return s;
 * 		}
 * }
 * 
 * 懒汉试:
 * class Single{
 * 		private static final Single s = null;
 * 		private Single();
 * 		public static Single getInstance(){
 * 			if(s == null)
 * 				s = new Single();
 * 			return s;
 * 		}
 * }
 * 懒汉试:特点在于延迟加载。
 * 如果多线程访问时会出现安全问题。
 * 可以加同步来解决,用双重判断能解决低效问题。
 * 加同步时使用的锁是该类所属的字节码文件对象(类名.class)。
 * 
 */

15)多线程——死锁

/*
 * 死锁。
 */
public class DeadLockTest {
	public static void main(String[] args) {
		Thread t1 = new Thread(new TestT(true));
		Thread t2 = new Thread(new TestT(false));
		t1.start();
		t2.start();
	}
}
class TestT implements Runnable{
	private boolean flag;
	TestT(boolean flag){
		this.flag = flag;
	}
	
	public void run(){
		if (flag){
			synchronized(MyLock.locka){
				System.out.println("If locka");
				synchronized(MyLock.lockb){
					System.out.println("If lockb");
				}
			}
		}
		else{
			synchronized(MyLock.lockb){
				System.out.println("Else lockb");
				synchronized(MyLock.locka){
					System.out.println("Else locka");
				}
			}
		}
	}
	
}
class MyLock{
	static Object locka = new Object();
	static Object lockb = new Object();
}


附言:我是Java新人,如有错误的地方请指出。
                         每天学习一点点,纠错一点点,进步很大点。



-------  android培训java培训java学习型技术博客、期待与您交流! ----------


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值