黑马程序员————多线程概述、同步死锁、线程控制、并发访问

------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------



多线程概述、同步死锁、线程控制、并发访问



一、多线程的概述


1.进程:正在运行的程序,是系统进行资源分配和调用的独立单位。

1.对于"操作系统"而言,每个独立运行的程序就是独立的"进程";
2."操作系统"会分别针对每个"进程"分配一块独立的内存空间;

2.多进程:

1)."操作系统"可以同时维护多个"应用程序"的执行。
  2).每个应用程序都交由操作系统管理,在某个时间点上,会有一个应用程序被操作系统分配给CPU去执行,执行一会后,会被操作系统
 终止执行,并交由另一个"应用程序"继续执行。由于转换非常快,CPU的运算速度也非常快,这就让我们感觉好像是多个应用程序在
 同时执行一样。

3).多进程的意义:
        1).方便了我们用户的使用。我们可以同时启动多个程序,一边听歌,一边上网,一边下载;
      2).充分的利用CPU资源;


3.线程:

1.线程是由一个"进程"的内部启动的,可以脱离于"主进程"而独立运行的一块代码;
2.一个线程一旦启动,将和"主进程"并行运行,一起面对操作系统,抢占系统资源;
  3.一个"进程"可以启动多个"线程";

4.多线程:

1).一个进程可以同时启动多个线程去单独运行;这个程序就是一个多线程程序;
  2).多线程的意义:
  1).可以使我们的应用程序"同时"运行多个非常复杂的代码;
  3).充分的利用了CPU的资源;


5.并行和并发:

1)."并行"是指逻辑上一起在执行,它强调的是在"同一段时间内"一起运行的程序
2)."并发"是指物理上的抢占同一资源。它强调的是在"同一时刻时"一起抢占系统的某个共享资源


6.JAVA程序运行原理

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


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


二、多线程实现方式:


1.继承Thread类;

A:实现步骤:

1.自定义一个线程类,要继承自Thread;
2.重写Thread中的run()方法;将要在线程中执行的代码写到这里;
3.启动线程:
  1).实例化一个我们自定义的线程类;
  2).调用它的start()方法;启动线程;

B:注意:

1.一个线程类,可以实例化多个对象,并分别启动。也就意味着多个线程同时运行;
2.一个线程对象,只能调用一次start,不能重复调用。否则抛出异常:java.lang.IllegalThreadStateException
★★ 3.只用调用start()才是启动线程。run()方法内的代码会以一个线程的方式去运行;




class Thread{
 		public void start(){
 			run();//调用的是子类的run()方法;
 		}
 		public void run(){
 		}
 }
 
class MyThread extends Thread{
 		//重写父类的run
  		public void run(){
 			System.out.println("子类的run");
  		}
 } 
 main(){
  		MyThread t1 = new MyThread();
  		t1.start();
 }



2.实现Runnable接口;


三、设置获取线程的名称


1.即使我们不去设定线程名称,每个线程都会有一个默认的线程名称;


2.获取线程名:

public final String getName();
默认的线程名称:Thread - "索引值(按启动顺序,从0开始)"
public static Thread currentThread()——返回对当前正在执行的线程对象的引用。————这样就可以获取任意方法所在的线程名称


3.设置线程名称:

  public final void setName(String name):设置线程的名称
public Thread( String name):分配新的 Thread 对象。这种构造方法与 Thread(null, null, name) 具有相同的作用。 参数:name - 新线程的名称。




四、线程的调度

1.线程有两种调度模型:
分时调度模型:
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型:   
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,
优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型
2.设置获取线程的优先级:
Java的线程的优先级范围:1--10(从低到高)
设置优先级:setPriority(int n):n一定要在1--10之间,否则抛异常;
获取优先级:getPriority():

3.注意

1.Java中的优先级跟操作系统的优先级往往不匹配,不要依赖于优先级,
去试图让某些线程先执行完毕,因为这得不到保证;
2.如果线程的内部工作非常简单,那么设置优先级的效果将不会明显;


五、线程的控制

1、休眠:

public static void sleep(long millis):休眠到指定的m毫秒后,将自动醒来;并且处于就粗状态。
由于其是静态的那么使用类名就可以调用。写在哪个线程内部,就是让哪个线程休眠。
public class MyThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			try {
				//让当前的线程休眠多长时间;
				Thread.sleep(1000 * 1);


			} catch (InterruptedException e) {
			}
			Date date = new Date();
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日  HH:mm:ss");
			String str = sdf.format(date);
			System.out.println(str);
		}//让进程休眠一秒钟,在执行
	}
}

2、加入:
public final void join();意味着,调用者可以获得优先执行,调用者执行完之后,才能执行其他的进程。

3、礼让:
public static void yield():退回到"就绪状态",可能会被操作系统重新分配;
1.直接使用类名就可以调用;
2.调用的线程,会退回到就绪的状态,同其它线程站在同一起跑线上,等待着被操作系统分配资源;
礼让的线程,很有可能被再次分配到执行权;

