十二、死锁

王朝学院·作者佚名  2016-08-27  
宽屏版  字体: 小 | 中 | 大 | 超大  

一、线程基本概述1.1、进程和线程

进程:一个应用程序一般都是一个进程,正在进行的程序

每一个进程最少都有一个线程,都有一个执行顺序,该顺序是一个执行路径或者一个控制单元

线程:进程中一个独立的控制单元,线程控制着进程的执行。

windows中的任务管理器,可以查看进程,linux下通过ps命令

线程是进程的最小单位

线程依赖于进程

线程随着进程的创建和创建,随着进程的结束而消亡

如迅雷:可以同时开启多个下载,就是多线程

多个程序同时执行,时CPU在很快的切换,看上去是同时执行,实际上是在CPU在切换执行。

多线程存在的意义:可以让程序中的内容同时执行。

二、继承线程的创建2.1、继承Thread,重写run方法

packagecom.pb.thread.demo1;/***

*@authorDenny

* 继承Thread 类并重写run方法来创建线程类

**/publicclassMyThreadextendsThread {PRivateintcount;/** 重写run 方法

* @seejava.lang.Thread#run()*/@Overridepublicvoidrun(){/** 要运行的代码块或者方法,在这里调用,或者直接写在这里*/while(count<10){

count++;

System.out.println("My thread run方法启动了"+count);

}

}publicstaticvoidmain(String[] args) {//声明线程类MyThread mt=newMyThread();//启动线程,JVM调用run方法mt.start();//主线程for(intx=0;x<10;x++){

System.out.println("main thread"+x);

}

}

}

多个线程都在抢占CPU的执行权,谁抢到,谁就执行

在某一时刻,只能有一个程序在运行

CPU在做着快速切换,以达到看上去是同时运行的效果。

多线程一个特性:随机性,谁抢到谁执行.

三、线程的运行和线程状态

3.1、调用start()方法

使该线程开始执行,Java虚拟机调用该线程的run()方法

为什么要重写run方法.

Thread类用于描述线程。

这个类定义一个功能,用于存储线程要运行的代码。该存储功能就是run方法.

也就是说,Thread类中的run方法是用于存储线程要运行的代码

如果直接调用run()方法,就是调用对象的普通方法一样.,仅仅是对象调用方法.线程创建了并没有运行。

只有start()才可以运行线程。

3.2、线程状态

新生:创建线程 new Thread()或者子类

运行:正在运行的线程,调用start()方法

冻结:已经创建的线程,但非运行状态的线程。sleep(long 毫秒), wait()线程等待,直到notify()唤醒才可以继续运行

临时状态阻塞:具备运行资格,但没有执行权,就是还没有被CPU切换到.

消亡:线程死亡stop(),或者线程运行完成。

四、获取线程对象4.1、获取当前线程对象和名称

继承thread类实现线程,局部变量都有单独的一份不能共享数据

packagecom.pb.thread.demo1;/***

*@authorDenny

* 继承Thread 类并重写run方法来创建线程类

*Thread.currentThread(),获取当前线程对象

*继承thread类实现线程,局部变量都有单独的一份不能共享数据*/publicclassMyThreadextendsThread {publicMyThread(String name){super(name);

}/** 重写run 方法

* @see java.lang.Thread#run()*/@Overridepublicvoidrun(){/** 要运行的代码块或者方法,在这里调用,或者直接写在这里*/for(intx=0;x<10;x++){

System.out.println("My thread run的名字:"+Thread.currentThread().getName()+","+x);

}

}publicstaticvoidmain(String[] args) {//声明线程类MyThread mt1=newMyThread("mt1");//mt1.setName("张三"); 设置线程名称MyThread mt2=newMyThread("mt2");//启动线程,JVM调用run方法mt1.start();

mt2.start();//主线程for(intx=0;x<10;x++){

System.out.println(currentThread().getName()+","+x);

}

}

}

示例:

五、接口实现线程5.1、实现Runnable接口

重写run方法()

