Java多线程Thread

16 篇文章 0 订阅

多线程实现原理

在这里插入图片描述

线程内存图

在这里插入图片描述

创建多线程的多种方法

自定义Thread子类

class MyThread extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}

实现Runnable接口

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

  1. 创建一个Runnable接口的实现类
  2. 在实现类中重写Runnable接口的run方法,设置线程任务
  3. 创建一个Runnable接口的实现类对象
  4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
  5. 调用Thread类中的start方法,开启新的线程执行run方法
class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+":"+i);
        }
    }
}
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
 thread.start();
 for (int i = 0; i < 100; i++) {
     Thread thread2 = Thread.currentThread();
     System.out.println(thread2.getName()+":"+i);
 }

在这里插入图片描述

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

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

  1. 避免了单继承的局限性
    一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类实现了Runnable接口,还可以继承其他的类,实现其他的接口
  2. 增强了程序的扩展性,降低了程序的耦合性(解耦)
    实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)。
    实现类中,重写了run方法:用来设置线程任务
    创建Thread类对象,调用start方法:用来开启新线程

使用匿名内部类

匿名内部类方式实现线程的创建
匿名:没有名字
内部类:写在其他类内部的类匿名内部类
作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合一步完成
把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
格式:

new父类/接口(){
	重复父类/接口中的方法
};
new Thread(){
  @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+":"+i);
        }
    }
}.start();

或者使用Runnable

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+":"+i);
        }
    }
});
thread.start();

线程的方法

获取线程的名称

获取线程的名称:

  1. 使用Thread类中的方法getName( )
    String getName()返回该线程的名称。
public class Demo02Thread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        new MyThread().start();
        new MyThread().start();

    }
}

class MyThread extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}

效果
交替运行
在这里插入图片描述

  1. 可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
    static Thread currentThread()返回对当前正在执行的线程对象的引用。
public class Demo02Thread {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        MyThread myThread = new MyThread();
        myThread.start();
        new MyThread().start();
        new MyThread().start();


    }
}

class MyThread extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+":"+i);
        }
    }
}

currentThread()可以用于主线程中,因为主线程是没有getName这个方法的。
在这里插入图片描述
**注:**直接使用对象调用run方法是没有创建线程的。我们可以用getName来印证

MyThread myThread = new MyThread();
myThread.run();
myThread.start();
new MyThread().start();
new MyThread().start();

直接用run运行在main线程上的。
在这里插入图片描述

设置线程名称

设置线程的名称:(了解)

  1. 使用Thread类中的方法setName(名字)
    void setName(String name)改变线程名称,使之与参数name 相同。
  2. 创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
    Thread( string name)分配新的Thread 对象。
class MyThread extends Thread{

    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+":"+i);
        }
    }
}

主程序

MyThread myThread = new MyThread();
myThread.setName("诸葛亮");
myThread.start();
MyThread myThread1 = new MyThread("刘备");
MyThread myThread2 = new MyThread("张让");
myThread1.start();
myThread2.start();

效果图:
在这里插入图片描述

线程暂停(睡眠)方法 sleep

public static void sleep(Long millis):使当前正在执行的线程以指定的毫秒数暂停〈暂时停止执行)。毫秒数结束之后,线程继卖执行

