黑马程序员--java多线程

本文详细介绍了Java多线程的基础概念、线程创建方式、线程状态、线程安全、静态函数同步、线程间通信以及停止线程等核心内容。通过实践案例,展示了如何在Java中实现多线程的高效并发执行。

------- android培训java培训、期待与您交流! ----------

java基础之多线程

1.多线程概述:

多线程就是在一个进程中有多个线程执行。进程是指正在执行的程序,进程有一个执行顺序,叫执行路径或控制单元。而线程就是进程中一个独立的执行路径。

单核CPU在一个时刻只能执行一个线程,CPU在多个线程间进行着快速切换,产生了同时执行的效果,所以多线程可以提高程序的执行效率。

 

2.线程的创建方式

方式一:继承Thread

1)、自定义线程类继承Thread

2)、复写Thread类的run方法,将自定义线程执行的代码存储到run方法中

3)、创建自定义线程类的对象

4)、调用start方法,启动线程,执行run方法

方式二:实现Runnable接口

1)、定义Runnable接口的实现子类

2)、复写接口的run方法,将线程执行的自定义代码存储到run方法中

3)、创建接口实现子类对象,作为实际参数传递给Thread类构造函数,创建Thread类对象,也就是创建了线程

4)、调用Thread类的start方法,启动线程,执行Runnable接口实现子类中的run方法


/*
 * 练习:创建两个线程,展示交替运行的效果
 * 思路:自定义线程和主线程交替运行。让两个线程分别打印循环语句执行结果,看
 * 是否是交替执行。
 */
public class CreatThread {

	public static void main(String[] args) {
		//Thread01 thread = new Thread01();
		Thread thread = new Thread(new SubRunnable());
		thread.start();
		for(int x = 0;x < 300;x++)
			System.out.println("main-----"+"主线程");
	}
}
class Thread01 extends Thread{
	public void run(){
		for(int x = 0;x < 300;x++)
			System.out.println("thread-----"+"自定义线程");
	}
}
class SubRunnable implements Runnable{
	public void run(){
		for(int x = 0;x < 300;x++)
			System.out.println("thread-----"+"自定义线程");
	}
}

 

两种方式的区别:

继承的方式是将线程运行的代码存储到Thread类的子类中的run方法中

实现的方式是将线程运行的代码存储到Runnable接口的实现子类中的run方法中

实现的方式打破了单继承的局限性

3.线程状态

创建线程,调用start方法,启动线程,线程有了执行资格,但不一定有执行权

临时状态(阻塞):线程有执行资格,但没执行权

冻结状态:线程执行sleep()或wait()时,失去执行资格和执行权,当指定时间到,或者被notify()唤醒,恢复执行资格,进入临时状态。

运行状态:有执行资格和执行权,即CPU正在执行线程

消亡状态:执行stop方法,或者run方法结束

 

4.线程安全

1)、线程安全问题的原因:多个线程共享的数据被多条语句执行,当一个线程执行了一部分语句,另一个线程就开始执行,会造成共享数据错误。

2)、解决方法:同步。也就是,当执行操作多线程共享数据的语句时,在一个线程执行这些语句时不允许其他线程执行,先这个个线程执行完再让另一个线程执行。

a.同步代码块

格式:

synchronized(对象){

需要同步的语句

}

其中的对象就是锁,拿到这个锁的线程才能执行其中的语句,没拿到锁,即使获取了cpu执行权也进不去。

b.同步函数

格式:

在函数前加上synchronized符号即可。

同步函数中的锁是哪个锁呢?同步函数中的锁是函数所属对象的引用,也就是this

3)、同步的前提:

a.必须有两个或两个以上的线程(多个线程)

b.必须是多个线程使用同一个锁(同一个锁)

4)、同步的利弊

保证了线程安全,但是判断锁比较消耗资源

5)、如何寻找线程安全问题

a.明确多线程操作的代码

b.明确多线程共享的数据

c.明确多线程操作的代码中操作共享数据的语句

 

5.静态函数同步

静态函数随着类的加载进入内存,优先于对象存在,静态函数同步无法使用本类对象作为锁,但类加载进内存时是以字节码文件的方式,也就是类类型的对象:类名.class,静态函数同步用的锁就是它!

例如:单例设计模式--懒汉式

有两种同步方式:同步函数和同步代码块

同步函数的方式,当多个线程调用时,每次都需要判断锁

同步代码块和双重判断的方式,可减少判断锁次数,稍微提高些效率

 

6.同步中嵌套同步时可能会发生死锁

JDK1.5的新特性,对线程安全处理的新方式,可以指定等待和被唤醒的线程,可以解决死锁问题

 