实现接口的方式最大的好处是可以共享数据

示例:多窗口同时售票,

packagecom.pb.thread.demo1;/***

*@authorAdministrator 多个窗口同时卖票

**/publicclassTicketimplementsRunnable {privateinttick = 100;publicvoidrun() {while(true) {if(tick > 0) {

System.out.println(Thread.currentThread().getName()+"卖:" + tick--);

}else{

break;

}

}

}publicstaticvoidmain(String[] args) {//声明线程类Ticket ticket=newTicket();//创建线程对象,并将类做为参数Thread t1=newThread(ticket);

t1.setName("一号窗口,");

Thread t2=newThread(ticket);

t2.setName("二号窗口,");

Thread t3=newThread(ticket);

t3.setName("三号窗口,");

Thread t4=newThread(ticket);

t4.setName("四号窗口,");

t1.start();

t2.start();

t3.start();

t4.start();

}

}

结果:

1一号窗口,卖:1002一号窗口,卖:983一号窗口,卖:974一号窗口,卖:965一号窗口,卖:956一号窗口,卖:947二号窗口,卖:998二号窗口,卖:929一号窗口,卖:9310一号窗口,卖:8811一号窗口,卖:8712一号窗口,卖:8613二号窗口,卖:8914二号窗口,卖:8415二号窗口,卖:8316二号窗口,卖:8217二号窗口,卖:8118二号窗口,卖:8019二号窗口,卖:7920二号窗口,卖:7821二号窗口,卖:7722二号窗口,卖:7623二号窗口,卖:7524二号窗口,卖:7425二号窗口,卖:7326二号窗口,卖:7227二号窗口,卖:7128二号窗口,卖:7029二号窗口,卖:6930二号窗口,卖:6831二号窗口,卖:6732二号窗口,卖:6633二号窗口,卖:6534二号窗口,卖:6435二号窗口,卖:6336二号窗口,卖:6237二号窗口,卖:6138二号窗口,卖:6039二号窗口,卖:5940二号窗口,卖:5841二号窗口,卖:5742二号窗口,卖:5643二号窗口,卖:5544二号窗口,卖:5445二号窗口,卖:5346二号窗口,卖:5247二号窗口,卖:5148二号窗口,卖:5049二号窗口,卖:4950二号窗口,卖:4851二号窗口,卖:4752二号窗口,卖:4653二号窗口,卖:4554二号窗口,卖:4455二号窗口,卖:4356二号窗口,卖:4257二号窗口,卖:4158二号窗口,卖:4059二号窗口,卖:3960二号窗口,卖:3861二号窗口,卖:3762二号窗口,卖:3663二号窗口,卖:3564二号窗口,卖:3465二号窗口,卖:3366二号窗口,卖:3267二号窗口,卖:3168二号窗口,卖:3069二号窗口,卖:2970二号窗口,卖:2871二号窗口,卖:2772二号窗口,卖:2673二号窗口,卖:2574二号窗口,卖:2475二号窗口,卖:2376二号窗口,卖:2277二号窗口,卖:2178三号窗口,卖:9079四号窗口,卖:9180三号窗口,卖:1981二号窗口,卖:2082一号窗口,卖:8583二号窗口,卖:1684三号窗口,卖:1785四号窗口,卖:1886三号窗口,卖:1387三号窗口,卖:1188三号窗口,卖:1089三号窗口,卖:990三号窗口,卖:891三号窗口,卖:792三号窗口,卖:693三号窗口,卖:594三号窗口,卖:495三号窗口,卖:396三号窗口,卖:297三号窗口,卖:198二号窗口,卖:1499一号窗口,卖:15100四号窗口,卖:12

两种创建线程方式:

优点

缺点

继承Thread类

1.编写简单

2.可以使用this关键字直接访问当前线程

无法继承其它类

无法实现数据共享

实现Runnable接口

1.可以继承其它类

2.多个线程之间可以使用同一个Runnable对象

3.可以共享数据

