黑马程序员_多线程有关问题

-----Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流------


 我们需要学习的是多线程,那么要学习多线程,我们首先要学习线程.而线程是依赖于进程的,所有我们需要学习进程.
1. 什么是进程?
        通过任务管理器,可以查看到进程,所谓的进程就是正在执行的程序.
2. 多进程的意义?
       如果计算机是单进程,那么指定一次执行一个程序.而我们现在的计算机都是多进程. 那么也就是说我们可以一边玩游戏一边听音乐.
       提高CPU的使用率.
      我们一边 玩游戏一边听音乐.存在两个进程,那么这个两个进程是同时执行的吗?同时: 指的是在一个时间点
      不是同时执行的,因此CPU在一个时间点上只能执行一个任务,而我们看到的像是同时执行,其实是这样子的.是CPU在这个两个进程之间进行高效的切换.
3. 什么是线程?
一个应用程序可以执行多个任务,而每一个任务就是一个线程.
4. 多线程的意义?
多线程的意义不是提高程序的运行效率,而是提高程序的使用率.
如何理解这句话呢?
我们程序在执行的时候其实都是在抢占CPU的时间片(CPU的执行权). 如果一个应用程序只有一个任务,而另一个应用程序有多个任务,那么那个应用
抢占到CPU的执行权的概率大呢?是多个任务的应用程序抢到的概率比较大.多个任务的应用程序一定就可以抢占到CPU的执行权吗?不一定,所以多线程的执行具有随机性.
并发和并行的区别:
并发: 就是在一个时间点上
并行: 就是在一个时间段上
5. JVM的运行原理:
   我们在使用java命令的时候是运行对应的程序吧,这时候是运行的JVM吧,那么这个JVM的运行就相当于启动了一个
  进程,并且他会自动启动一个线程,这个线程就是主线程,这个主线程是用来调用我们的main方法 .
6.  jvm的运行是多线程的吗?
  是多线程,因此至少存在两个线程一个是主线程,一个是垃圾回收线程. 
那么我们用该这么实现多线程呢?
两种方法:实现多线程的两个方式:
第一种方式:
步骤:
a: 创建一个类,然后让这个类继承Thread
b: 重写run方法
c: 创建该类的对象
d: 启动线程类
public class ThreadTest {
	
	public static void main(String[] args) {
		
		//线程的第一种创建方式
		
		// 创建对象
		MyThread t1 = new MyThread() ;
		MyThread t2 = new MyThread() ;
		
		// 启动线程
		/**
		 * 不能使用run来启动线程,下边的代码其实只是在调用run方法
		 * 启动线程: 需要使用start方法
		 */
//		t1.run() ;
//		t1.run() ;
		
		// public void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 
		// 启动线程 注意:线程的启动只能是一次,不能进行多次启动
		t1.start() ;
		t2.start() ;

	}

}

创建一个MyThread类,然后让这个类继承Thread
/**
 * 为什么要重写run方法?
 * 我们现在的这个类是不是可以有多个方法? 可以存在多个方法.
 * 而我们的多个方法中的代码都是需要被线程执行的吗?不是的.
 * 如果我们想让某一段代码被线程执行,那么我们就把这个代码写在run方法 .
 * run方法中写的代码就是需要被线程执行的代码. 
 * 
 * 	run方法中的代码的特点:
 * 	写的代码都是比较耗时的代码
 */
public class MyThread extends Thread {

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

实现线程的第二种方式:
步骤:
  a: 创建一个类,然后让这个类去实现Runnable接口
  b: 复写run方法
  c: 创建定义的类的对象
   d: 创建Thread类的对象,然后把C中的对象作为参数传递进来
  e: 启动线程
  这两种方式: 第二种方式相对比较友好一点点.因为其解决了继承带来的局限性
public class ThreadTest {
	
	public static void main(String[] args) {
		// 实现线程的第二种方式
		
		// 创建MyThread的对象
		MyThread my = new MyThread() ;
		
		
		//给线程设置名称
		 
		Thread t1 = new Thread(my , "小强") ;
		Thread t2 = new Thread(my , "小迪")  ;
		
		//启动线程
		t1.start() ;
		t2.start() ;
	}

}

创建一个MyThread类,然后让这个类去实现Runnable接口
public class MyThread implements Runnable {

