java----多线程

多线程
一、多线程
疑问神马是进程
:进程是一个正在执行中的程序,每一个进程的执行,都有一个执行的顺序,该顺序就是一个执行路径,或者叫一个控制单元。
疑问神马是线程
:线程就是进程中一个独立的控制单元,线程在控制着进程的执行,一个进程中至少有一个线程。
Java VM启动时会有一个进程java.exe,该进程中至少有一个线程负责Java程序的执行,而且这个线程运行的代码存在于main()方法中,该线程称为主线程

扩展:其实,JVM启动不止一个线程,还有负责垃圾回收机制的线程。
疑问如何在自定义的代码中自定义一个线程呢?
:通过对Api文档的查找,Java已经提供了对线程这类事物的描述,就是Thread类。
步骤
1、定义类继承Thread
2复写Thread类中的run()方法。目的:将自定义的代码存储在run方法中,让线程运行。
3调用线程的start()方法。该方法两个作用:启动线程;调用run()方法。
class Demo extends Thread
{
	public void run(){
		for(int x=0;x<60;x++){
			System.out.println("demo run "+x);
		}
	}
}
class ThreadDemo
{
	public static void main(String []args){
		Demo d = new Demo();
		d.start();
		for(int x=0;x<60;x++){
			System.out.println("Hello World!—"+x);
		}
	}
}

发现:运行结果每一次都不同。因为多个线程都获取CPU的执行权,CPU执行到谁,谁就运行。明确一点:在某一时刻,只能有一个程序在运行,多核除外。CPU在做着快速切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行形容为在互相抢夺CPU执行权,这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,CPU说的算。
疑问 为神马要覆盖run()方法?
Thread类用于描述线程。该类线程就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run()方法。也就是说,Thread类中的run()方法用于存储线程要运行的代码。
start:开启线程并执行该线程的run()方法,调用底层。
练习:创建两个线程,和主线程交替运行。
class Test extends Thread
{
	String name;
	Test(String name){
		this.name = name;
	}
	public void run(){
		for(int x=0;x<60;x++){
			System.out.println(name+"--->"+x);
		}
	}
}
class Demo
{
	public static void main(String []args){
		Test t1 = new Test("one");
		Test t2 = new Test("two");
		t1.start();
		t2.start();
		for(int x=0;x<60;x++){
			System.out.println("main"+"------>"+x);
		}
	}
}
创建线程方式一
继承Thread类
1.子类覆盖父类中的run()方法,将线程运行代码存放在run()方法中。
2.建立子类对象的同时,线程也被创建。
3.通过调用start方法开启线程。
线程的四种状态

临时状态:具备运行资格,但没有执行。
冻结:放弃了执行资格。
sleep()方法:需要指定睡眠时间,单位是毫秒。
一个特殊的状态:就绪,具备了执行资格,但是还没有获取资源。
线程的名称:线程都有自己默认的名称:Thread-编号。该编号从0开始。
currentThread():返回对当前正在执行的线程对象的引用。
getName():返回该线程的名称。
应用
Thread.currentThread().getName();//这样可以获取当前运行线程的名字。
需求:简单的卖票程序,多个窗口同时卖票。
class Ticket implements Runnable
{
	//100张票
	int tickets = 100;
	public void run(){
		while(tickets>0){
			System.out.println(Thread.currentThread().getName()+"sale.."+tickets--);
		}
	}
}
class Demo
{
	public static void main(String []args){
		Ticket ti = new Ticket();
		//窗口1
		Thread t1 = new Thread(ti);
		//窗口2
		Thread t2 = new Thread(ti);
		//窗口3
		Thread t3 = new Thread(ti);
		//窗口4
		Thread t4 = new Thread(ti);
		//窗口1开始卖
		t1.start();
		//窗口2开始卖
		t2.start();
		//窗口3开始卖
		t3.start();
		//窗口4开始卖
		t4.start();
	}
}
创建线程方式二:实现Runnable接口。
步骤
1.定义类实现Runnable接口。
2.覆盖Runnable接口中的run()方法(将线程要运行的代码存放在该run()方法中)。
3.通过Thread类建立线程对象。
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
疑问为神马要将Runnable接口的子类对象传递给Thread的构造函数?
:因为,自定义的run()所属的对象是Runnable接口的子类对象。所以要让线程去执行指定对象的run()方法。就必须明确该run()方法所属对象。
5.调用Thread类的start()方法开启线程,并调用Runnable接口子类的run()方法。
疑问实现方式和继承方式有什么区别(重点)?
:采用实现的方式使得类的扩展能力加强,除了可以继承其他类,还能实现多个接口。线程代码存放在Thread子类的run()方法中。而采用继承的方式,再想继承别的类就不行了。所以说开发中建议使用继承式。线程代码存放在接口的子类run()方法中。
实现方式的好处:避免了单继承的局限性。
多线程的安全问题:当线程多的时候,CPU的切换可能会导致程序出问题。
技巧:利用sleep()放慢模拟CPU切换。
class Ticket implements Runnable
{
	//100张票
	int tickets = 100;
	public void run(){
		while(tickets>0){
			try{Thread.sleep(100);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"sale.."+tickets--);
		}
	}
}
class Demo
{
	public static void main(String []args){
		Ticket ti = new Ticket();
		//窗口1
		Thread t1 = new Thread(ti);
		//窗口2
		Thread t2 = new Thread(ti);
		//窗口3
		Thread t3 = new Thread(ti);
		//窗口4
		Thread t4 = new Thread(ti);
		//窗口1开始卖
		t1.start();
		//窗口2开始卖
		t2.start();
		//窗口3开始卖
		t3.start();
		//窗口4开始卖
		t4.start();
	}
}