for (int i = 0; i < 10; i++) {
  System.out.println(i);
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

因为InterruptedException 是编译期异常,所以我们需要处理。

多线程安全问题

主代码:

SellTicket sellTicket = new SellTicket();
Thread thread = new Thread(sellTicket);
Thread thread2 = new Thread(sellTicket);
Thread thread3 = new Thread(sellTicket);
thread.start();
thread2.start();
thread3.start();

实现Runnable接口类

public class SellTicket implements Runnable{
    public int numberOfTicket = 100;

    @Override
    public void run() {

        while (numberOfTicket>0){
            System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
            numberOfTicket--;
        }
    }
}

运行截图:
在这里插入图片描述
会出现售卖同一张票的情况,这个是异步造成的。

解决线程安全问题

同步代码块

解决线程安全问题的一种方案:使用同步代码块格式:

synchronized(锁对象){
	可能会出现线程安全问题的代码(访问了共享数据的代码)
}
public class SellTicketSynchronized implements Runnable{
    //定义一个共享资源
    public int numberOfTicket = 100;

    //定义一个锁对象
    Object object = new Object();

    @Override
    public void run() {
        //同步代码块
        synchronized (object){
            while (numberOfTicket>0){
                System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
                numberOfTicket--;
            }
        }
    }
}

同步技术的原理:
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法
t0抢到了cpu的执行权执行run方法,遇到synchronized代码块这时t0会检查synchronized代码块是否有锁对象,发现有,就会获取到锁对象,进入到同步中执行
t1抢到了cpu的执行权,执行run方法,遇到synchronized代码块这时t1会检查synchronized代码块是否有锁对象
发现没有,t1线程就会进入到阻塞状态,会一直等待t0线程归还锁对象。一直到t0线程执行完同步中的代码,会把锁对象归还给同步代码块t1才能获取到锁对象进入到同步中执行
总结: 同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步

注意:

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

同步方法

解决线程安全问题的二种方案:
使用同步方法使用步骤:

  1. 把访问了共享数据的代码抽取出来,放到一个方法中
  2. 在方法上添加synchronized修饰符
//	格式:定义方法的格式
修饰符 synchronized 返回值类型方法名(参数列表){
	可能会出现线程安全问题的代码(访问了共享数据的代码
}
public class SellTicketSynchronized02 implements Runnable{
    //定义一个共享资源
    public int numberOfTicket = 100;

    @Override
    public void run() {
        payTicket();
    }

    private synchronized void payTicket() {
        while (numberOfTicket>0){
            System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
            numberOfTicket--;
        }
    }

}

同步方法相当于,同步锁对象用的就是this对象

private void payTicket() {
	synchronized(this) {
		 while (numberOfTicket>0){
            System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
            numberOfTicket--;
        }
	}
}

还可以使用静态的同步方法
静态的同步方法锁对象是谁?不能是this
this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性–>class文件对象(反射)

public class SellTicketSynchronized03 implements Runnable{
    //定义一个共享资源
    public static int numberOfTicket = 100;

    @Override
    public void run() {
        payTicket();
    }

    private static synchronized void payTicket() {
        while (numberOfTicket>0){
            System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
            numberOfTicket--;
        }
    }

}

Lock锁

解决线程安全问题的三种方案: 使用Lock锁
java.util.concurrent.Locks. Lock接口
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
lock接口中的方法:
void lock() 获取锁。
void unlock() 释放锁。
java.util.concurrent.Locks.ReentrantLock implements Lock接口
使用步骤:

  1. 在成员位置创建一个ReentrantLock对象
  2. 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
  3. 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicketLock implements Runnable{
    //定义一个共享资源
    public int numberOfTicket = 100;

    Lock l = new ReentrantLock();

    @Override
    public void run() {
        payTicket();
    }

    private synchronized void payTicket() {
        l.lock();
        while (numberOfTicket>0){
            System.out.println(Thread.currentThread().getName()+"正在卖第"+numberOfTicket+"张票;");
            numberOfTicket--;
        }
        l.unlock();
    }

}

两个线程之间通信

可以使用wait()和notify()
调用wait()和notify()的必须是锁对象
此时是两个不同的线程对同一资源访问,就需要挂起和唤醒操作
Pai

public class Pai {
    public String bin;
    public  String xian;

    public boolean flag = false;
}

PaiStore.java

public class PaiStore extends Thread{
    Pai pai;

    public PaiStore(Pai pai) {
        this.pai = pai;
    }

    @Override
    public void run() {
        int catagroy = 0;
        while (true){
            synchronized (pai){
                if (pai.flag){
                    try {
                        pai.wait(); //如果此时有派了,就不需要做,则进入等待序列
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 被唤醒后
                if (catagroy%2 == 0){
                    pai.bin = "意大利";
                    pai.xian = "水果牛肉";
                    pai.flag = true;
                }else if (catagroy%2 == 1){
                    pai.bin = "美式";
                    pai.xian = "榴莲";
                    pai.flag = true;
                }
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("生产出了"+pai.bin+pai.xian);
                    catagroy++;
                    pai.notify();
                }

            }
        }
    }

}

Consumer.java

public class Consumer extends Thread{
    Pai pai;

    public Consumer(Pai pai) {
        this.pai = pai;
    }

    @Override
    public void run() {

        while (true){
            synchronized (pai){
                if (pai.flag == false){
                    try {
                        pai.wait(); //如果此时没有派了,则进入等待序列
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("顾客吃了"+pai.bin+pai.xian);
                pai.flag = false;
                pai.xian = "";
                pai.bin = "";
                pai.notify();
            }
        }
    }
}

main

Pai pai = new Pai();
PaiStore paiStore = new PaiStore(pai);
Consumer consumer = new Consumer(pai);
paiStore.start();
consumer.start();

obejct类中的方法
void wait()
在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。
void notify ()
唤醒在此对象监视器上等待的单个线程。会继续执行wait方法之后的代码

此时只有生产出了派,顾客才能买来吃,没有则只能等待,等待店铺生产后被唤醒。有派的话,则店铺进入等待,等顾客购买后被唤醒,继续生产。

这里的wait和notify与操作系统里的PV操作类似

注: wait还有带参的方法和sleep的功能类似了。
进入到Timewaiting(计时等待)有两种方式

  1. 使用sLeep(Long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/BLocked状态
  2. 使用wait(Long m)方法, wait方法如果在毫秒值结束之后,还没有被notifyl唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

唤醒的方法:
void notify()唤醒在此对象监视器上等待的单个线程。void notifyALL()唤醒在此对象监视器上等待的所有线程。

线程之间的状态

在这里插入图片描述

线程池

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

线程池的使用步骤:

  1. 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的钱线程池
  2. 创建一个类,实现RunnabLe接口,重写run方法,设置线程任务
  3. 调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值