编程方式稍微复杂,如需访问当前线程,需要调用Thread类的currentThread()方法

六、线程安全6.1、还是上面的例子

加上sleep(1000)睡觉1秒

packagecom.day10.thread.demo1;publicclassTicketimplementsRunnable {privateintnum=100;

@Overridepublicvoidrun() {while(true){if(num>0){try{

Thread.sleep(1000);//当前线程睡觉1秒,毫秒单位}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+",卖出"+num--+"号票");

}

}

}publicstaticvoidmain(String[] args) {

Ticket t=newTicket();

Thread t1=newThread(t);

Thread t2=newThread(t);

Thread t3=newThread(t);

Thread t4=newThread(t);

t1.start();

t2.start();

t3.start();

t4.start();

}

}

结果:

Picked up JAVA_TOOL_OPTIONS: -javaagent:/usr/share/java/jayatanaag.jar

Thread-0,卖出100号票

Thread-2,卖出99号票

Thread-1,卖出98号票

Thread-3,卖出97号票

Thread-0,卖出96号票

Thread-2,卖出95号票

Thread-1,卖出94号票

Thread-3,卖出93号票

Thread-0,卖出92号票

Thread-2,卖出91号票

Thread-1,卖出90号票

Thread-3,卖出89号票

Thread-0,卖出88号票

Thread-2,卖出87号票

Thread-1,卖出86号票

Thread-3,卖出85号票

Thread-0,卖出84号票

Thread-2,卖出83号票

Thread-1,卖出82号票

Thread-3,卖出81号票

Thread-0,卖出80号票

Thread-2,卖出79号票

Thread-1,卖出78号票

Thread-3,卖出77号票

Thread-0,卖出76号票

Thread-2,卖出75号票

Thread-1,卖出74号票

Thread-3,卖出73号票

Thread-0,卖出72号票

Thread-2,卖出71号票

Thread-1,卖出70号票

Thread-3,卖出69号票

Thread-0,卖出68号票

Thread-2,卖出67号票

Thread-1,卖出66号票

Thread-3,卖出65号票

Thread-0,卖出64号票

Thread-2,卖出63号票

Thread-1,卖出62号票

Thread-3,卖出61号票

Thread-0,卖出60号票

Thread-2,卖出59号票

Thread-1,卖出58号票

Thread-3,卖出57号票

Thread-0,卖出56号票

Thread-2,卖出55号票

Thread-1,卖出54号票

Thread-3,卖出53号票

Thread-0,卖出52号票

Thread-2,卖出51号票

Thread-1,卖出50号票

Thread-3,卖出49号票

Thread-0,卖出48号票

Thread-2,卖出47号票

Thread-1,卖出46号票

Thread-3,卖出45号票

Thread-0,卖出44号票

Thread-2,卖出43号票

Thread-1,卖出42号票

Thread-3,卖出41号票

Thread-0,卖出40号票

Thread-2,卖出39号票

Thread-1,卖出38号票

Thread-3,卖出37号票

Thread-0,卖出36号票

Thread-2,卖出35号票

Thread-1,卖出34号票

Thread-3,卖出33号票

Thread-0,卖出32号票

Thread-2,卖出31号票

Thread-1,卖出30号票

Thread-3,卖出29号票

Thread-0,卖出28号票

Thread-2,卖出27号票

Thread-1,卖出26号票

Thread-3,卖出25号票

Thread-0,卖出24号票

Thread-2,卖出23号票

Thread-1,卖出22号票

Thread-3,卖出21号票

Thread-0,卖出20号票

Thread-2,卖出19号票

Thread-1,卖出18号票

Thread-3,卖出17号票

Thread-0,卖出16号票

Thread-2,卖出15号票

Thread-1,卖出14号票

Thread-3,卖出13号票

Thread-0,卖出12号票

Thread-2,卖出11号票

Thread-1,卖出10号票

Thread-3,卖出9号票

Thread-0,卖出8号票

Thread-2,卖出7号票

Thread-1,卖出6号票

Thread-3,卖出5号票

Thread-0,卖出4号票

Thread-2,卖出3号票

Thread-1,卖出2号票

Thread-3,卖出1号票

Thread-0,卖出0号票Thread-2,卖出-1号票

Thread-1,卖出-2号票

发现多卖票了,

为什么?因为卖票当前线程睡眠了1秒,其它线程就可以把这张票已经卖出了,

条件检查1>0,如果4个人同时检查,条件成立,就继续卖票就出现了,负数

怎么解决?给线程加锁

线程安全问题产生的原因:

1. 多个线程在操作共享的数据。

2. 操作共享数据的线程代码有多条。

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

解决方式:就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

看下面的同步synchronized

七、同步代码块7.1、synchronized

同步代码块的格式:

synchronized(对象){

需要被同步的代码;

}

同步的好处:解决了线程的安全问题。

同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

同步的前提:必须有多个线程并使用同一个锁。

修改上面的代码

packagecom.day10.thread.demo1;publicclassTicketimplementsRunnable {privateintnum=100;

@Overridepublicvoidrun() {while(true){synchronized(this){if(num>0){try{

Thread.sleep(1000);//当前线程睡觉1秒,毫秒单位}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+",卖出"+num--+"号票");

}else{

break;

}

}

}

}publicstaticvoidmain(String[] args) {

Ticket t=newTicket();

Thread t1=newThread(t);

Thread t2=newThread(t);

Thread t3=newThread(t);

Thread t4=newThread(t);

t1.start();

t2.start();

t3.start();

t4.start();

}

}

八、同步函数 8.1、同步方法

packagecom.day10.thread.demo2;publicclassAccount {//余额privatedoubleblance=500;//取款publicvoidwithDraw(intnum){

blance=blance-num;

}publicdoublegetBlance() {returnblance;

}publicvoidsetBlance(doubleblance) {this.blance =blance;

}

}packagecom.day10.thread.demo2;publicclassAccountThreadimplementsRunnable {privateAccount account=newAccount();

@Overridepublicvoidrun() {for(inti = 0; i < 5; i++) {if(account.getBlance()<0){

System.out.println("透支了!!");

}

makWithDraw(100);//每次取100}

}/** 取款的方法*/publicvoidmakWithDraw(intnum){//判断余额是不是大于要取款的数if(account.getBlance()>=num){

System.out.println(Thread.currentThread().getName()+":准备取款!");try{

Thread.sleep(1000);//等待1秒account.withDraw(num);

System.out.println(Thread.currentThread().getName()+":取款完成!,当前余额为:"+account.getBlance());

}catch(InterruptedException e) {

e.printStackTrace();

}

}else{

System.out.println(Thread.currentThread().getName()+" 余额不足以支付当前取款, 余额为: "+account.getBlance());

}

}

}packagecom.day10.thread.demo2;publicclassDemo {publicstaticvoidmain(String[] args) {

AccountThread at=newAccountThread();

Thread t1=newThread(at);

t1.setName("张三");

Thread t2=newThread(at);

t2.setName("张三老婆");

t1.start();

t2.start();

}

}

结果:

张三:准备取款!张三老婆:准备取款!张三:取款完成!,当前余额为:400.0张三:准备取款!张三老婆:取款完成!,当前余额为:300.0张三老婆:准备取款!张三:取款完成!,当前余额为:200.0张三:准备取款!张三老婆:取款完成!,当前余额为:100.0张三老婆:准备取款!张三:取款完成!,当前余额为:0.0张三 余额不足以支付当前取款, 余额为:0.0张三 余额不足以支付当前取款, 余额为:0.0张三老婆:取款完成!,当前余额为:-100.0

透支了!!

张三老婆 余额不足以支付当前取款, 余额为: -100.0

透支了!!

张三老婆 余额不足以支付当前取款, 余额为: -100.0

加上同步关键字

/** 取款的方法*/publicsynchronizedvoidmakWithDraw(intnum){//判断余额是不是大于要取款的数if(account.getBlance()>=num){

System.out.println(Thread.currentThread().getName()+":准备取款!");try{

Thread.sleep(1000);//等待1秒account.withDraw(num);

System.out.println(Thread.currentThread().getName()+":取款完成!,当前余额为:"+account.getBlance());

}catch(InterruptedException e) {

e.printStackTrace();

}

}else{

System.out.println(Thread.currentThread().getName()+" 余额不足以支付当前取款, 余额为: "+account.getBlance());

}

}