分析:当线程运行到Thread.sleep(100);的时候,线程处于冻结状态,这时其他线程抢到了CPU的执行权开始运行,直到把票卖完,该线程结束。这时原来处于冻结状态的的线程恢复了运行,接着执行。就出现了错票(负值)。
解决办法:当多条语句在操作同一个线程的共享数据时,一个线程对多条语句只执行一部分,还没有执行完。另一个线程参与进来执行,导致共享数据的错误。对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。
synchronized(对象){
	需要被同步的代码;
}
class Ticket implements Runnable
{
	int tickets = 100;
	Object obj = new Object();
	public void run(){
		while(tickets>0){
			try{Thread.sleep(100);}catch(Exception e){}
			synchronized(obj){
				if(tickets>0)
					System.out.println(Thread.currentThread().getName()+"sale.."+tickets--);
			}
		}
	}
}
class Demo
{
	public static void main(String []args){
		Ticket ti = new Ticket();
		Thread t1 = new Thread(ti);
		Thread t2 = new Thread(ti);
		Thread t3 = new Thread(ti);
		Thread t4 = new Thread(ti);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

原理synchronized(obj)中的obj相当于,持有锁的线程可以在同步中执行,没有持有锁的线程,即使获取到了CPU的执行权,也进不去。当0线程进来的时候,锁状态为标志由1变为0。然后0线程sleep。这时1线程进来了,判断锁状态,发现为0,进不来。0醒了,执行完后出同步,把标志由0变为1。这时1线程如果拿到了执行权就可以进来了。
同步的前提
1.必须要有两个或者两个以上的线程。
2.必须是多个线程使用同一个锁。
3.必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。
需求:银行有一个金库,有两个储户分别存300元,每次存100,存3次。
class Bank
{
	private int sum;
	public void add(int n){
		sum = sum+n;
		try{Thread.sleep(100);}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 Demo
{
	public static void main(String []args){
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}

目的:该程序是否有安全问题,如果有,如何解决?
1.明确哪些代码是多线程运行代码。
2.明确共享数据。
3.明确多线程运行代码中哪些语句是操作共享数据的。
分析:当程序运行完sum = sum+n;后,CPU切换了,这时另一线程进来了,又做了一次sum = sum+n;。然后CPU又切换到上一个线程,这时打印的是两个线程执行完累加的结果。这就出现了问题。
解决办法:运用同步代码块将操作共享数据的语句封装起来。
class Bank
{
	private int sum;
	Object obj = new Object();
	public void add(int n){
		synchronized(obj){
			sum = sum+n;
			try{Thread.sleep(100);}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 Demo
{
	public static void main(String []args){
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}
也可以使用同步函数,将函数用 synchronized关键字修饰。
class Bank
{
	private int sum;
	Object obj = new Object();
	public synchronized void add(int n){
		sum = sum+n;
		try{Thread.sleep(100);}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 Demo
{
	public static void main(String []args){
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}
同步的两种 表现形式同步函数同步代码块
练习:验证同步函数用的锁是this锁。
采用Object
class Ticket implements Runnable
{
	int tickets = 100;
	boolean flag = true;
	Object obj = new Object();
	public void run(){
		if(flag){
			while(tickets>0){
				synchronized(obj){
					if(tickets>0){
						try{Thread.sleep(100);}catch(Exception e){}
						System.out.println(Thread.currentThread().getName()+"sale-----------"+tickets--);
					}
				}
			}
		}
		else{
			while(tickets>0){
				show();
			}
		}
	}
	public synchronized void show(){
		if(tickets>0){
			try{Thread.sleep(100);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"sale-----"+tickets--);
		}
	}
}
class Demo
{
	public static void main(String []args){
		Ticket ti = new Ticket();
		Thread t1 = new Thread(ti);
		Thread t2 = new Thread(ti);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}
		ti.flag = false;
		t2.start();
	}
}

采用this
class Ticket implements Runnable
{
	int tickets = 100;
	boolean flag = true;
	Object obj = new Object();
	public void run(){
		if(flag){
			while(tickets>0){
				synchronized(this){
					if(tickets>0){
						try{Thread.sleep(100);}catch(Exception e){}
						System.out.println(Thread.currentThread().getName()+"sale-----------"+tickets--);
					}
				}
			}
		}
		else{
			while(tickets>0){
				show();
			}
		}
	}
	public synchronized void show(){
		if(tickets>0){
			try{Thread.sleep(100);}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()+"sale-----"+tickets--);
		}
	}
}
class Demo
{
	public static void main(String []args){
		Ticket ti = new Ticket();
		Thread t1 = new Thread(ti);
		Thread t2 = new Thread(ti);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}
		ti.flag = false;
		t2.start();
	}
}

比较两个锁,发先只有this锁的程序没有出问题。所以说明同步函数使用的是this锁。出问题是因为两这个用的锁不同,不存在限制的问题。用的所相同时,会判断锁,获取不到锁,即使有运行资格,也不会运行。
疑问为神马同步函数用的是this锁?
:函数要被对象调用,每个函数都有一个所属对象的引用,那就是this
疑问如果同步函数被static修饰后,使用的锁是什么呢?
class Ticket implements Runnable
{
	static int tickets = 100;
	boolean flag = true;
	Object obj = new Object();
	public void run(){
		if(flag){
			while(tickets>0){
				synchronized(this){
					if(tickets>0){
						try{Thread.sleep(100);}catch(Exception e){}
						System.out.println("sale------"+tickets--);
					}
				}
			}
		}
		else{
			while(tickets>0){
				show();
			}
		}
	}
	public static synchronized void show(){
		if(tickets>0){
			try{Thread.sleep(100);}catch(Exception e){}
			System.out.println("sale------------"+tickets--);
		}
	}
}
class Demo
{
	public static void main(String []args){
		Ticket ti = new Ticket();
		Thread t1 = new Thread(ti);
		Thread t2 = new Thread(ti);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}
		ti.flag = false;
		t2.start();
	}
}

:通过验证,发现不再是this锁了,因为静态方法中也不可能有this。静态先进入内存,这时还没有对象,所以不应该是this锁,但是一定用该类对应的字节码文件对象。类名.class该对象的类型是class
采用Ticket.clss锁:
class Ticket implements Runnable
{
	static int tickets = 100;
	boolean flag = true;
	Object obj = new Object();
	public void run(){
		if(flag){
			while(tickets>0){
				synchronized(Ticket.class){
					if(tickets>0){
						try{Thread.sleep(100);}catch(Exception e){}
						System.out.println("sale------"+tickets--);
					}
				}
			}
		}
		else{
			while(tickets>0){
				show();
			}
		}
	}
	public static synchronized void show(){
		if(tickets>0){
			try{Thread.sleep(100);}catch(Exception e){}
			System.out.println("sale------------"+tickets--);
		}
	}
}
class Demo
{
	public static void main(String []args){
		Ticket ti = new Ticket();
		Thread t1 = new Thread(ti);
		Thread t2 = new Thread(ti);
		t1.start();
		try{Thread.sleep(10);}catch(Exception e){}
		ti.flag = false;
		t2.start();
	}
}

结论:静态的同步方法,使用的锁是该方法所在类的字节码文件对象:类名.class。同步代码块使用的锁是this锁。
复习:单例模式——懒汉式(面试)
class Test
{
	private static Test t = null;
	private Test(){}
	public static Test getTest(){
		if(t==null){
			synchronized(Test.class){
				if(t==null){
					t = new Test();
				}
			}
		}
		return t;
	}
}
class Demo
{
	public static void main(String []args){
		Test t1 = Test.getTest();
		Test t2 = Test.getTest();
		System.out.println(t1==t2);
	}
}
懒汉式采用双重判断的方式减少判断同步锁,优化了执行效率,不必每次都对锁进行判断。
死锁(重点):死锁通常发生在同步中嵌套同步。
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
{
	public static Object locka = new Object();
	public static Object lockb = new Object();
}
class Demo
{
	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、付费专栏及课程。

余额充值