【学习笔记】JAVA多线程理解

内容:Java黑马就业班 P307-334


java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行:同一个优先级,随机选择一个执行。
每个新线程都会开辟一个新的栈空间来执行run方法,cpu可以选择线程执行。

创建多线程程序的方式

创建多线程程序的第一种方式

创建Thread类的子类
java.lang.Thread类是描述线程的类,想要实现多线程程序,就必须继承Thread类

实现步骤:
1.创建一个Thread类的子类

2.在Thread类的子类种重写Thread类中的run方法,设置线程任务(开启线程要做什么)

//这里创建了一个叫MyThread的子类
public class MyThread extends Thread{
	@Override
	public void run(){
		......
	}
}

3.创建Thread类中的子类对象

4.调用Thread类中的方法,开始新的线程,执行run方法

public class Main{
	public static void main(String []args){
		MyThread mt=new MyThread();
		mt.start();
	}
}

void start()使该线程开始执行;Java虚拟机调用该线程的run方法。

结果是两个线程并发地运行;当前线程和另一个线程。

多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动。

创建多线程程序的第二种方式

实现Runnable接口
java.lang.Runnable,Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参数方法
java.lang.Thread类的构造方法
Thread(Runnable target)分配新的Thread对象。
Thread(Runnable target,String name)分配新的Thread对象。

实现步骤:
1.创建一个Runnable接口的实现类

2.在实现类中重写 RunnabLe接口的run方法,设置线程

public class RunnableImp1 implements Runnable{
	@Override
	public void run(){
		......
	}
}

3.创建一个 Runnable接口的实现类对象

4.创建 Thread类对象,构造方法中传递Runnable接口的实现类对象

5.调用 Thread类中的 start方法,开启新的线程执行run方法

public class Main{
	public static void main(String []args){
		RunnableImp1 run=new RunnableImp1();
		Thread t=new Thread(run);
		t.start();
	}
}

两种方式的区别

实现了Runnable接口创建多线程程序的好处:

1.避免了单继承的局限性

(1)一个类只能继承一个类(一个人只能有一个亲爹)类继承了 Thread类就不能继承其他的类
(2)实现了Runnable接口,还可以继承其他的类,实现其他的接口

2.增强了程序的扩展性,降低了程序的耦合性(解耦)

(1)实现 Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
(2)实现类中,重写了run方法:用来设置线程任务
(3)创建Thread类对象,调用 start方法:用来开启新线程

使用匿名内部类实现线程的创建

匿名:没有名字
内部类:写在其他类内部的类

匿名内类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合一步完成
把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成

匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

//格式
	new 父类/接口(){
		重复父类/接口中的方法
	};
public class Main{
	public static void main(String []args){
		//第一种 线程父类是Thread
		new Thread(){
			@Override
			public void run(){
				......
			}
		}.start();
	
		//第二种 线程接口Runnable
		Runnable r=new Runnable(){
			@Override
			public void run(){
				......
			}
		};
		new Thread(r).start();

		//第三种 在第二种的基础上继续匿名
		new Thread(new Runnable(){
			@Override
			public void run(){
				......
			}
		}).start();
	}
}

线程同步机制

一、同步代码块

同步代码块:
synchronized可以用于方法钟的某个区块中,表示只对这个区块的资源实行互斥访问。

synchronized(同步锁){
	需要同步操作的代码
}

同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁
1.锁对象 可以是任意类型
2.多个线程对象 要使用同一把锁

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

注意:

1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行

二、同步方法

同步方法:
使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

public synchronized void method(){
	可能会产生线程安全问题的代码
}

同步锁是谁?
对于非 static方法同步锁就是this。
对于 static方法我们使用当前方法所在类的字节码对象(类名.class)

使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加 synchronized修饰符

三、Lock锁

java.util.concurrent.locks.Lock机制提供了比synchronized代码块和 synchronized方法更广泛的锁定操作。同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock接口中的方法:

void lock() 获取锁
void unlock() 释放锁

java.util.concurrent.locks.ReentrantLock implements lock 接口

使用步骤:
1.在成员位置创建一个 ReentrantLock对象

Lock l=new ReentrantLock ();

2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁

l.lock();

在可能会出现安全问题的代码后调用Loc接口中的方法 unLock释放锁

l.unlock();

线程状态

线程概述图
在这里插入图片描述
Timed Waiting(计时等待)
当我们调用了sleep方法之后,当前执行的线程就进入到”休眠状态“,其实就是所谓的 Timed Waiting

