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

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锁被使用
				}                    //同步中嵌套同步,出现死锁
			}
		}
		else
			while(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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值