黑马程序员--多线程之整理笔记
--------- android培训、java培训、期待与您交流! ----------
1线程之学习
1.1基本概念
l 进程:是一个正在执行的程序。每一个进程执行都有一个顺序。该顺序是一个执行路径,或者叫一个控制单元。
l 线程:就是进程中的一个独立的进程单元。线程在控制着进程的执行。
两者之间的联系:一个进程中至少有一个线程。
1.1.1运行一个java程序的进程分析
此时java vm会启动,启动的时候有一个进程java.exe.
该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。
扩展:其实更细节的说明jvm,它启动不止一个线程,还有负责垃圾回收机制的线程。
1.2 创建线程的两种方式
- 继承Thread类
- 实现Runnable接口
1.2.1创建线程的第一种方式:继承Thread类。
步骤:
- 定义类继承Thread。
- 复写Thread类中的run方法。
- 调用线程的start方法,该方法有两个作用:启动线程,调用run方法。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class Demo extends Thread 2 { 3 public void run() 4 { 5 for (int i = 0; i <100; i++) { 6 System.out.println("demo run---"+i); 7 } 8 } 9 } 10 public class ThreadDemo { 11 12 public static void main(String[] args) 13 { 14 Demo d=new Demo();//创建好一个线程 15 d.start(); 16 for(int x=0;x<100;x++) 17 { 18 System.out.println("Hello World!---"+x); 19 } 20 } 21 22 }
发现运行结果每次都不一样,为什么呢?
- 因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。
- 明确一点,在某一个时刻只能有一个程序在运行(多核除外)
- cpu在做着快速的切换,以达到看上去是同时运行的效果。
- 我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
- 这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。
1.2.2 创建线程的第二种方式:实现Runnable接口
步骤如下:
- 定义类实现Runnable接口
- 覆盖Runnable接口中的run方法。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class Demo implements Runnable 2 { 3 public void run() 4 { 5 for (int i = 0; i <100; i++) { 6 System.out.println("demo run---"+i); 7 } 8 } 9 } 10 public class ThreadDemo { 11 12 public static void main(String[] args) 13 { 14 Demo d1=new Demo(); 15 Thread th=new Thread(d1); 16 th.start(); 17 for(int x=0;x<100;x++) 18 { 19 System.out.println("Hello World!---"+x); 20 } 21 } 22 23 }
- 将线程要运行的代码存放在该run方法中
- 通过Thread类建立线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
- 为什么要将Runnable接口的子类对象传递给Thread类的构造函数?
- 因为自定义的run方法所属对象是Runnable接口的子类对象。
- 所以要让线程去执行指定对象的run方法。就必须明确该run方法所属对象。
- 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
1.2.3两者的比较:
实现Runnable接口相对于继承Thread类来说,有如下显著的好处:
(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
1.3线程的安全性问题:
1.3.1产生线程安全的原因:
线程的working memory是cpu的寄存器和高速缓存的抽象描述:现在的计算机,cpu在计算的时候,并不总是从内存读取数据,它的数据读取顺序优先级 是:寄存器-高速缓存-内存。线程耗费的是CPU,线程计算的时候,原始的数据来自内存,在计算过程中,有些数据可能被频繁读取,这些数据被存储在寄存器和高速缓存中,当线程计算完后,这些缓存的数据在适当的时候应该写回内存。当多个线程同时读写某个内存数据时,就会产生多线程并发问题,涉及到三个特 性:原子性,有序性,可见性。 支持多线程的平台都会面临 这种问题,运行在多线程平台上支持多线程的语言应该提供解决该问题的方案。
1.3.2解决同步问题两种方式:
- 同步代码块
- 同步函数
同步代码块格式:
synchroized(对象)
{ 同步的代码}
对象如同锁,持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
- 同步的前提:
- 必须要有两个或者两个以上的线程。
- 必须是多个线程使用同一个锁。
- 必须保证同步中只能有一个线程在运行。
- 同步的好处:解决了多线程中的安全问题。
- 同步的弊端:多个线程需要判断锁,较为消耗资源。
- 如何找出该线程是否存在安全性问题呢?
- 明确哪些代码是多线程运行代码
- 明确共享数据。
- 明确多线程运行代码中那些代码语句是操作共享数据的。
同步函数解决安全性问题
格式如下:
public synchronized void 函数名()
{ 代码 }
- 同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所述对象引用。就是this。所以同步函数使用的锁是this。
- 静态同步函数使用的锁是什么呢?
静态内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象类型是Class
静态同步方法使用的锁是该方法所在类的字节码文件对象类名.class。
1.4线程的单例设计模式
1.4.1单例设计模式---饿汉式
class Single
{
private static final Single s=new Single();
private Single()
{
}
public static Single getInstance()
{
return s;
}
}
1.4.2单例设计模式--懒汉式
- //懒汉式
- /*
- * 懒汉式用于实例的延迟加载,
- * 多线程访问时会出现安全问题,怎么解决?可以用同步代码块解决
- * 同步代码块,但是降低了效率
- * 用双重判断可以提高懒汉式的效率
- * 该类所属的字节码文件
- */
- public class Single {
- private static Single s=null;
- private Single(){}
- public static Single getInstance()
- {
- if(s==null)
- {
- synchronized(Single.class)
- {
- if(s==null)
- s=new Single();
- }
- }
- return s;
- }
- }
- class SingleDemo
- {
- public static void main(String[] args)
- {
- System.out.println("Hello World!");
- }
- }
1.5多线程的死锁
1.5.1产生死锁原因
简单来说同步中嵌套同步。多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
由于线程被无限期地阻塞,因此程序不可能正常终止。
1.5.2产生死锁的条件
一般造成死锁必须同时满足如下4个条件:
- 互斥条件:线程使用的资源必须至少有一个是不能共享的;
- 请求与保持条件:至少有一个线程必须持有一个资源并且正在等待获取一个当前被其它线程持有的资源;
- 非剥夺条件:分配资源不能从相应的线程中被强制剥夺;
- 循环等待条件:第一个线程等待其它线程,后者又在等待第一个线程。
因为要产生死锁,这4个条件必须同时满足,所以要防止死锁的话,只需要破坏其中一个条件即可。
导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性的访问权。当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Test implements Runnable { private boolean flag; Test(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(); } public class DeadLock { public static void main(String[] args) { Thread t1 = new Thread(new Test(true)); Thread t2 = new Thread(new Test(false)); t1.start(); t2.start(); } }