结果:

张三:准备取款!张三:取款完成!,当前余额为:400.0张三老婆:准备取款!张三老婆:取款完成!,当前余额为:300.0张三:准备取款!张三:取款完成!,当前余额为:200.0张三:准备取款!张三:取款完成!,当前余额为:100.0张三:准备取款!张三:取款完成!,当前余额为:0.0张三 余额不足以支付当前取款, 余额为:0.0张三老婆 余额不足以支付当前取款, 余额为:0.0张三老婆 余额不足以支付当前取款, 余额为:0.0张三老婆 余额不足以支付当前取款, 余额为:0.0张三老婆 余额不足以支付当前取款, 余额为:0.0

没有发生透支现象

同步代码块:

/** 取款的方法*/publicvoidmakWithDraw(intnum){synchronized(account){//判断余额是不是大于要取款的数if(account.getBlance()>=num){

System.out.println(Thread.currentThread().getName()+":准备取款!");try{

Thread.sleep(1000);//等待1秒account.withDraw(num);

System.out.println(Thread.currentThread().getName()+":取款完成!,当前余额为:"+account.getBlance());

}catch(InterruptedException e) {

e.printStackTrace();

}

}else{

System.out.println(Thread.currentThread().getName()+" 余额不足以支付当前取款, 余额为: "+account.getBlance());

}

}

}

