文章目录
- 创建多线程程序的方式
- 创建多线程程序的第一种方式
- 创建多线程程序的第二种方式
- 两种方式的区别
- 使用匿名内部类实现线程的创建
- 线程同步机制
- 一、同步代码块
- 二、同步方法
- 三、Lock锁
- 线程状态
- 线程池
- 一、线程池概述
- 二、线程池的使用
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();
}
}
运行结果