4、守护:

public final void setDaemon(boolean on):on为true,守护线程。
主进程结束,守护线程也结束,但不会立即结束,会有一个小缓冲;
在主程序中,将某个线程设置为守护线程。


5、中断:stop()(过时了)
         public void interrupt():线程一定要处于:Object-->wait();Thread-->sleep();Thread--join()三种状态之一时,才能触发此异常;
只有当线程处于以下三种状态的阻塞时,才会有效:
  1.Object-->wait()
  2.Thread-->sleep();
  3.Thread-->join();
当调用interrupt()方法时,会促使虚拟机产生一个InterruptedException异常
  并且被线程内的上述三个方法调用的catch捕获,捕获到后,可以结束线程的执行;
6、以上五种线程控制的方法,两种静态方法,sleep()和yield()都是使用类名调用,常在线程内部使用。
7.面试题
A:当一个线程被start()后会被立即执行吗?
不会的,当start()后会进入“就绪”状态,由操作系统分配执行权力。只有操作系统分配了执行权,才会获得执行。
B:但一个线程sleep醒来后,会不会就执行,只是就绪状态。


六、实现线程的的第二种方法--实现Runnable接口

1.步骤
自定义一个类,实现Runnable接口
重写接口中的run()方法
启动线程:
实例化自定义类对象
实例化Thread对象,并将我们自定义对象当做参数传递给Thread的构造方法
2.获取线程名称
public static Thread currentThread()——返回对当前正在执行的线程对象的引用。————
这样就可以获取任意方法所在的线程名称调用Thread类的start()方法启动线程。


实现方法的原码抽象分析:
class Thread{
 * 		private Runnable runnable = null;
 * 		Thread(Runnable run){
 * 			this.runnable = run;
 * 		}
 * 		void start(){
 * 			if(this.runnable == null){
 * 				run();//如果不是用Runnable对象构造的,就执行我们的类的run,这个自定义类就是应该是Thread的子类;
 * 			}else{
 * 				this.runnable.run();//执行的是我们实现Runnable接口的类中的run()
 * 			}
 * 		}
 * 		void run(){
 * 		}
 * }
 * class MyThread extends Thread{
 * 		//重写父类的run
 * 		void run(){
 * 		}
 * }
 * main(){
 * 		new MyThread().start();
 * }	

3.两种实现形式的比较:
第一种需要继承自一个类,对子类形成一种限制,如果子类已经继承了其他类,就不能再继承这个类了。
第二种方式比较灵活;推荐使用。
适合多个相同的代码的程序去处理同一个资源的情况,把线程同程序的代码和数据有效分离了,较好的体现了面向对象的设计思想。
4.购买电影票的实例分析:
票池是一个共享的数据区域,所以只能实例化一个对象,否则就不对了。
票池的get方法,要是多个线程并行访问,会长生并发性问题,即产生重复的票和负票。
解决方法:对可能产生并发访问的方法加锁。


七、解决并发性问题:

1.使用关键字:synchronized
2.使用格式:ynchronized(被锁的对象){可能被并发访问的代码}。
被锁的对象:是一个对象的引用,表示"被锁的对象",意味着,如果一个线程正在访问这个"被锁对象"的synchronized
代码块时,其它线程不能访问这个对象中的任何的synchronized的代码块或synchronized的方法;
3.synchronized可以修饰一个代码块:同步代码块
          synchronized可以修饰一个方法:同步方法

★★4.静态的方法可以被synchronized修饰。
          静态的方法内部也可以包含synchronized的代码块,但一般锁定是某个类的class对象。
 可以将一个静态方法声明为同步的.在静态方法内添加同步代码块,一般锁的是某个类的class对象。
5.同步的特点好处和弊端:
1).特点:当一个代码块被同步后,当一个线程访问时,其它线程列队等待。它能保证同一时刻只为一个线程服务,一个线程执行完毕,才能轮到下一个线程执行;
2).好处:可以解决多线程访问的并发性问题;
3).弊端:因为要使其它线程列队等待,所以会有其它额外的操作,而且这些操作都非常耗时,所以效率比较低;


八、以前的线程问题

StringBuffer:线程安全的。效率低
StringBuilder:线程不安全的。效率高
 
集合:
ArrayList:线程不安全。效率高
Vector:线程安全,效率低
  
Hashtable:线程安全的,效率低;
HashMap:线程不安全,效率高
  
工具:
Collections类中,有些:synchronizedXxxx():方法,可以将线程不安全的集合转换为线程安全的集合;
ArrayList<String> strList = new ArrayList<>();//线程不安全的
strList.add("aaa");
strList.add("bbb");

//转换为线程安全的List
List<String> list2 = Collections.synchronizedList(strList);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值