结果同上,没有发生透支现象

同步函数和同步代码块的区别:

1. 同步函数的锁是固定的this。

2. 同步代码块的锁是任意的对象。

建议使用同步代码块。

由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。

九、同步锁9.1、同锁

同步方法同步对象是this,同步代码块也可以使用this来同步

十、静态同步10.1、静态同步

不是this,因为静态方法中也不可以定义this

静态进内存,内存中没有本类对象,但是一定有该类对应的字节码文件对象

静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。

synchronizid(类名.class){ 或者对象.getClass

}

十一、单例模式加上同步懒汉式11.1、懒汉式加锁

packagecom.day10.thread.demo2;/***

*@authordenny

*单例模式

*饿汉式,不存在复生成对象的问题

*懒汉工,加synchronize*/publicclassSingleDemo {privatestaticSingleDemo singleDemo;privateSingleDemo(){

}publicstaticSingleDemo getNewIntance(){if(singleDemo==null){//判断是不是空,不是空就直接返回,是空就加锁synchronized(SingleDemo.class){//加锁//this.getClass() 直接写对象singleDemo.getClass()也行if(singleDemo==null){

singleDemo=newSingleDemo();

}

}

}returnsingleDemo;

}

}

十二、死锁12.1、死锁常见情景之一:同步的嵌套。

classMyLock{staticObject lockA=newObject();staticObject lockB=newObject();

}publicclassDemoimplementsRunnable {privatebooleanflag;publicDemo(booleanflag) {this.flag =flag;

}@Overridepublicvoidrun() {if(flag) {while(true) {synchronized(MyLock.lockA) {

System.out.println("if.........lockA");synchronized(MyLock.lockB) {

System.out.println("if.........lockB");

}

}

}

}else{while(true){synchronized(MyLock.lockB) {

System.out.println("else.........lockB");synchronized(MyLock.lockA) {

System.out.println("else.........lockA");

}

}

}

}

}publicstaticvoidmain(String[] args) {

Thread t1=newThread(newDemo(true));

Thread t2=newThread(newDemo(false));

t1.start();

t2.start();

}

}

建议将要锁的对象,声明为static

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
© 2005- 王朝网络 版权所有