黑马程序员——多线程

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

Java实现多线程有两种方式

1.继承Thread类

public class MyThread extends Thread{
	private String name;
	public MyThread(String name) {
		this.name = name;
	}
	public void run() {
		for(int i=0;i<5;i++){
			System.out.println(name+"运行,i="+i);
		}
	}
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程A");
		MyThread mt2 = new MyThread("线程B");
		mt1.run();
		mt2.run();
	}
}

上面程序运行完A再运行B,并没有交替运行,此时线程并没有启动,启动线程需要调用Thread的start方法:

public class MyThread extends Thread{
	private String name;
	public MyThread(String name) {
		this.name = name;
	}
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(name+"运行,i="+i);
		}
	}
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程A");
		MyThread mt2 = new MyThread("线程B");
		mt1.start();
		mt2.start();
	}
}

在启动多线程的时候为什么必须调用start(),而不能调用run()呢?

start()方法在Thread中的定义

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        ...
            start0();
        ...
    }
    private native void start0();

以上代码可以发现start()方法被重复调用会报异常。此方法调用了start0();  此方法是调用了操作系统函数,多线程的实现需要依靠底层操作系统支持。


2.实现Runnable接口

和Thread类构造方法结合,来启动线程

class MyThread implements Runnable{
	private String name;
	public MyThread(String name) {
		this.name = name;
	}
	public void run() {
		for (int i = 0; i < 50; i++) {
			System.out.println(name+"运行,"+i);
		}
	}
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程A");
		MyThread mt2 = new MyThread("线程B");
		Thread t1 = new Thread(mt1);
		Thread t2 = new Thread(mt2);
		t1.start();
		t2.start();
	}
}
Thread类和Runnable接口

Thread类是Runnable接口的子类,但他没有完全实现Runnable接口中的run()方法,而是调用了Runnable接口的run方法,也就是说此方法是由Runnable接口的子类完成的,所以如果要通过继承Thread类完成多线程,则必须复写run()方法,以下为部分定义

private Runnable target;
public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}
private void init(ThreadGroup g, Runnable target, String name,
        long stackSize, AccessControlContext acc){
	...
	this.target = target;
	...
}
public void run() {
    if (target != null) {
        target.run();
    }
}
Thread类和Runnable接口的子类同时继承Runnable接口,之后将Runnable接口的子类实例放入Thread类中,这种操作模式和代理设计类似

两者区别

1.继承Thread类不能资源共享 

class MyThread extends Thread{
	private int ticket = 5;
	public void run() {
		for (int i = 0; i < 100; i++) {
			if(ticket > 0){
				System.out.println("卖票:ticket="+ticket--);
			}
		}
	}
	public static void main(String[] args) {
		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		mt1.start();
		mt2.start();
	}
}
两个线程分别卖了各自的5张票

class MyThread implements Runnable{
	private int ticket = 5;
	public void run() {
		for (int i = 0; i < 100; i++) {
			if(ticket > 0){
				System.out.println("卖票:ticket="+ticket--);
			}
		}
	}
	public static void main(String[] args) {
		MyThread mt = new MyThread();
		new Thread(mt).start();
		new Thread(mt).start();
	}
两个线程共卖了5张票

总结:1.使用Runnable接口可以使多个线程共享同一资源

