Java多线程编程

线程的创建

三种创建线程的方式:

​ 如图所示:创建线程需要从上面几个方法实现线程。最重要的是Runnable接口

Threa类创建线程

线程:是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。

package com.wyx;

public class TestThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("调用线程"+i);
        }
    }
    public static void main(String[] args) {
        TestThread thread = new TestThread();
        /*
        调用start方法线程同时运行,
        调用run方法,走完调用线程后接着走主线程
        */
        thread.start();
        for (int i = 0; i < 500; i++) {
            System.out.println("这里是主线程"+i);
        }
    }
}

实现Runnable接口

声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。

package com.wyx;

public class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("调用线程"+i);
        }
    }
    public static void main(String[] args) {
//        创建线程实现类
        MyThread myThread = new MyThread();
//        将实现类对象放入Thread类中
        Thread thread = new Thread(myThread);
        thread.start();
        for (int i = 0; i < 500; i++) {
            System.out.println("这里是主线程"+i);
        }
    }
}

总结:

​ Thread类和Runable接口都能实现多线程,因为Java实行单继承方式,所以推荐使用Runable接口。

线程不安全性

package com.wyx;

public class TestRunnable implements Runnable{

    private int ticketNums = 20;
    @Override
    public void run() {
        while (true){
            if(ticketNums==0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+":拿到了第" + ticketNums-- + "张票");
        }
    }
    public static void main(String[] args) {
        TestRunnable ticket = new TestRunnable();
        new Thread(ticket,"爸爸").start();
        new Thread(ticket,"妈妈").start();
        new Thread(ticket,"儿子").start();
    }
}

执行以上代码出现了线程的不安全,两个人同时获得了第20张票(利用后面的线程同步解决)

实现Callable接口

  • 实现Callable接口,需要返回值类型。
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务:ExecutorServices ser = Executors.newFixedThreadPool(1)
  • 提交执行:Future result = ser.submit(t1)
  • 获取结果:boolean re = result.get()
  • 关闭服务:ser.shutdownNow();

静态代理模式(线程的底部实现原理)

  • 真实对象和代理对象都需要实现同一个接口
  • 代理对象要代理真实角色

优点:代理对象可以做真实对象做不了的事情。真实对象专注做自己的事情。

Lambda 表达式

函数式编程

优点:

  1. 避免匿名内部类过多
  2. 可以让代码更简洁
  3. 去掉没有意义的代码,只留下核心的逻辑

函数式接口:任何一个接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。如下就是一个函数式接口

public interface Runnable{
    public abstract void run();
}

对于一个函数式接口,我们可以通过 Lambda 表达式创建该接口的对象

package com.wyx;

//推导Lambda表达式
public class TestLambda {
    public static void main(String[] args) {
//        3.获取实例化对象
        Ilike like = null;
//        4.编写lambda表达式重写方法
        like = ()->{
            System.out.println("哈哈哈");
        };
//        5.调用方法
        like.lambda();
/*      当输出方法只有一条语句时,可以省略大括号。
        当传入参数之一一个时可以省略(),多个参数时参数用,隔开可以省略参数类型                
*/
    }
}

//  1.定义接口
interface Ilike{
    void lambda();
}
//  2.定义接口实现类
class Like implements Ilike{
    @Override
    public void lambda() {       
    }
}

线程的状态

停止线程

不推荐使用 JDK 提供的stop()、destroy()方法。

推荐线程自己停下来。

建立使用一个标志位进行终止变量,当flag=false,则终止运行。

线程休眠

sleep(时间)指定当前线程阻塞的毫秒数;

sleep存在异常,使用时需要处理异常;

sleep时间达到后线程进入就绪状态;

sleep可以模拟网络延时,倒计时等。

每一个对象都有一个锁,sleep不会释放锁

线程礼让(yield)

  • 让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 就是让CPU重新调度,礼让不一定成功!看CPU心情

线程强制执行(Join)

  • join合并线程,等待线程执行完成后,在执行其他线程,其他线程阻塞如同插队一样

线程插队:意思是在完成之前必须完成VIP线程,但是从启动线程start方法开始,线程都在运行。

package com.wyx;

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("VIP线程"+i);
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new TestJoin());
        thread.start();
        for (int i = 0; i < 100; i++) {
            if(i == 99){
                try {
//                    线程插队:意思是在完成之前必须完成VIP线程,但是重启动线程到现在都在运行线程。
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main线程"+i);
        }
    }
}

查看线程状态(State)

NEW 未启动状态
RUNNABLE 正在运行
TIMED_WAITING 等待状态
TERMINATED 结束状态

结束后的进程不能重新启动

package com.wyx;

public class TestState {
    public static void main(String[] args) {
        Thread thread = new Thread(()-> {
            for (int i = 0; i < 2; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("等待两秒结束");
        });
//        查看未启动的线程状态
        Thread.State state = thread.getState();
        System.out.println(state);

//        查看启动后线程转台
        thread.start();
        state = thread.getState();
        System.out.println(state);

//        持续查看线程状态
        while (state != Thread.State.TERMINATED){   //只要线程不停止就一种查看
            try {
                Thread.sleep(500);  //这里代表打印的速度
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            state = thread.getState();
            System.out.println(state);
        }
    }
}

线程的优先级

  • 线程优先级用数字表示,范围0~10

  • 优先级高不一定最先跑,但是大多数都是。

  • 使用以下方式改变或获取优先级

    getPriority():获取优先级

    setPriority(int xx):更改优先级

package com.wyx;

public class TestPriority {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5= new Thread(myPriority);

//        先设置优先级在启动取值(0~10)
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(3);
        t3.start();

        t4.setPriority(8);
        t4.start();

        t5.setPriority(10);
        t5.start();
    }

}
class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

守护线程(setDaemon)

即守护没有执行完的线程,所有的小线程执行完后,守护线程也会停止

package com.wyx;

public class TestDaemon {
    public static void main(String[] args) {
        Thread god = new Thread(new god());
        god.setDaemon(true);    //默认是false表示是用户线程,正常线程都是用户线程。。。
        god.start();    //启动守护线程
        new Thread(new you()).start();  //启动用户线程
    }
}
class god implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("守护线程");
        }
    }
}
class you implements  Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("线程未结束");
        }
        System.out.println("用户线程结束");
    }
}

