黑马程序员_Java基础_线程基础,创建,同步(单例设计模式的同步),死锁
来源:互联网 发布:linux清除所有arp缓存 编辑:程序博客网 时间:2024/06/01 10:29
1,进程定义:进程就是指正在执行的程序,怎样查看正在执行的进程呢?我们在使用电脑的时候,其实就有多个正在执行的程序,通过Ctri+Alt+Del 组合键可以进入windows任务管理器查看进程,我们进入后会看到很多.exe,这些就是我们的电脑当前正在执行的程序,也就是一个个的进程。
每一个程序执行的都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
2,线程:是进程中一个独立控制单元,控制着进程的执行。一个进程中至少有一个线程。我们之前写的程序都是单线程的程序。我们在编译java文件的时候,会启动javac进程,启动java命令时,会启动JVM执行.class文件。这个进程中至少有一个负责线程的执行,这个线程运行的代码就是main函数里面的代码,该线程称之为主线程。
注意:通常我们可以理解为这样的程序时单线程程序,实际上并不止就这一个线程。原因是还有一个线程就是JVM的垃圾回收机制中控制垃圾回收的线程,在主线程执行的时候会启动,回收内存中不再使用的对象的内存,其实最少有两个线程。
多线程最常见的应用就是下载软件,下载软件在下载东西的时候,就是多个线程同时向服务器发送请求,同时多条路劲在下载文件。其实多线程在执行的时候并不是多个线程同时执行,而是CPU在多个线程之间进行这快速的切换,中间的事件间隔我们可以忽略,因为太快了,所以我们认为是同时在执行,其实这种执行叫做并发执行。
二,线程的五种状态:
(1)被创建:创建Thread类的子类,将要运行的代码放在run方法中,调用start方法创建线程,并调用run方法,此时线程进入运行状态。
(2)运行:运行状态就是run方法中的代码执行过程,调用stop方法终止整个线程,run方法结束。运行状态时调用sleep方法或者wait方法,是线程进入(3)冻结状态,此时线程放弃了执行权,当睡眠时间或者从冻结状态调用notify方法,能从冻结状态转化为运行状态。
(4)阻塞:这个状态比较特殊,这个状态线程具有执行权,但是在等待CPU资源,这个状态有可能在run中的代码运行一部分还没运行完时,CPU去执行其他线程中的代码去了。冻结状态被叫醒后不一定直接进入运行状态,也有可能进入阻塞状态。当然阻塞状态也有可能进入冻结状态。冻结状态:没有了执行权,当然某个线程睡眠或者等待的时候。
(5)消亡:也就是该线程结束,run中的代码执行完。如果中途要关闭,则通过调用stop方法,否则线程执行完自动消亡。
三,自定义线程:
参考java文档的Thread类时,发现:
创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。例如,计算大于某一规定值的质数的线程可以写成:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,下列代码会创建并启动一个线程:
PrimeThread p = new PrimeThread(143);
p.start();
通过API的解释可以看出创建一个线程的方式一继承Thread类:
1,创建一个类,继承Thread类
2,重写Thread类中的run方法
3,调用线程的启动方法,start,该方法的作用有两个,一个是启动线程和调用run方法。
示例一:
class RunDemo extends Thread { public void run() { for(int i=0;i<70;i++) { System.out.println("Demo run ......"); } }}public class ThreadDemo { public static void main(String[] args) { RunDemo d = new RunDemo(); d.start(); for(int i=0;i<70;i++) { System.out.println("main run..."); } }}
这个程序在执行的时候应该是Demo run和main run是交替执行的。执行过程是主线程启动,main函数执行,然后RunDemo线程启动,run方法和main方法里面的for循环并发执行。
4,为什么定义一个继承Thread类的类的线程时候,要调用start方法,而不调用run方法呢?原因是如果调用了run方法,那么就不是一个独立的控制单元控制一段代码块的执行了,就成了方法的调用,程序中相当于只有一个main线程。程序会按顺序执行。
示例二:定义一个线程类,然后在main方法中启动两个自定义线程,交替执行线程中的内容。
class RunDemo extends Thread { //private String name; RunDemo(String name) { //this.name = name; super(name);//调用父类的构造函数给自定义线程赋一个名字 } public void run() { for(int i=0;i<70;i++) { System.out.println(Thread.currentThread().getName() + " run ......" + i); //System.out.println(this.getName() + " run ......" + i);等价于上面这个 } }}public class ThreadDemo { public static void main(String[] args) { RunDemo d = new RunDemo("one"); RunDemo d1 = new RunDemo("two"); d.start(); d1.start(); /*for(int i=0;i<70;i++) { System.out.println("main run..."); }*/ }}
Thread.currentThread().getName()可以获得线程对象的名字,通过setName或者构造函数可以设置名字,其他操作查看java文档。
自定义线程方式二:
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。采用这种风格的同一个例子如下所示:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,下列代码会创建并启动一个线程:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
实现Runnable接口的方式创建线程步骤:
1,定义一个类实现Runnable接口;
2,覆盖Runnable接口中的run方法;目的:将线程要运行的代码存放在该run方法中;
3,通过Thread类建立线程对象;
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数;原因是:run方法是属于Runnable接口的子类对象,所以要让线程指定所指定对象的run方法,就必须明确run方法所属的对象;
5,调用Thread类的run方法,启动线程,并调用Runnable接口子类的run方法。
示例:
需求:模拟火车站窗口购票系统,一共100张票;
分析:利用多线程的原理,火车票多个窗口同时在卖一定数量的火车票,当窗口1卖了1号座位的车票,其他窗口就不能再卖1号座位的票了;那个窗口卖的是几号座位的票取决于cpu的执行顺序,多个窗口卖火车票,相当于多个线程在同时执行;如果使用方式一创建线程必定出问题,假设四个窗口,每个窗口都要创建一个Thread子类的对象,这样一共就是400张票。如果只创建一个对象,让该对象运行四次,那么运行时必定会出现错误提示,线程状态错误。所以使用这种方式创建时不可以的。解决方法是使用第二种创建线程的方式:
代码如下:
class Demon2 implements Runnable { private int tickets = 100; Object obj = new Object(); public void run() { while(true) { if(tickets>0) { System.out.println(Thread.currentThread() + "print ticket no:" + tickets --); } } }}class ThreadTest2 { public static void main(String[] args) { //创建Runnable接口子类对象 Demon2 d = new Demon2(); //创建线程对象,将Runnable接口的子类对象传给线程 Thread t1 = new Thread(d); Thread t2 = new Thread(d); Thread t3 = new Thread(d); Thread t4 = new Thread(d); //启动线程 t1.start(); t2.start(); t3.start(); t4.start(); }}
总结:实现方式和继承方式的区别:
继承Thread,线程代码存放在Tread子类的的run方法中。
实现Runnable,线程代码存放在接口的子类的run方法中。
实现的好处:避免了单线程的局限性。定义线程的时候,第一种方式不建议使用。建议使用第二种方式。
四,线程的同步:
1,上述卖票系统,在判断tickets之后让该线程睡眠1秒钟,这时候通过分析发现会打印出0,-1,-2,-3等错误座位,这就是多线程存在的安全问题。原因:当多条语句在操作同一个线程共享数据时,一个线程的多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误;
解决方法:
对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中,其他线程不可以参与进来执行;
2,Java对多线程的安全问题有专门的解决方法:就是同步代码块;
synchronized(对象) {
需要被同步的代码块
}
对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获得cpu的执行权,也进不去,因为没有锁;哪些代码需要同步,就看哪些语句在操作共享数据;
3,同步的前提是:
(1)要有两个或两个以上的线程;(2)必须是多个线程使用同一个锁;(3)必须保证同步中只能有一个线程在执行;
请看下面示例:class Demon2 implements Runnable { private int tickets = 100; Object obj = new Object(); public void run() { while(true) { synchronized(obj) { //重点部分,加锁了。每个线程进来之前都会判断该锁是否开启, //如果开启就进入,然后将锁关闭,这样后来的线程就没法进入,等之前进来的程序执行完后才能进来 if(tickets > 0) { try{Thread.sleep(10);}catch(Exception e) {} System.out.println(Thread.currentThread() + "print ticket no:" + tickets --); } } } }}class ThreadTest2 { public static void main(String[] args) { Demon2 d = new Demon2(); Thread t1 = new Thread(d); Thread t2 = new Thread(d); t1.start(); t2.start(); }}
4,在函数上加锁:
虽然锁的方法有两种,就是锁住共享数据部分或者锁住函数,但是这里如果直接给函数上锁的话,一旦一个线程进去之后就不能出来,所以不能直接在run函数上加锁,要先将共享数据封装到一个函数内部,然后多该封装函数加锁。
class Demon2 implements Runnable { private int tickets = 1000; //Object obj = new Object(); boolean flag = true; public void run() { if(flag) { while(true) { synchronized(this) { //如果改成自定义的obj,那么这两个进程使用的锁就不是同一个锁,不满足同步的条件 if(tickets > 0) { try{Thread.sleep(10);}catch(Exception e) {} System.out.println(Thread.currentThread() + "print ticket no:" + tickets --); } } } } else while(true){ show(); } } public synchronized void show() {//这里的锁使用的对象时this if(tickets > 0) { try{Thread.sleep(10);}catch(Exception e) {} System.out.println(Thread.currentThread() + "print ticket no:" + tickets --); } }}class ThreadTest2 { public static void main(String[] args) { Demon2 d = new Demon2(); Thread t1 = new Thread(d); Thread t2 = new Thread(d); t1.start();t1.flag = flase; t2.start(); }}
五,结合线程同步的单例设计模式。
//饿汉式class Single { private static final Single s = new Single(); private Single() {} public static Single getInstance() { return s; }}//带延迟加载的懒汉式class Single2 { private static Single2 s = null; private Single2() {} public static Single2 getInstance() { if(s == null) { synchronized (Single2.class) { if(s == null) { s = new Single2(); } } } return s; }}
重点:面试中可能会问到:懒汉式和饿汉式的区别是什么?回答:懒汉式的特点在于延迟加载。懒汉式的延迟加载有没有什么问题?回答:如果是多线程访问时会出现安全问题。解决方法是用同步来解决。用同步代码块和同步函数都可以,但是效率比较低。用双重判断的方式能够解决效率问题。同步的时候的锁是属于该类所属的字节码文件对象。
六,死锁。
所谓的死锁就是指,两个进程各自拿着各自的锁而不是放资源,而每个线程要想运行,就必须拿到对方的锁,这时候就会出现死锁的问题。
关于死锁的示例:
class ThreadDead implements Runnable { private boolean flag; public ThreadDead(boolean flag) { this.flag = flag; } public void run() { while(true) { if(flag) { synchronized (MyLock.lock1) { System.out.println("if lock1 run..."); synchronized (MyLock.lock2) { System.out.println("if lock2 run..."); } } } else { synchronized (MyLock.lock2) { System.out.println("else lock2 run..."); synchronized (MyLock.lock1) { System.out.println("else lock1 run..."); } } } } }}class MyLock { static Object lock1 = new Object(); static Object lock2 = new Object();}public class DeadLock { public static void main(String[] args) { Thread t1 = new Thread(new ThreadDead(true)); Thread t2 = new Thread(new ThreadDead(false)); t1.start(); t2.start(); }}
- 黑马程序员_Java基础_线程基础,创建,同步(单例设计模式的同步),死锁
- 黑马程序员_JAVA基础4_线程,同步,单例
- 黑马程序员_Java基础[26]_线程2、同步代码块、同步函数
- 黑马程序员_JAVA基础_单例模式
- 黑马程序员_Java基础[27]_静态同步函数
- 黑马程序员_Java基础_面向对象,封装,继承,单例设计模式,构造函数,构造代码块
- 黑马程序员_Java基础[11]_单列设计模式
- 黑马程序员_Java基础_装饰设计模式
- 【黑马程序员】java基础_单例设计模式
- 黑马程序员_Java基础[25]_线程
- 黑马程序员_Java基础_线程池
- 黑马程序员_java的线程同步synchronized
- 黑马程序员-JAVA基础-多线程的安全、同步与死锁
- 黑马程序员--java基础--线程,单例设计模式
- 黑马程序员_Java基础_面向对象(Static的使用、对象初始化和调用成员过程、单例设计模式)
- Java基础之多线程(一)--概述、同步、死锁、单例模式
- 黑马程序员_Java基础_枚举 和 单例模式实例
- Java基础——多线程+卖票程序+同步函数-单例设计模式+死锁
- [Wikioi 1220]数字三角形---两种不同的解法(复习)
- KMP字符串模式匹配详解
- 微软中国:对政府禁装Win8感到非常意外
- 时间工具类
- Windows 各种计时函数总结
- 黑马程序员_Java基础_线程基础,创建,同步(单例设计模式的同步),死锁
- 吐槽下CSDN编辑器
- mapreduce编程实例(4)-求中位数和标准差
- 敏捷遇上UML-需求分析及软件设计最佳实践(郑州站 2014-6-7)
- Thinkphp 分页
- Thinkphp 截取字符串和判断字符串长度
- Jquery mousemove、mouseout和 hover 运用
- ASP.NET中常用的26个优化性能方法
- MySql中distinct的用法 查询出某个字段不重复的记录