多线程、单例设计模式、死锁

来源:互联网 发布:js 数组 编辑:程序博客网 时间:2024/06/08 13:29

1  多线程

1.1  进程

   进程就是一个正在执行中的程序。

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


1.2  线程:

   线程就是进程中的一个独立的控制单元。

      线程在控制着进程的执行,一个进程中至少有一个线程。


1.3  主线程

     Java虚拟机(JVM)启动的时候会有一个进程java.exe,该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。

其实JVM启动不止一个线程,还有负责垃圾回收机制的线程。


1.4  创建线程:

通过对API的查找,Java已经提供了对线程这类事物的描述,就是Thread类。

 

创建线程的第一种方式:继承Thread类,覆run()方法。

步骤:1,自定义类,并继承Thread类。

          2、重写Thread类中的run()方法。

                目的:将定义的代码存储在run()方法中,让线程运行。

          3、调用线程的start()方法。

               start()方法有两个作用:启动线程,调用run()方法。

代码示例如下:

class Demo extends Thread {  //要创建线程必须继承Thread类public void run(){for(int x=0;x<120;x++)System.out.println("Demo run--"+x);}}class ThreadDemo{public static void main(String[] args){Demo d = new Demo();  //创建好一个线程d.start();   //启动线程,并调用run()方法。//d.run();   //这样,仅仅是对象调用方法,而线程创建了,并没有运行。for(int x=0;x<120;x++)System.out.println("Hello World--"+x);}  }

发现运行结果每一次都不同。

因为多个线程都获取CPU执行权,CPU执行到谁,谁就运行。Demo线程和主线程争夺CPU

明确一点,在某一个时刻,只能有一个程序在运行(多核除外)。

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

我们可以形象的把多线程的运行形容为在互相抢夺CPU的执行权(CPU资源);

这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,CPU说的算。

 

为什么要覆盖run()方法呢?

Thread类用于描述线程;该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run()方法。也就是说Thread类中的run()方法,用于存储线程要执行的代码。

 

线程都有自己默认的名称:

Thread-编号,该编号从0开始。

currentThread();  该方法获取当前线程对象;

getName();  该方法获取线程名称

设置线程名称:serName()或者构造函数。

 

1.5  简单的卖票程序

需求:简单的卖票程序,多个窗口同时卖票。

 

创建线程的第二种方式:实现Runnable接口。

步骤:1、定义类实现Runnable接口。

          2、覆盖Runnable接口中的run()方法。

               将线程要运行的代码存放在该run()方法中。

          3、通过Thread类建立线程对象。

          4、将Runnable接口的子类对象作为实际参数,传递给Thread类的构造函数。

               为什么要将Runnable接口的子类对象传递给Thread的构造函数:

               因为,自定义的run()方法所属的对象是Runnable接口的子类对象。

               所以要让线程去执行指定对象的run()方法,就必须明确该run()方法所属的对象。

          5、调用Thread类的start()方法,启动线程,并调用Runnable接口子类的run方法。

 

第一种和第二种,即实现Runnable接口方式和继承Thread类方式有什么区别?

实现方式的好处:避免了单继承的局限性。

在定义线程时,建议使用实现方式。

两种方式的区别:

继承Thread类:线程代码存放在Thread子类的run()方法中。

实现Runnable接口:线程代码存放在接口的子类的run()方法中。

 

多线程的安全问题:

问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,这时另一个线程参与进来执行,导致了共享数据的错误。

解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

Java对于多线程的安全问题提供了专业的解决方式:同步代码块和同步函数。

 

同步代码块:

synchronized(对象)

{

需要被同步的代码; //即操作共享数据的代码,是需要被同步的代码

}