线程同步(解决线程不安全)

由于我们可以通过private关键字来保证数据对象只能被访问,所以Java提供了针对同步方法的关键字:synchronized。它的包括两种用法:synchronized 方法和 synchronized 块

public synchronized void method(){}

synchronized 方法控制对 “对象” 的访问,每个对象对应一把锁,调用对象的锁才能执行此方法,方法一旦执行,就独占该锁,只有运行完成才能返回锁,后面的线程得到了锁才能继续执行。

缺点:将方法声明为 synchronized 将会影响效率,但是线程是安全的

//synchronized 锁方法时默认锁这个方法的对象。即为 synchronized(this){},使用方法如下:
public synchronized void method(){}

//synchronized 块 即锁定代码块中需要修改数据,如需要修改票数,即传入包含票的类
synchronized (包含票数的实例化的对象){
    修改的代码块;
}

Lock(锁)

Lock是显示锁(手动开启和关闭),synchronized 是隐式锁,

Lock只能锁代码块。

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。

ReentrantLock 类实现了Lock锁 使用方法如下:

package com.wyx;

import java.util.concurrent.locks.ReentrantLock;

public class TestRunnable implements Runnable{

    private int ticketNums = 20;
    //定义锁
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
//            加锁
            lock.lock();
            if(ticketNums>0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":拿到了第" + ticketNums-- + "张票");
            }else {
                break;
            }
        }
        lock.unlock(); //解锁
    }
    public static void main(String[] args) {
        TestRunnable ticket = new TestRunnable();
        new Thread(ticket,"爸爸").start();
        new Thread(ticket,"妈妈").start();
        new Thread(ticket,"儿子").start();
    }
}
// 注意因为线程加锁,所以只能是爸爸拿到票。只有爸爸的线程结束才能运行接下来的线程

线程协作

Java 提供了几个方法解决线程之间通信问题

管程法线程通信

简单来说,生产者生产东西,消费者消费东西,存放东西有一个缓冲区,缓冲区满了生产者停止生产,缓冲区空了消费者停止消费。

生产者消费者问题。。

package com.wyx;

//测试生产者消费者模型
public class Test {
    public static void main(String[] args) {
        Container container = new Container();
        new Productor(container).start();
        new Consumer(container).start();
    }
}

//生产者
class Productor extends Thread{
    Container c;

    public Productor(Container c) {
        this.c = c;
    }

    //生产
    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            c.push(new Food(i));
            System.out.println("生产了"+i+"份食物");
        }
    }
}

//消费者
class Consumer extends Thread{
    Container c;

    public Consumer(Container c) {
        this.c = c;
    }

//    消费
    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            System.out.println("消费了第"+c.pop().id+"份食物");
            try {
                Thread.sleep(100); //线程睡眠防止打印速度跟不上,显示先消费后生产
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//食物
class Food {
    int id;

    public Food(int id) {
        this.id = id;
    }
}

//食物暂存区
class Container {
//    容器大小
    Food[] foods = new Food[5];
    int count = 0; //容器计数器

//    生产者放入食物
    public synchronized void push(Food food){
        //如果容器满了,就通知生产者停止生产
        if(count == (foods.length-1)){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 如果没有满,生产者放入产品
        foods[count] = food;
        count++;

        //可以通知消费者消费
        this.notifyAll();
    }
//    消费者消费食物
    public synchronized Food pop(){
        // 判断能否消费
        if (count == 0){
            //等待生产者生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费,则消费
        count--;
        Food food = foods[count];

//        通知生产者生产
        this.notifyAll();
        return food;
    }
}

信号灯法

生产者和消费者相互制约,类似于交通路口,一次只能有一条路的车通过,因此也叫信号灯法。

public class light {
public static void main(String[]args)
{
	Tv tv=new Tv();
	new Player(tv).start();
	new Watcher(tv).start();
}

}
//生产者 演员
class Player extends Thread{
Tv tv;
public Player(Tv tv)
{
	this.tv=tv;
}
public void run()
{
	for(int i=0;i<20;i++)
	{
		if(i%2==0)
		{
			this.tv.play("偶不变");
		}else
		{
			this.tv.play("奇变");
		}
	}
}
}
//消费者 观众
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv)
{
	this.tv=tv;
}
public void run()
{
	for(int i=0;i<20;i++)
	{
		this.tv.watch("无聊");
	}
}
}
//同一个资源 电视
	class Tv {

String voice;
//信号灯
//为真则演员表演,观众等待
//为假则观众观看,演员等待

boolean flag=true;

//表演
public synchronized void play(String voice)
{
	if(!flag)
	{
		try {
			this.wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	System.out.println("表演了:"+voice);
	this.voice=voice;
	
	//表演后
	this.notifyAll();
	this.flag=!this.flag;
}

public void watch(String string) {
	// TODO Auto-generated method stub
	
}

//观看
public synchronized void watch()
{
	if(flag)
	{
		try {
			this.wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	System.out.println("听到了:"+voice);
	
	//观看后:
	this.notifyAll();
	this.flag=!this.flag;
}
}
posted @ 2021-07-27 15:10  橘子有点甜  阅读(108)  评论(0编辑  收藏  举报