7.线程间通信



/*
 * 线程间通信:就是多个线程操作同一个资源,但操作动作不同
 * 练习:有一个资源,有存储和取出两个动作
 * 思路:定义一个资源(资源唯一),自定义两个线程,分别对资源进行
 * 存储和取出操作
 */
public class ThreadCommunicate {

	public static void main(String[] args) {
		Resource r = new Resource();
		// Thread in = new Thread(new InPut(r));
		// Thread out = new Thread(new OutPut(r));
		// in.start();
		// out.start();
		new Thread(new InPut(r)).start();
		new Thread(new OutPut(r)).start();
	}

}

// 定义一个资源,并复写构造函数
class Resource {
	private String name;
	private String sex;
	private boolean flag = false;

	public synchronized void set(String name, String sex) {
		if (flag)
			try {
				wait();
			} catch (Exception e) {
			}
		this.name = name;
		this.sex = sex;
		flag = true;
		notify();
	}

	public synchronized void get() {
		if (!flag)
			try {
				wait();
			} catch (Exception e) {
			}
		System.out.println(name + "----->" + sex);
		flag = false;
		notify();
	}
}

// 定义存线程
class InPut implements Runnable {
	int x = 0;
	private Resource r;

	InPut(Resource r) {
		this.r = r;
	}

	public void run() {
		// TODO Auto-generated method stub
		while (true) {
			if (x == 0)
				r.set("小明", "男");
			else
				r.set("丽丽", "女女");
			x = (x + 1) % 2;
		}
	}
}

class OutPut implements Runnable {
	private Resource r;

	OutPut(Resource r) {
		this.r = r;
	}

	public void run() {
		while (true) {
			r.get();
		}
	}
}

// notifyAll();

/*
 * wait: notify(); notifyAll();
 * 
 * 都使用在同步中,因为要对持有监视器(锁)的线程操作。 所以要使用在同步中,因为只有同步才具有锁。
 * 
 * 为什么这些操作线程的方法要定义Object类中呢? 因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
 * 只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。 不可以对不同锁中的线程进行唤醒。
 * 
 * 也就是说,等待和唤醒必须是同一个锁。
 * 
 * 而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。
 */

生产者和消费者

/*
 * 需求:生产一个商品消费一个商品,两个生产两个消费
 * 思路:定义一个资源(商品工厂),包含生产和消费产品的方法。因为对资源的操作的动作
 * 有两种:生产和消费,所以定义两个线程类对应两中动作。
 */
public class ProducerConsumer {

	public static void main(String[] args) {
		Resources r = new Resources();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();

	}

}
class Resources{
	private int count = 1;
	private String name;
	private boolean flag = false;
	Lock lock = new ReentrantLock();
	Condition condition_pro = lock.newCondition();
	Condition condition_con = lock.newCondition();
	public void set(String name)throws InterruptedException{
		lock.lock();
		try{
		while(flag){
			condition_pro.await();
		}
			this.name = name+count++;
				System.out.println("生产--->"+this.name);
			flag = true;
			condition_con.signal();
		}
		finally{
			lock.unlock();
		}
	}
	public void out()throws InterruptedException{
		lock.lock();
		try{
			while(!flag)
				condition_con.await();
			System.out.println("消费------>"+name);
			flag = false;
			condition_pro.signal();
		}
		finally{
			lock.unlock();
		}
	}
}
class Producer implements Runnable{
	private Resources r;
	public Producer(Resources r) {
		this.r = r;
	}

	@Override
	public void run() {
		while(true){
			try {
				r.set("商品");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
			}
		}
	}
	
}
class Consumer implements Runnable{
	private Resources r;
	public Consumer(Resources r) {
		this.r = r;
	}
	@Override
	public void run() {
		while(true){
			try {
				r.out();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
			}
		}
	}
}


7.停止线程:

原理就是让线程运行的代码结束,也就是让run方法结束。怎么结束run方法呢?一般run方法中会定义循环,所以只要定义循环结束标记,让循环结束即可。如果过程中处于了冻结状态,就不会读到循环标记,这时可以通过interrupt方法强制解除冻结状态,让线程恢复到具备执行资格的状态,可以读到循环结束标记结束循环。

 

8.守护线程:

可以理解为后台线程,调用setDaemon(true)将该线程设置为守护线程,该方法必须在启动线程前调用。当所有线程都是守护线程时,虚拟机退出。

 

9.加入线程:使用join方法可以临时加入一个线程。

A线程执行到B线程的join方法时,A线程失去执行权和执行资格,处于冻结状态,B线程开始执行。只有B线程运行结束后,A线程才恢复执行资格,但不一定马上获得执行权。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值