    对象如同锁,持有锁的线程可以在同步中执行。

没有持有锁的线程即使获取CPU的执行权,也进不去,因为没有获取锁。

 

同步的前提

1、必须要有两个或两个以上的线程。锁住操作共享数据的代码。

2、必须是多个线程使用同一个锁(即同一个对象),必须保证同步中只能有一个线程在运行。

 

好处:解决了多线程的安全问题

弊端:多个线程都需要判断锁,较为消耗资源。

 

卖票程序代码:

class Ticket implements Runnable {//extends Thread {private int tick = 100;Object obj = new Object(); //解决安全问题public void run() {while(true) {synchronized(obj){ //同步代码块,解决安全问题if(tick>0) {try{Thread.sleep(10);}catch(Exception e){} //sleep()抛出一个异常。出现-1、-2,出现安全问题System.out.println(Thread.currentThread().getName()+"sale: "+tick--);}}}}}class ThreadDemo2 {public static void main(String[] args){Ticket t = new Ticket();  //t不是线程,因为与Thread类无关Thread t1 = new Thread(t); //创建一个线程,并把Runnable子类对象传递给Thread构造函数Thread t2 = new Thread(t);Thread t3 = new Thread(t);Thread t4 = new Thread(t);t1.start();t2.start();t3.start();t4.start();System.out.println("---main---");}  }


1.6  同步函数:

synchronized作为修饰符放在函数声明中,此函数就具有同步功能(相当于同步代码块)。

这个被synchronized修饰的函数就是同步函数。

 

同步函数用的哪一个锁呢?

函数需要被对象调用,那么函数都有一个所属对象的引用,就是this

所以同步函数使用的同步锁是this

牢记同步的两个前提,如果加同步后还有问题,就查看是否满足同步到前提。

 

静态同步函数:

如果同步函数被静态修饰后,使用的锁是什么呢?

通过验证,发现不再是this,因为静态方法中也不可以定义this

 

静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。getClass()方法

类名.class  该对象的类型是class

静态的同步方法,使用的同步锁是该方法所在类的字节码文件对象:类名.class

 

需求:银行有一个金库,有两个储户分别存300元,每次存100,存三次。

目的:该程序是否有安全问题,如果有,如何解决?

如何找问题(需要同步的代码怎么找):

1、明确哪些代码是多线程运行代码。

2、明确共享数据。

3、明确多线程运行代码中哪些语句是操作共享数据的。

 

代码:

class Bank {private int sum;//Object obj = new Object();public synchronized void add(int n){  //用synchronized修饰,add为同步函数//synchronized(obj){sum = sum + n;try{Thread.sleep(10);} catch(Exception e){}System.out.println("sum="+sum);//}}}class Cus implements Runnable {private Bank b = new Bank();public void run() {for(int x=0; x<3; x++) {b.add(100);}}}class ThreadDemo3 {public static void main(String[] args) {Cus c = new Cus();Thread t1 = new Thread(c);Thread t2 = new Thread(c);t1.start();t2.start();}  }

2  单例设计模式

设计模式:解决某一类问题最行之有效的方法。

 

单例设计模式:一个类在内存只存在一个对象,想要保证对象唯一。

1、为了避免其他程序过多建立该类对象,先禁止其他程序建立该类对象

2、还为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象。

3、为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式。

 

这三部分怎么用代码体现呢?

1、将构造函数私有化

2、在类中创建一个本类对象

3、提供一个方法可以获取到该对象。

 

对于事物该怎么描述,还怎么描述。

当需要将该事物的对象保证在内存中唯一时,就将以上的三步加上即可。

 

2.1  饿汉式 :

    饿汉式:先初始化对象。

Single类一进内存,就已经创建好了对象。

 

class Single {

      private Single(){}

      private static final Single s = new Single();

      public static Single getInstance() {

              return s;

      }

}

 

2.2  懒汉式

懒汉式:对象是方法被调用时,才初始化,也叫做对象的延时加载。

Single类进内存,对象还没有存在,只有调用了getInstance()方法时,才建立对象。

 

class Single {

      private Single(){}

      private static Single s = null;

      public static Single getInstance() {

            if(s==null)

                s = new Single();

           return s;

      }

}

 

代码示例:

class Single {          //懒汉式延迟加载,会出现多线程安全问题,用同步锁解决private Single(){}private static Single s = null;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(Single.getInstance().getClass());}}

3  死锁

死锁:同步中嵌套同步,会发生死锁。

比如,A锁的同步代码中需要B锁,而B锁的同步代码中需要A锁,

 就会产生冲突,发生死锁。

代码示例:

class Ticket implements Runnable {private int tick = 1000;Object obj = new Object();boolean flag = true;public void run() {if(flag) {while(true) {synchronized(obj) {  //obj锁show();          //需要this锁,而此时this锁被使用}                    //同步中嵌套同步,出现死锁}}elsewhile(true)show();}public synchronized void show() {  //this锁synchronized(obj) {            //需要obj锁,而此时obj锁被使用if(tick > 0) {try{Thread.sleep(10);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"....code: "+tick--);}}}}class DeadLockDemo {public static void main(String[] args) {Ticket t = new Ticket();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();try{Thread.sleep(10);}catch(Exception e){}t.flag = false;t2.start();}}

小练习:写一个死锁程序。

提示:多线程,同步锁,死锁。

class Test implements Runnable {private boolean flag;Test(boolean flag){this.flag = flag;}public void run() {if(flag){synchronized(MyLock.locka){    //使用a锁System.out.println("if locka");synchronized(MyLock.lockb){  //需要b锁,而此时b锁在被使用System.out.println("if lockb");}}}else{synchronized(MyLock.lockb){    //使用b锁System.out.println("else lockb");synchronized(MyLock.locka){   //需要a锁,而此时a锁在被使用System.out.println("else locka");}}}}}class MyLock {static Object locka = new Object();static Object lockb = new Object();}class DeadLockTest{public static void main(String[] args){Thread t1 = new Thread(new Test(true));Thread t2 = new Thread(new Test(false));t1.start();t2.start();}}




0 0
原创粉丝点击