多线程
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”
创建一个多线程
第一种创建方法
package com.demo1.stage1;
/**
* 创建多线程方式一:
* 1,创建一个继承Thread的子类
* 2,重写run方法
* 3,创建Thread类的子类的对象
* 4,通过此类对象调用start()*/
public class Main {
public static void main(String[] args) {
//创建子类对象
Demo1 demo1 = new Demo1();
//调用start方法:1,启动当前线程;2,,调用当前线程的run方法
demo1.start();
}
}
class Demo1 extends Thread{
//重写Thread的run方法
@Override
public void run() {
for(int i = 0; i<100; i++){
if (i%2==0){
System.out.println(i);
}
}
}
}
注意:一个子类对象只能有调用一次start()方法,如果想再调用需要再次创建一个新的子类对象。
第二种创建方法
package com.demo1.stage1;
/**
* 创建多线程方式二:
* 1.创建一个实现了Runnable接口的类
* 2.实现类去实现Runnable中的抽象方法:run()
* 3.创建实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.通过Thread类的对象调用start()
* */
public class Demo3 {
public static void main(String[] args) {
//3.创建实例对象
Mytest2 mytest2 = new Mytest2();
//4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
Thread thread = new Thread(mytest2);
//5.通过Thread类的对象调用start()方法:启动线程,调用当前线程的run()方法,》调用了Runnable类型的target的run()方法
thread.start();
}
}
//1.创建一个实现了Runnable接口的类
class Mytest2 implements Runnable{
//2.实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for(int i = 0; i<100; i++){
if (i%2==0){
System.out.println(i);
}
}
}
}
线程常用的方法
- start():启动当前线程;调用当前线程的run()
- run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
- currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setNmae():设置当前线程的名字
- yield():释放当前cpu的执行权
- join():在线程a中调用线程b的join(),此前线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
- stop():强制结束线程生命期,不建议使用
- sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,线程是阻塞状态。
- isAlive():判断当前线程是否存活。
线程的名称获取和设置
public class Demo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"线程1");//设置线程名字
thread.start();
System.out.println(Thread.currentThread().getName()+"正在执行");//获取当前正在执行的线程名字
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在执行");//获取当前正在执行的线程名字
}
线程的休眠
Thread.sleep(1000);//休眠1秒
线程中断
结束线程时,为了安全不能直接调用stop(),外部掐死线程会造成资源不能释放,并且会产生内存垃圾,一个线程是否关闭,应该由线程本身决定。
那么怎么操作来进行线程的中断呢?
可以使用线程的interrupt()方法标记线程在何时何处进行关闭,先观察以下代码:
public class Demo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"线程1");//设置线程名字
thread.start();
System.out.println(Thread.currentThread().getName()+"正在执行");//获取线程名字
thread.interrupt();//标记线程中断
}
}
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
//当程序运行到线程中断的标记处,就会直接跳转到该处
System.out.println("程序已关闭");
return;//关闭此方法
}
System.out.println(Thread.currentThread().getName()+"正在执行");
}
}
}
当程序运行到 thread.interrupt();时,该线程就会自动执行 catch()块的代码,在此代码块中,可以用return来关闭方法从而中断线程。
守护线程
线程.setDaemon(true) 将该线程标记为守护线程,当用户线程结束时,所有标记为守护线程的线程都死亡
public class Demo3 {
public static void main(String[] args) {
//3.创建实例对象
Mytest2 mytest2 = new Mytest2();
//4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
Thread thread = new Thread(mytest2);
//5.通过Thread类的对象调用start()方法:启动线程,调用当前线程的run()方法,》调用了Runnable类型的target的run()方法
thread.setDaemon(true);//设置守护线程,当用户线程结束时,所有的线程死亡
thread.start();
}
}
//1.创建一个实现了Runnable接口的类
class Mytest2 implements Runnable{
//2.实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for(int i = 0; i<100; i++){
if (i%2==0){
System.out.println(i);
}
}
}
}
线程安全问题
以卖票为例,观察以下代码
public class Demo2 {
public static void main(String[] args) {
MyThread2 myThread2 = new MyThread2();
Thread window1 = new Thread(myThread2, "窗口1");
Thread window2 = new Thread(myThread2, "窗口2");
Thread window3 = new Thread(myThread2, "窗口3");
window1.start();
window2.start();
window3.start();
}
}
class MyThread2 implements Runnable{
private int num=10;
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
if(num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName()+"出票成功!余票为:"+num);
}else {
return;
}
}
}
}
输出结果:
看代码,在MyThread类中,明明规定有num大于零的条件才去执行售票的代码,但是输出的结果却出现了负值,这是怎么回事呢?
原来,在这三条线程中,因为执行的代码存在时间差,比如,当num=1时,窗口1的线程判断num是大于零的,于是执行了下面的代码,但是num–的代码还没来得及执行,这一时刻num还是等于1,而窗口2和窗口3接收到的num还是1,满足运行条件,而窗口1的线程执行num–时,数值变为0,窗口2窗口3的线程也继续执行num–,于是就出现了负值。
这就出现了线程安全的问题
解决方案一:同步代码块
同步可理解线程排队机制。
格式:synchronize(锁对象){执行的代码} java的所有对象都可以往里面传。
例如:
class MyThread2 implements Runnable{
private int num=10;
private Object o=new Object();
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
synchronized (o){//同步代码块
if(num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName()+"出票成功!余票为:"+num);
}else {
return;
}
}
}
}
}
这里传入了一个Object的对象,三条线程同时看管着这个锁,当一条线程执行该锁下的代码时,其他线程需要等待执行的完成,释放锁后,其他线程才去争抢执行。
注意,锁对象必须在run()方法外部创建,作为大家共有的对象。
解决方案二:同步方法
与同步代码块的不同的是,同步方法是以方法为单位的加锁,而同步代码块比较细,可以以一行代码加锁。
做法很简单,就是将synchronize修饰到方法中
class MyThread2 implements Runnable{
private int num=10;
@Override
public void run() {
while (true){
boolean a=sale();
}
}
public synchronized boolean sale(){//同步方法,修饰到方法中
if(num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName()+"出票成功!余票为:"+num);
return true;
}else {
return false;
}
}
}
注意:当同时有同步方法和同步代码块或者多个同步方法时,只执行同步代码块或只执行一个同步方法,因为多个线程只看管同一个锁。
解决方案三:显式锁Lock
前面的同步代码块和同步方法都属于隐式锁
显示锁为Lock类,实现类有ReentrantLock,Condition,ReadWriteLock。
实现的方式如下:
class MyThread2 implements Runnable{
private int num=10;
//显式锁
Lock lock=new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();//锁
if(num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName()+"出票成功!余票为:"+num);
}else {
return;
}
lock.unlock();//解锁
}
}
}
公平锁和非公平锁
公平锁:多个线程执行时需要排队,先到先得。
非公平锁:多个线程争相抢夺,谁抢到谁执行,以上三种方案都为非公平锁
定义方式如下:
//显式锁 true:为公平锁 false:为不公平锁
Lock lock=new ReentrantLock(true);
线程死锁
线程a等线程b,线程b等线程a = 线程死锁
观察下面的代码:
/**
* 死锁
* @author 风亦未止
*/
public class Demo3 {
public static void main(String[] args) {
Criminal criminal = new Criminal();
Police police = new Police();
Thread thread = new Thread(new MyThread3(criminal, police));
thread.start();
criminal.say(police);
}
}
//线程
class MyThread3 implements Runnable{
Criminal criminal;
Police police;
public MyThread3(Criminal criminal, Police police) {
this.criminal = criminal;
this.police = police;
}
@Override
public void run() {
police.say(criminal);
}
}
//罪犯
class Criminal{
public synchronized void say(Police p){
System.out.println("罪犯:你放了我,我就放了人质");
p.fun();
}
public synchronized void fun(){
System.out.println("人质放了人,也逃走了");
}
}
//警察
class Police{
public synchronized void say(Criminal c){
System.out.println("警察:你放人质,我就放了你");
c.fun();
}
public synchronized void fun(){
System.out.println("警察救了人,罪犯逃走了");
}
}
输出结果:
现在,两个线程死锁了,fun()方法没能够执行。
分析:创建的线程调用 police.say(criminal); 主线程调用criminal.say(police);。两个线程相互等待对方将锁释放,造成的死锁。
多线程通讯问题
假设:有厨师,服务员;服务员需要等待厨师炒完菜后才能上菜,观察下面代码:
/**
* 厨师与服务员
* @author 风亦未止
*/
public class Demo4 {
public static void main(String[] args) {
Food food = new Food();
Waiter waiter = new Waiter(food);
Cook cook = new Cook(food);
Thread thread = new Thread(cook);
Thread thread1 = new Thread(waiter);
thread.start();
thread1.start();
}
//厨师
static class Cook implements Runnable{
private Food food;
public Cook(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
food.setNameAndTaste("红烧牛肉","香辣味");
}else{
food.setNameAndTaste("麻辣香锅","柠檬味");
}
}
}
}
//服务员
static class Waiter implements Runnable{
private Food food;
public Waiter(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
food.get();
}
}
}
//菜
static class Food{
private String name;
private String taste;
public void setNameAndTaste(String name, String taste){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name=name;
this.taste=taste;
System.out.println("厨师做了菜:"+name+","+taste);
}
public void get(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("服务员拿到了菜:"+name+","+taste);
}
}
}
运行后会发现,红烧牛肉居然出现了柠檬味,或者厨师做了两个菜,服务员只端上一份菜的情况。分析:前者出现的原因是,厨师刚设置了菜名时,菜名就被服务员拿走了。从而导致错乱。
代码修改后:
/**
* 厨师与服务员
* @author 风亦未止
*/
public class Demo4 {
public static void main(String[] args) {
Food food = new Food();
Waiter waiter = new Waiter(food);
Cook cook = new Cook(food);
Thread thread = new Thread(cook);
Thread thread1 = new Thread(waiter);
thread.start();
thread1.start();
}
//厨师
static class Cook implements Runnable{
private Food food;
public Cook(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0){
food.setNameAndTaste("红烧牛肉","香辣味");
}else{
food.setNameAndTaste("麻辣香锅","柠檬味");
}
}
}
}
//服务员
static class Waiter implements Runnable{
private Food food;
public Waiter(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
food.get();
}
}
}
static class Food{
private String name;
private String taste;
boolean flag=true;
public synchronized void setNameAndTaste(String name, String taste){
if (flag) {
this.name=name;
this.taste=taste;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("厨师做了菜:" + name + "," + taste);
flag=false;
this.notifyAll();//唤醒当前所有的线程
try {
this.wait();//当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if (!flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("服务员拿到了菜:" + name + "," + taste);
flag=true;
this.notifyAll();//唤醒当前所有的线程
try {
this.wait();//当前线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程的六种状态
线程刚被创建时,处在NEW状态。
不论如何,最终线程都会走向死亡。
带返回值特殊的线程创建
Callable接口
class MyThread implements Callable<V>
Callable使用步骤
-
编写实现Callable接口,实现call方法
class MyThread5 implements Callable<V>{ @Override public V call() throws Exception { return null; } }
-
创建FutureTask对象,并传入第一步编写的Callable对象
MyThread5 myThread5 = new MyThread5(); FutureTask futureTask = new FutureTask<>(myThread5);
-
通过Thread,启动线程
Thread thread = new Thread(futureTask); thread.start();
可以通过get()方法获取返回值;isDone()方法:判断方法是否执行完毕;cancel()方法:结束线程
线程池Executors
顾名思义,就是专门装入线程的池子。有时候我们需要创建大量的线程,比如一万个线程,正常情况下,就会频繁的创建和结束各个线程,这会大大降低系统的效率。如果使用线程池,可以容纳多个线程,池中的线程可以反复使用,省去了频繁创建线程对象的操作对象,节省了大量的时间和资源。
线程池的好处
- 降低资源的消耗
- 提高响应速度
- 提高线程的可管理性
java中的四种线程池
1.缓存线程池
/**
* 缓存线程池(长度无限制)
* 执行流程:
* 1.判断线程池是否存在空闲线程
* 2.存在则使用
* 3.不存在,则创建线程,并放入线程池,然后使用
* @author 风亦未止
*/
public class Demo5 {
public static void main(String[] args) {
//创建线程池
ExecutorService service= Executors.newCachedThreadPool();
//向线程池中加入新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("我是一条线程:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("我也是一条线程:"+Thread.currentThread().getName());
}
});
}
}
2.定长线程池
/**定长线程池。(长度是指定的数值)
* 任务加入后的执行流程:
* 1.判断线程池是否存在空闲线程
* 2.存在则使用
* 3.不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池,然后使用
* 4,。不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
* @author 风亦未止
*/
public class Demo6 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
3.单线程线程池
/**
* 单线程线程池。
* 执行流程:
* 1.判断线程池的那个线程是否空闲
* 2.空闲则使用
* 3.不空闲,则等待池中的单个线程空闲后使用
* @author 风亦未止
*/
public class Demo7 {
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
线程任务执行完不会自动关闭,此时会等待执行任务,但长时间不执行会自动关闭
4.周期性任务定长线程池
/**周期任务 定长线程池
* 执行流程:
* 1.判断线程池是否存在空闲线程
* 2.存在则使用
* 3.不存在空闲线程池,且线程池未满的情况下,则创建线程并冉福线程池,然后使用
* 4.不存在空闲线程池,且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行,当某个时机触发时,自动执行某任务
* @author 风亦未止
*/
public class Demo8 {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 1 .定时执行一次
* 参数1.定时执行的任务
* 参数2.时长数字
* 参数3.时长数字的时间单位,TimeUnit的常量
*
*/
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},5, TimeUnit.SECONDS);
/**
* 周期执行任务
* 参数1. 任务
* 参数2. 延迟时长数字(第一次执行在什么时间后)
* 参数3.周期时长数字(每隔多久执行一次)
* 参数4.时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
},5,1,TimeUnit.SECONDS);
}
}
Lambda表达式
/**
* Lambda表达式
* 函数式编程思想
* 而面向对象:创建对象调用方法解决问题
*
* @author 风亦未止
*/
public class Demo9 {
public static void main(String[] args) {
//匿名对象
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("输出一个结果");
}
});
thread.start();
//使用Lambda表达式,与上面的效果是一样的
Thread thread1 = new Thread(() -> System.out.println("输出一个结果"));
thread1.start();
}
}
自定义一个:
public class Demo9 {
public static void main(String[] args) {
print(new MyMath() {
@Override
public int sum(int a, int b) {
return a+b;
}
},100,200);
//使用Lambda表达式
//只保留接口参数和抽象方法体,并用->隔开
print((int a,int b) -> {
return a+b;
},200,200);
}
public static void print(MyMath myMath,int a ,int b){
int num=myMath.sum(a,b);
System.out.println(num);
}
interface MyMath{
int sum(int a,int b);
}
}