    //复写run()方法
	@Override
	public void run() {
	    
		for(int x = 0 ; x < 200 ; x++){
			System.out.println(Thread.currentThread().getName() + "----" + x);
		}
	}

}

线程中所使用的方法有哪些?
如何来获取线程的名称以及给线程设置名称:
  public final String getName(): 返回该线程的名称。 
  public final void setName(String name)改变线程名称,使之与参数 name 相同。 
举例为:
使用构造方法给线程设置名称
MyThread t1 = new MyThread("小强 ") ;
MyThread t2 = new MyThread("
小迪 ") ;
另外一种:创建对象
MyThread t1 = new MyThread() ;
MyThread t2 = new MyThread() ;
调用setName(String name);设置线程名称
t1.setName("小强") ;
t2.setName("小迪") ;
线程的调度以及线程的优先级:
  调度模型:
  分时调度模型:  给每一个线程分配指定的时间,来完成线程的执行
抢占式调度模型:  优先执行优先级高的线程,如果多个线程的优先级相同,随机执行一个
而我们的java语言采用的就是抢占式调度模型
抢占式调用模型涉及到线程的优先级,而我们先在没有给线程设置优先级,那么应该存在默认的优先级.
我们可以通过  public final int getPriority()返回线程的优先级
进过调取该方法我们可以得到:
线程的默认的优先级是5
线程的优先级有一个范围: 这个范围是1-10 
但是 虽然我们设置了优先级,设置的优先级仅仅表示这个线程获取到CPU的执行权的概率增大了,一两次的运行说明不了问题
设置线程的优先级public final void setPriority(int newPriority)更改线程的优先级。
线程休眠机制:
public class ThreadTest {
	
	public static void main(String[] args) {
		
		//两种给线程赋值方式由于在MyThread中复写了Mythread(String name) 方法, 所以可以直接创建对象时定义或者通过setName设置
		
		// 创建线程对象
		MyThread t1 = new MyThread("小迪") ;
		MyThread t2 = new MyThread("小强") ;
		
//		// 给线程设置名称
//		t1.setName("小强") ;
//		t2.setName("小迪") ;
		
		// 启动线程
		t1.start() ;
		t2.start() ;
		
	}

}

Mythread类为:
public class MyThread extends Thread {
	
	public MyThread(){}

	public MyThread(String name){
		super(name) ;
	}
	
	@Override
	public void run() {
		for(int x = 0 ; x < 100 ; x++){
			
			//调用休眠方法sleep()并捕获异常
			try {
				Thread.sleep(200) ;
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(getName() + "----" + x);
		}
	}
}

了解线程礼让与线程守护:
线程礼让:
public static void yield()暂停当前正在执行的线程对象,并执行其他线程。 
 yield这个暂停线程的时间太短了,这时候别的线程有可能没有抢占到CPU的执行权,这时候该线程醒了,那么可以继续在和别的线程在抢占CPU的执行权
线程守护:
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。
线程终端的两种方式:stop() 与 interrupt() 
/**
 * 线程中断:
 *		public final void stop(): 终止线程
 *		public void interrupt(): 中断线程,查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
 */
public class ThreadTest {

	public static void main(String[] args)  {
		
		// 创建一个线程对象
		MyThread t1 = new MyThread() ;
		
		// 给线程设置名称
		t1.setName("小强") ;
		
		// 启动
		t1.start() ;
		
		try {
			Thread.sleep(3000) ;
			// t1.stop() ;
			// public void interrupt()中断线程。 
			t1.interrupt() ;
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
	}
	
}

Mythread类为:
public class MyThread extends Thread {
	
	//构建有参无参构造方法
	public MyThread(){}

	public MyThread(String name){
		super(name) ;
	}
	
	@Override
	public void run() {
		
		System.out.println("线程执行了.......");
		
		try {
			Thread.sleep(10000) ;
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("线程结束了......");
	}
}

通过多线程的学习我们做了模拟电影院出票的多线程模式
public class ThreadTest {
	
	public static void main(String[] args) {
       
        //使用第二种方式创建多线程
		
		// 创建SellTickets对象
		SellTickets st = new SellTickets() ;
		
		// 创建Thread对象
		Thread t1 = new Thread(st , "窗口1") ;
		Thread t2 = new Thread(st , "窗口2") ;
		Thread t3 = new Thread(st , "窗口3") ;
		
		// 启动线程
		t1.start() ;
		t2.start() ;
		t3.start() ;
	}

}

出票的sellTickets类为:
public class SellTickets implements Runnable {
	
	/**
	 * 定义票数为静态变量随着类的加载而加载
	 */
	private static int tickets = 100 ;

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

但是这样写有bug ,当最后为1时三个窗口都可能别加载进入if判断中sleep等待休眠结束开始运行时打印出0 和 -1 .为了解决这个问题我们可以用以下方法改善
/**
 * 我们线程中产生问题的标准:
 * 	a: 是否是多线程环境
 * 	b: 是否存在共享数据
 * 	c: 是否有多条语句操作共享数据
 *如何来解决这个问题呢?
 *	我们可以让一个线程执行操作共享数据的代码,然后其他的线程处于等待状态. 
 *如何让一个线程执行这段代码的同时,其他线程处于等待状态呢?
 *需要使用同步代码块:
 *		格式:
 *			synchronized(对象){
 *				需要被同步的代码 ;
 *			}
 *	同步代码块保证同步的作用的关键是这个对象,其实这个对象可以被看做成一把锁.有的书上把这个对象称之为监视器
 *	这个同步代码块中的对象要保证多个线程使用的是一个
 *	
 *	同步代码块的好处:	 提高了数据的安全性
 *	同步代码块的弊端:	每一个线程进来以后都需要判断同步锁,所有对应的效率比较低
 *  即线程安全则效率就降低
 */
public class ThreadTest {
	
	public static void main(String[] args) {

		// 创建SellTickets对象
		SellTickets st = new SellTickets() ;
		
		// 创建Thread对象
		Thread t1 = new Thread(st , "窗口1") ;
		Thread t2 = new Thread(st , "窗口2") ;
		Thread t3 = new Thread(st , "窗口3") ;
		
		// 启动线程
		t1.start() ;
		t2.start() ;
		t3.start() ;
	}
}

出票的sellTickets类为:
public class SellTickets implements Runnable {
	
	/**
	 * 定义票数
	 */
	private static int tickets = 100 ;
	private static Object obj = new Object() ;

	@Override
	public void run() {
		while(true){
		    
		    //使用同步代码块
			synchronized(obj){
				if(tickets > 0){
					try {
						Thread.sleep(100) ;
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "出售第" + (tickets--) + "张票");
				}
			}
		}
	}
}

了解:
同步代码块的锁是,是任意的对象
  同步方法的锁对象到底是谁呢? 格式:public synchronized void sellTicket()       锁对象是this
   静态同步方法的锁对象到底是谁呢? 格式:public static synchronized void sellTicket()     锁对象是当前类的字节码文件对象
通过这些例子我们可以很好地认识多线程的原理以及调用Thread中的方法.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值