         2. 避免单继承带来的局限


线程的状态

1.创建状态

程序中使用构造方法创建了一个线程对象后,就有了相应的内存空间和其他资源,但还处于不可运行状态。

2.就绪状态

调用start()启动线程后就进入就绪状态,进入线程队列排队,等待cpu服务,已经具备运行条件

3.运行状态

当就绪状态的线程获得处理器资源时就进入了运行状态,自动调用run(),run()定义了线程的操作和功能

4.阻塞状态

当正在执行的线程,被人为挂起suspend(),sleep(),wait()或执行了耗时的I/O操作,会让出cpu并中止自己的执行,进入阻塞状态,不能进入排队队列,只有当引起阻塞的原因解除后,才能转入就绪状态

5.死亡状态

当调用stop()方法时和run()方法执行结束后,线程进入死亡状态,此时不具有继续运行的能力。


线程操作的相关方法

创建线程如果没有为线程指定一个名称,会自动为线程分配一个名称格式为:Thread-x。Thread类中有一个static类型的属性为线程自动命名。

class MyThread implements Runnable{	// 实现Runnable接口
	public void run(){	// 覆写run()方法
		for(int i=0;i<3;i++){
			System.out.println(Thread.currentThread().getName()
					+ "运行,i = " + i) ;	// 取得当前线程的名字
		}
	}
};
public class CurrentThreadDemo{
	public static void main(String args[]){
		MyThread mt = new MyThread() ;	// 实例化Runnable子类对象
		new Thread(mt,"线程").start() ;		// 启动线程
		mt.run() ;	// 直接调用run()方法
	}
};
运行结果

main运行,i = 0
线程运行,i = 0
main运行,i = 1
线程运行,i = 1
main运行,i = 2
线程运行,i = 2

以上程序中,主方法直接通过Runnable接口的子类对象调用其中的run(),主方法也是一个线程,java中所有线程都是同时启动的,哪个线程先抢占到cpu资源,哪个就先执行。
java程序每次运行至少执行2个进程,一个main进程,一个垃圾收集进程。

判断线程是否启动

class MyThread implements Runnable{	// 实现Runnable接口
	public void run(){	// 覆写run()方法
		for(int i=0;i<3;i++){
			System.out.println(Thread.currentThread().getName()
					+ "运行,i = " + i) ;	// 取得当前线程的名字
		}
	}
};
public class ThreadAliveDemo{
	public static void main(String args[]){
		MyThread mt = new MyThread() ;		// 实例化Runnable子类对象
		Thread t = new Thread(mt,"线程");		// 实例化Thread对象
		System.out.println("线程开始执行之前 --> " + t.isAlive()) ;	 // 判断是否启动
		t.start() ;	// 启动线程
		System.out.println("线程开始执行之后 --> " + t.isAlive()) ;	 // 判断是否启动
		for(int i=0;i<3;i++){
			System.out.println("main运行 --> " + i) ;
		}
		// 以下的输出结果不确定
		System.out.println("代码执行之后 --> " + t.isAlive()) ;	 // 判断是否启动
	}
};
主线程运行完了,子线程还在运行

线程的强制运行

线程强制运行期间,其他线程无法运行,必须等待此线程完成后才能执行。

class MyThread implements Runnable{	
	public void run(){	
		for(int i=0;i<50;i++){
			System.out.println(Thread.currentThread().getName()
					+ "运行,i = " + i) ;	
		}
	}
};
public class ThreadJoinDemo{
	public static void main(String args[]){
		MyThread mt = new MyThread() ;	
		Thread t = new Thread(mt,"线程");		
		t.start() ;	
		for(int i=0;i<50;i++){
			if(i>10){
				try{
					t.join() ;	// 线程强制运行
				}catch(InterruptedException e){}
			}
			System.out.println("Main线程运行 --> " + i) ;
		}
	}
};
运行结果:

Main线程运行 --> 8
线程运行,i = 8
Main线程运行 --> 9
线程运行,i = 9
Main线程运行 --> 10
线程运行,i = 10
线程运行,i = 11
线程运行,i = 12

线程的休眠和中断

class MyThread implements Runnable{	
	public void run(){	
		System.out.println("1、进入run()方法") ;
		try{
				Thread.sleep(10000) ;	// 线程休眠10秒
				System.out.println("2、已经完成了休眠") ;
		}catch(InterruptedException e){
			System.out.println("3、休眠被终止") ;
			return ; // 返回调用处
		}
		System.out.println("4、run()方法正常结束") ;
	}
};
public class ThreadInterruptDemo{
	public static void main(String args[]){
		MyThread mt = new MyThread() ;	
		Thread t = new Thread(mt,"线程");		
		t.start() ;	
		try{
				Thread.sleep(2000) ;	// 线程休眠2秒
		}catch(InterruptedException e){
			System.out.println("3、休眠被终止") ;
		}
		t.interrupt() ;	// 中断线程执行
	}
};

一个线程运行时,另外一个线程可以通过interupt()方法中断运行状态,休眠一旦中断会进入catch中的代码。

线程的优先级

setPriority(Thread.MIN_PRIORITY);  //1 
		setPriority(Thread.NORM_PRIORITY);  //5
		setPriority(Thread.MAX_PRIORITY);  //10 

优先级越高,越有可能被先执行

 

Thread.currentThread().getPriority();

main函数的优先级为5

线程的礼让

让其他线程先运行

class MyThread implements Runnable{	
	public void run(){	
		for(int i=0;i<5;i++){
			try{
				Thread.sleep(500) ;
			}catch(Exception e){}
			System.out.println(Thread.currentThread().getName()
					+ "运行,i = " + i) ;	// 取得当前线程的名字
			if(i==2){
				System.out.print("线程礼让:") ;
				Thread.currentThread().yield() ;	// 线程礼让
			}
		}
	}
};
public class ThreadYieldDemo{
	public static void main(String args[]){
		MyThread my = new MyThread() ;	
		Thread t1 = new Thread(my,"线程A") ;
		Thread t2 = new Thread(my,"线程B") ;
		t1.start() ;
		t2.start() ;
	}
};
运行结果

线程A运行,i = 0
线程B运行,i = 0
线程A运行,i = 1
线程B运行,i = 1
线程A运行,i = 2
线程B运行,i = 2
线程A运行,i = 3
线程礼让:线程B运行,i = 3
线程礼让:线程A运行,i = 4
线程B运行,i = 4

同步与死锁

通过Runnable接口实现多线程,多个线程操作同一资源,引发资源同步问题

class MyThread implements Runnable{
	private int ticket = 5 ;	// 假设一共有5张票
	public void run(){
		for(int i=0;i<100;i++){
			if(ticket>0){	// 还有票
				try{
					Thread.sleep(300) ;	// 加入延迟
				}catch(InterruptedException e){
					e.printStackTrace() ;
				}
				System.out.println("卖票:ticket = " + ticket-- );
			}
		}
	}
};
public class SyncDemo01{
	public static void main(String args[]){
		MyThread mt = new MyThread() ;	// 定义线程对象
		Thread t1 = new Thread(mt) ;	// 定义Thread对象
		Thread t2 = new Thread(mt) ;	// 定义Thread对象
		Thread t3 = new Thread(mt) ;	// 定义Thread对象
		t1.start() ;
		t2.start() ;
		t3.start() ;
	}
};
运行结果:

卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 0
卖票:ticket = -1

产生-1的原因是线程中加入了延迟操作,一个线程还没有对票进行减操作之前,其他线程已经将票数减少了。

解决方法

1.同步代码块

class MyThread implements Runnable{
	private int ticket = 5 ;	// 假设一共有5张票
	public void run(){
		for(int i=0;i<100;i++){
			synchronized(this){	// 要对当前对象进行同步
				if(ticket>0){	// 还有票
					try{
						Thread.sleep(300) ;	// 加入延迟
					}catch(InterruptedException e){
						e.printStackTrace() ;
					}
					System.out.println("卖票:ticket = " + ticket-- );
				}
			}
		}
	}
};
public class SyncDemo02{
	public static void main(String args[]){
		MyThread mt = new MyThread() ;	// 定义线程对象
		Thread t1 = new Thread(mt) ;	// 定义Thread对象
		Thread t2 = new Thread(mt) ;	// 定义Thread对象
		Thread t3 = new Thread(mt) ;	// 定义Thread对象
		t1.start() ;
		t2.start() ;
		t3.start() ;
	}
};
2.同步方法

class MyThread implements Runnable{
	private int ticket = 5 ;	
	public void run(){
		for(int i=0;i<100;i++){
			this.sale() ;	// 调用同步方法
		}
	}
	public synchronized void sale(){	// 声明同步方法
		if(ticket>0){	// 还有票
			try{
				Thread.sleep(300) ;	// 加入延迟
			}catch(InterruptedException e){
				e.printStackTrace() ;
			}
			System.out.println("卖票:ticket = " + ticket-- );
		}

	}
};
死锁
当两个线程都在等待彼此先完成,造成了程序的停滞,死锁是在程序运行时出现的

class Zhangsan{	// 定义张三类
	public void say(){
		System.out.println("张三对李四说:“你给我画,我就把书给你。”") ;
	}
	public void get(){
		System.out.println("张三得到画了。") ;
	}
};
class Lisi{	// 定义李四类
	public void say(){
		System.out.println("李四对张三说:“你给我书,我就把画给你”") ;
	}
	public void get(){
		System.out.println("李四得到书了。") ;
	}
};
public class ThreadDeadLock implements Runnable{
	private static Zhangsan zs = new Zhangsan() ;		// 实例化static型对象
	private static Lisi ls = new Lisi() ;		// 实例化static型对象
	private boolean flag = false ;	// 声明标志位,判断那个先说话
	public void run(){	// 覆写run()方法
		if(flag){
			synchronized(zs){	// 同步张三
				zs.say() ;
				try{
					Thread.sleep(500) ;
				}catch(InterruptedException e){
					e.printStackTrace() ;
				}
				synchronized(ls){
					zs.get() ;
				}
			}
		}else{
			synchronized(ls){
				ls.say() ;
				try{
					Thread.sleep(500) ;
				}catch(InterruptedException e){
					e.printStackTrace() ;
				}
				synchronized(zs){
					ls.get() ;
				}
			}
		}
	}
	public static void main(String args[]){
		ThreadDeadLock t1 = new ThreadDeadLock() ;		// 控制张三
		ThreadDeadLock t2 = new ThreadDeadLock() ;		// 控制李四
		t1.flag = true ;
		t2.flag = false ;
		Thread thA = new Thread(t1) ;
		Thread thB = new Thread(t2) ;
		thA.start() ;
		thB.start() ;
	}
};

生产者和消费者

生产者不断生产,消费者不断从生产者取走数据,但由于线程运行的不确定性,会存在2个问题

1.生产者线程向数据存储空间添加可信息的名称,还没有加入信息的内容,程序就切换到了消费者线程,消费者线程把信息的名称和上一个信息的内容联系到一起

2.生产者放了若干次数据,消费者才开始取数据;或者消费者取完一个数据后,还没等到生产者放入新的数据,又重复取出已取过的数据

class Info{	// 定义信息类
	private String name = "zqt";	 // 定义name属性
	private String content = "黑马学员"  ;		// 定义content属性
	public void setName(String name){
		this.name = name ;
	}
	public void setContent(String content){
		this.content = content ;
	}
	public String getName(){
		return this.name ;
	}
	public String getContent(){
		return this.content ;
	}
};
class Producer implements Runnable{	// 通过Runnable实现多线程
	private Info info = null ;		// 保存Info引用
	public Producer(Info info){
		this.info = info ;
	}
	public void run(){
		boolean flag = false ;	// 定义标记位
		for(int i=0;i<50;i++){
			if(flag){
				this.info.setName("zqt") ;	// 设置名称
				try{
					Thread.sleep(90) ;
				}catch(InterruptedException e){
					e.printStackTrace() ;
				}
				this.info.setContent("黑马学员") ;	// 设置内容
				flag = false ;
			}else{
				this.info.setName("itheima") ;	// 设置名称
				try{
					Thread.sleep(90) ;
				}catch(InterruptedException e){
					e.printStackTrace() ;
				}
				this.info.setContent("www.itheima.com") ;	// 设置内容
				flag = true ;
			}
		}
	}
};
class Consumer implements Runnable{
	private Info info = null ;
	public Consumer(Info info){
		this.info = info ;
	}
	public void run(){
		for(int i=0;i<50;i++){
			try{
				Thread.sleep(90) ;
			}catch(InterruptedException e){
				e.printStackTrace() ;
			}
			System.out.println(this.info.getName() + 
				" --> " + this.info.getContent()) ;
		}
	}
};
public class ThreadCaseDemo01{
	public static void main(String args[]){
		Info info = new Info();	// 实例化Info对象
		Producer pro = new Producer(info) ;	// 生产者
		Consumer con = new Consumer(info) ;	// 消费者
		new Thread(pro).start() ;
		new Thread(con).start() ;
	}
};

运行结果

itheima --> 黑马学员
zqt --> www.itheima.com
itheima --> 黑马学员
zqt --> www.itheima.com
itheima --> 黑马学员
itheima --> 黑马学员

解决方法:

定义同步方法,并且不直接调用setter和getter方法

class Info{	// 定义信息类
	private String name = "zqt";	 // 定义name属性
	private String content = "黑马学员"  ;		// 定义content属性
	public synchronized void set(String name,String content){
		this.setName(name) ;	// 设置名称
		try{
			Thread.sleep(300) ;
		}catch(InterruptedException e){
			e.printStackTrace() ;
		}
		this.setContent(content) ;	// 设置内容
	}
	public synchronized void get(){
		try{
			Thread.sleep(300) ;
		}catch(InterruptedException e){
			e.printStackTrace() ;
		}
		System.out.println(this.getName() + 
			" --> " + this.getContent()) ;
	}
	public void setName(String name){
		this.name = name ;
	}
	public void setContent(String content){
		this.content = content ;
	}
	public String getName(){
		return this.name ;
	}
	public String getContent(){
		return this.content ;
	}
};
class Producer implements Runnable{	// 通过Runnable实现多线程
	private Info info = null ;		// 保存Info引用
	public Producer(Info info){
		this.info = info ;
	}
	public void run(){
		boolean flag = false ;	// 定义标记位
		for(int i=0;i<50;i++){
			if(flag){
				this.info.set("zqt","黑马学员") ;	// 设置名称
				flag = false ;
			}else{
				this.info.set("itheima","www.itheima.com") ;	// 设置名称
				flag = true ;
			}
		}
	}
};
class Consumer implements Runnable{
	private Info info = null ;
	public Consumer(Info info){
		this.info = info ;
	}
	public void run(){
		for(int i=0;i<50;i++){
			this.info.get() ;
		}
	}
};
public class ThreadCaseDemo02{
	public static void main(String args[]){
		Info info = new Info();	// 实例化Info对象
		Producer pro = new Producer(info) ;	// 生产者
		Consumer con = new Consumer(info) ;	// 消费者
		new Thread(pro).start() ;
		new Thread(con).start() ;
	}
};
zqt --> 黑马学员
itheima --> www.itheima.cn
itheima --> www.itheima.cn
itheima --> www.itheima.cn
zqt --> 黑马学员
zqt --> 黑马学员
以上代码解决了信息错乱,但仍然存在重复读取的问题,解决这个问题需要用到Object类

等待和唤醒

生产者每生产一个就要等待消费者取走,消费者每取走一个就要等待生产者生产

class Info{	// 定义信息类
	private String name = "zqt";	 // 定义name属性
	private String content = "黑马学员"  ;		// 定义content属性
	private boolean flag = false ;	// 设置标志位
	public synchronized void set(String name,String content){
		if(!flag){
			try{
				super.wait() ;
			}catch(InterruptedException e){
				e.printStackTrace() ;
			}
		}
		this.setName(name) ;	// 设置名称
		try{
			Thread.sleep(300) ;
		}catch(InterruptedException e){
			e.printStackTrace() ;
		}
		this.setContent(content) ;	// 设置内容
		flag  = false ;	// 改变标志位,表示可以取走
		super.notify() ;
	}
	public synchronized void get(){
		if(flag){
			try{
				super.wait() ;
			}catch(InterruptedException e){
				e.printStackTrace() ;
			}
		}
		try{
			Thread.sleep(300) ;
		}catch(InterruptedException e){
			e.printStackTrace() ;
		}
		System.out.println(this.getName() + 
			" --> " + this.getContent()) ;
		flag  = true ;	// 改变标志位,表示可以生产
		super.notify() ;
	}
	public void setName(String name){
		this.name = name ;
	}
	public void setContent(String content){
		this.content = content ;
	}
	public String getName(){
		return this.name ;
	}
	public String getContent(){
		return this.content ;
	}
};
class Producer implements Runnable{	// 通过Runnable实现多线程
	private Info info = null ;		// 保存Info引用
	public Producer(Info info){
		this.info = info ;
	}
	public void run(){
		boolean flag = false ;	// 定义标记位
		for(int i=0;i<50;i++){
		<span style="white-space:pre">	</span>if(i%2==0)
				this.info.set("zqt","黑马学员") ;	// 设置名称
			}else{
				this.info.set("itheima","www.itheima.com") ;	// 设置名称
			}
		}
	}
};
class Consumer implements Runnable{
	private Info info = null ;
	public Consumer(Info info){
		this.info = info ;
	}
	public void run(){
		for(int i=0;i<50;i++){
			this.info.get() ;
		}
	}
};
public class ThreadCaseDemo03{
	public static void main(String args[]){
		Info info = new Info();	// 实例化Info对象
		Producer pro = new Producer(info) ;	// 生产者
		Consumer con = new Consumer(info) ;	// 消费者
		new Thread(pro).start() ;
		new Thread(con).start() ;
	}
};