在这里插入图片描述
Timed blocked(锁阻塞)
没争取到锁对象就是锁阻塞状态
在这里插入图片描述
WAITING(无限等待状态)
直接上demo

package demo1;

public class demo1wait {
	/*
	 * 等待唤醒案例:线程之间的通信
	 * 创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态
	 * 创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
	 * 
	 * 注意:
	 * 		顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
	 * 		同步使用的锁对象必须保证唯一
	 * 		只有锁对象才能调用wait方法和notify方法
	*/
	public static void main(String[] args) {
		//创建锁对象,保证唯一
		final Object obj=new Object();
		//创建一个顾客线程(消费者)
		new Thread(){
			@Override
			public void run(){
				//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
				synchronized(obj){
					System.out.println("我要两个菜包!");
					try {
						obj.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println("你家的菜包子真好吃");
				}
			}
		}.start();
		//创建一个老板线程(生产者)
		new Thread(){
			@Override
			public void run(){
				try {
					sleep(5000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
				synchronized(obj){
					System.out.println("老板:您要的两个菜包子来啦!");
					obj.notify();
				}
			}
		}.start();
	}
}

如果此时有两个顾客同时来买包子,老板应该可以一起做,一起给,那就不能用notify方法了,用notifyAll方法。上demo

package demo1;

public class demo1wait {
	/*
	 * 等待唤醒案例:线程之间的通信
	 * 创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态
	 * 创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
	 * 
	 * 注意:
	 * 		顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
	 * 		同步使用的锁对象必须保证唯一
	 * 		只有锁对象才能调用wait方法和notify方法
	*/
	public static void main(String[] args) {
		//创建锁对象,保证唯一
		final Object obj=new Object();
		//创建一个顾客线程(消费者)
		new Thread(){
			@Override
			public void run(){
				while(true){
					synchronized(obj){
						System.out.println("顾客1:我要两个菜包!");
						try {
							obj.wait();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						System.out.println("顾客1:你家的菜包子真好吃");
						System.out.println("--------------------------");
					}
				}
				//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
			}
		}.start();
		new Thread(){
			@Override
			public void run(){
				while(true){
					synchronized(obj){
						System.out.println("顾客2:我要两个菜包!");
						try {
							obj.wait();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
						System.out.println("顾客2:你家的菜包子真好吃");
						System.out.println("--------------------------");
					}
				}
				//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
			}
		}.start();
		//创建一个老板线程(生产者)
		new Thread(){
			@Override
			public void run(){
				while(true){
					try {
						sleep(5000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
					synchronized(obj){
						System.out.println("老板:您要的两个菜包子来啦!");
//						obj.notify();
						obj.notifyAll();
					}
				}
			}
		}.start();
	}
}

线程池

一、线程池概述

线程池是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

在JDK1.5之后,JDK内置了线程池,可直接使用

线程池是个容器,那就是用集合来装的(ArrayList,HashSet,LinkedList,HashMap)

当程序第一次启动的时候创建多个线程保存到一个集合中;当我们想要使用线程的时候就可以从集合中取出来线程使用。
在这里插入图片描述

Thread t=list.remove();//返回被移除的元素,线程只能被一个任务使用
//以LinkedList为例
Thread t=linked.removeFirst();

当我们使用完毕线程,需要把线程归还给线程池

list.add(t);
//以LinkedList为例
linked.addLast(t);

在这里插入图片描述
合理利用线程池的好处

1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个。任务

2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

二、线程池的使用

java.util.concrrent.Executors:线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)

java.util.concurrent.ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task)提交一个Runnable任务用于执行
关闭/销毁线程池的方法
void shutdown()

线程池的使用步骤:
1.使用线程池的工厂类Executors里边提供的静态方法 newFixedThreadpool生产一个指定线程数量的线程池
2.创建一个类,实现 Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService中的方法 submit,传递线程任务(实现类),开启线程,执行run方法调用
4.ExecutonService中的方法 shutdown销毁线程池(不建议执行)

demo
Runnable接口实现类

package demo1;

public class RunnableImp implements Runnable{

	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+"正在运行");
	}

}

主函数类

package demo1;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo1wait {
	public static void main(String[] args) {
		//开启3个线程的线程池
		ExecutorService es=Executors.newFixedThreadPool(3);
		es.submit(new RunnableImp());
		es.submit(new RunnableImp());
		es.submit(new RunnableImp());
		es.submit(new RunnableImp());
		es.submit(new RunnableImp());
		es.submit(new RunnableImp());
		//es.shutdown();
	}
}

运行结果
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章