运行结果:

zqt --> 黑马学员
itheima --> www.itheima.com
zqt --> 黑马学员
itheima --> www.itheima.com
zqt --> 黑马学员

线程的生命周期其他方法

suspend(),resume(),stop() 为 @Deprecated声明 不推荐使用,容易造成死锁

那么如何停止一个线程运行呢?

设置标志位停止线程的运行。

class MyThread implements Runnable{
	private boolean flag = true ;	// 定义标志位
	public void run(){
		int i = 0 ;
		while(this.flag){
			System.out.println(Thread.currentThread().getName()
				+"运行,i = " + (i++)) ;
		}
	}
	public void stop(){
		this.flag = false ;	// 修改标志位
	}
};
public class StopDemo{
	public static void main(String args[]){
		MyThread my = new MyThread() ;
		Thread t = new Thread(my,"线程") ;	
		t.start() ;	
		try{
			Thread.sleep(30) ;
		}catch(Exception e){
			
		}
		my.stop() ;	// 修改标志位,停止运行
	}
};
总结:

线程被暂停的原因:wait(),sleep(),join()

暂停解除的原因:notify(),sleep()时间到

练习:1.设计4个线程对象,两个线程每次对j加一,两个线程每次对j减一

class ThreadTest {
    private int j;
 
    public static void main(String args[]) {
        ThreadTest tt = new ThreadTest();
        Inc inc = tt.new Inc();
        Dec dec = tt.new Dec();
        for (int i = 0; i < 2; i++) {
            Thread t = new Thread(inc);
            t.start();
            t = new Thread(dec);
            t.start();
        }
    }
 
    private synchronized void inc() {
        j++;
        System.out.println(Thread.currentThread().getName() + "-inc:" + j);
    }
 
    private synchronized void dec() {
        j--;
        System.out.println(Thread.currentThread().getName() + "-dec:" + j);
    }
 
    class Inc implements Runnable {
        public void run() {
            for (int i = 0; i < 100; i++) {
                inc();
            }
        }
    }
 
    class Dec implements Runnable {
        public void run() {
            for (int i = 0; i < 100; i++) {
                dec();
            }
        }
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值