Java线程

一.程序,进程,线程

1.程序

程序是为完成某种功能,使用计算机语言编写的一系列指令的集合。指的是静态的代码(安装在电脑上的文件)。

2.进程

进程也可以称为程序,但是是运行中的程序。进程是操作系统进行资源分配的最小单位。

3.线程

进程可以进一步细化为线程,线程就是进程中一个最小的执行单元,是cpu进行调度的最小单元。

eg:qq(进程)中的一个聊天窗口。

一个单核的cpu如何运行多个任务?

通过切换进程,但是这样开销会很大。

进程和线程之间的关系:

1.一个进程中可以包含多个线程。(一个运行中的qq可以开多个聊天窗口)

2.一个线程只能隶属于一个进程(qq聊天窗口只能属于qq的),并且线程不能脱离进程独立运行。

3.一个进程中至少包含一个线程(也就是主线程,java中的main方法就是用来启动主线程的)

4.在主线程中可以创建并启动其他的线程。

5.一个进程内所有线程共享该进程的内存资源。

二.创建线程

Thread类

创建一个类必须继承Thread类,并且去重写里面的run()方法,方法体可以是任何执行代码。

注意:Thread 类中的run()方法和start()方法的区别:run()方法只是需要重写的方法,只是一个简单的类中方法,并不是开启线程的方法,start()方法才是真正开启线程的方法,可以实现线程的单独实现。

创建线程方法1:

写一个类去继承 Thread类**重写run()**方法,线程中要执行的任务都写在run()方法中。

线程通过start()开启后,会由操作系统去调用run()方法(本地方法)。

java 复制代码
package com.zyk.Thread.demo1;

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

   }
}


package com.zyk.Thread.demo1;

public class ThreadTest {
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        //调用run()方法和在一个main中没有任何区别,只是调用方法而已,不是开启线程
        //myThread.run();
        // start()才是真正的开启线程
        myThread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main:"+i);
        }
    }
}

运行结果:

创建线程方法2:

通过自定义一个类去实现Runnable接口,重写接口中的run()方法,去定义执行任务。

然后通过new出来的Thread类对象,通过构造方法去创建并开启线程。

java 复制代码
package com.zyk.Thread.demo2;

public class Task implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("自定义线程:"+i);
        }
    }
}



public class ThreadTest {
    public static void main(String[] args) {
        //定义了执行任务
        Task task=new Task();
        //创建线程
        Thread thread=new Thread(task);
        thread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main:"+i);
        }
    }
}

运行结果:

通过实现Runnable接口去创建线程的方法的优点:

1.因为java是单继承,一旦继承一个类就不能再继承其他类,避免了单继承的局限。

2.适合多线程来处理同一份资源时使用。

创建线程方法3:

通过实现Callable接口。

java 复制代码
package com.zyk.Thread.demo9;

import java.util.concurrent.Callable;

public class SumTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Integer sum=0;
        for (int i = 0; i < 100; i++) {
            sum+=i;
        }
        return sum;

    }
}



package com.zyk.Thread.demo9;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SumTask sumTask=new SumTask();
        FutureTask<Integer> futureTask=new FutureTask<>(sumTask);
        Thread thread=new Thread(futureTask);
        thread.start();
        Integer i = futureTask.get();
        System.out.println(i);
    }
}

使用此方法在开启线程时需要借助FutureTask作为中转new线程。

优点:

此方法的call()方法可以有返回值,并且可以去抛出异常。(与第二种创建线程方法的区别

三.Thread常用方法

1.run()方法

用来定义线程中需要执行的任务代码。

2.Thread.currentThread()

获取到当前线程。( 在哪个线程中执行就拿的是哪个线程)

3.myThread.setName(" ");

对线程重新命名。

4.setPriority();

设置线程优先级。(1<=优先级<=10,作用是为操作系统中的调度算法提供的)

5.getId();

获取线程id。

6.getName();

获取线程名字。

7.getPriority();

获取线程优先级。(java中默认为5)

8.getState();

获取线程状态。

9.start();

启动线程。

10.sleep(long millis);

让线程阻塞休眠milis毫秒。

11.join();

等待调用join()方法的线程执行完毕,其他线程再执行。(让其他线程等待当前线程执行完之后再执行)

12.yield();

使当前线程主动让步重新进入就绪队列排队。

四.线程状态

新建态:刚刚创建了一个线程对象,并没有启动。

就绪态(可运行):调用start()之后,线程就进入了就绪态,进入了操作系统的调度队列中。

运行态:获得了cpu的执行权,就进入到了cpu执行。

阻塞态:例如调用了sleep(),有线程调用了join(),线程中调用了Scanner .....

死亡/销毁态:run()方法中任务执行完毕。

状态切换

新建态-->就绪态:调用start()方法。

运行态-->就绪态:1.cpu正常切换线程 2.线程调用yield()

运行态-->销毁态:1.run()正常执行完毕 2.调用了Thread中的stop()方法 3.线程调用中出现异常并没有处理、

运行态-->阻塞态:1.调用sleep() 2.调用join(),注意是除了调用join()线程之外的其他线程进入阻塞态 3.Scanner控制台输入 4.等待同步锁 5.wait()

阻塞态-->就绪态:1.sleep()时间到了 2.调用join()的线程执行完毕 3.控制台输入完毕 4.获取到同步锁 5.其他线程唤醒notify()

五.多线程

在一个程序中创建多个线程执行。

优点:

1.提高了程序的响应速度

2.提高了cpu的利用率

3.改善程序结构,例如将一个大的任务拆分成多个小任务

缺点:

1.线程多了,占用内存

2.cpu开销变大

3.线程之间同时对共享资源的访问会相互影响,如果不加以控制会导致数据错误(eg:买票 抢购 秒杀......)

如何解决此类问题呢 :

排队+锁

几个线程之间要排队,一个个对共享资源进行操作,而不是同时操作。

为了保证数据在方法中被访问的正确性,在访问时加入锁机制。

加锁方式

synchronized关键字

synchronized修饰方法时,同步锁对象不需要我们去提供

1.非静态的方法--锁对象默认为this

2.静态方法--锁对象默认为当前类的class对象(类的对象,一个类的对象只有一个)

java 复制代码
synchronized(同步锁对象){
    //同步代码块
}
//同步锁对象作用:用来记录有没有线程进入到同步代码块之中,如果有线程进入到同步代码块,那么其他线程就不能进入到同步代码块之中,直到上一个线程执行完同步代码块的内容,释放锁之后,其他线程才能进入。
//同步锁对象要求:同步锁对象必须是要唯一的,可以是任意类的对象,多个线程对应一个同步锁对象,因此必须是要用static修饰的.
java 复制代码
public synchronized void print(){

}


eg:
public class TicketThread extends Thread{
    //票数
    static int num=10;
    static Object obj=new Object();

    @Override
    public void run() {
        this.print();
    }

    public static synchronized void print(){
        while(true){
            if(num<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+ "抢到了第" + num + "张票");
            num--;

        }
    }
}

使用类继承Thred类:

java 复制代码
package com.zyk.Thread.demo4;

public class TicketTest {
    public static void main(String[] args) {
        TicketThread t1=new TicketThread();
        t1.setName("窗口1");
        t1.start();

        TicketThread t2=new TicketThread();
        t2.setName("窗口2");
        t2.start();
    }
}
package com.zyk.Thread.demo4;

public class TicketThread extends Thread{
    //票数
    static int num=10;
    static Object obj=new Object();

    @Override
    public void run() {
        this.print();
    }

    public static synchronized void print(){
        while(true){
            if(num<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+ "抢到了第" + num + "张票");
            num--;
        }
    }

/*    @Override
    public void run() {
        while (true){
            //参数为同步锁对象
            synchronized (obj){
                if (num > 0) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
                    num--;
                } else {
                    break;
                }
            }
        }
    }*/
}

实用类实现Runnable接口:

java 复制代码
package com.zyk.Thread.demo5;

public class TicketTest {
    public static void main(String[] args) {
        TicketTask ticketTask=new TicketTask();

        Thread t1=new Thread(ticketTask);
        t1.setName("窗口1");
        t1.start();
        Thread t2=new Thread(ticketTask);
        t2.setName("窗口2");
        t2.start();
    }
}


package com.zyk.Thread.demo5;

public class TicketTask implements Runnable {
    int num = 10;

    @Override
    public void run() {
        synchronized (this) {
            while (true) {
                if (num > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
                    num--;
                } else {
                    break;
                }
            }
        }
    }
}

ReentrantLock类

ReentrantLock是一个java的类

他只能修饰代码块,并且加锁释放锁都是由java代码块实现的。

一般加锁和释放锁需要在 try--finally发代码块中执行,因为如果加锁之后的代码中出现了异常,这个时候锁就必须要关闭。

java 复制代码
package com.zyk.Thread.demo6;

public class TicketTest {
    public static void main(String[] args) {
        TicketThread t1=new TicketThread();
        t1.setName("窗口1");
        t1.start();

        TicketThread t2=new TicketThread();
        t2.setName("窗口2");
        t2.start();
    }
}



package com.zyk.Thread.demo6;

import java.util.concurrent.locks.ReentrantLock;

public class TicketThread extends Thread {
    //票数
    static int num = 10;
    static ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        try {
            reentrantLock.lock();
            while (true) {
                if (num > 0) {
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
                    num--;
                } else {
                    break;
                }
            }
        } finally {
            reentrantLock.unlock();
        }
    }
}

两种加锁方式的区别

1.synchronized 是一个关键字;ReentrantLock是一个类。

2.synchronized既可以修饰代码块,有可以修饰方法。

ReentrantLock只能修饰代码块。

3.synchronized的加锁和释放锁由底层编译后的指令去实现,自动执行,出现异常会自动释放锁。(synchronized属于非公平锁)

ReentrantLock的加锁和释放锁由java代码实现(底层是同步队列,属于公平锁),需要手动操作,并且一般使用try--finally,一旦出现异常保证能释放锁。

线程通信

线程通讯指的是多个线程通过相互牵制,相互调度,既线程间的相互作用。

涉及三个方法:

1.wait(),执行此方法,当前线程进入阻塞状态,并释放同步锁对象。

2.notify(),唤醒线程

3.notifyAll()

wait()、notify()、notifyAll()方法被调用在synchronized 代码块中,且必须使用锁对象调用,才能实现线程之间的相互牵制相互调度。

java 复制代码
package com.zyk.Thread.demo7;

public class PrintNumTest {
    public static void main(String[] args) {
        PrintNumThread p1=new PrintNumThread();
        p1.start();

        PrintNumThread p2=new PrintNumThread();
        p2.start();

    }
}



package com.zyk.Thread.demo7;

public class PrintNumThread extends Thread{
    static int num=1;
    static String obj=new String();
    @Override
    public void run() {
        synchronized (obj){
            while (num<=100){
                obj.notify();
                System.out.println(Thread.currentThread().getName()+":"+num);
                num++;
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

生产消费者问题

java 复制代码
package com.zyk.Thread.demo8;
//消费者线程
public class CustomerThread extends Thread{

    Counter counter;
    public CustomerThread(Counter counter) {
        this.counter=counter;
    }

    @Override
    public void run() {
        try {
            counter.sub();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}


package com.zyk.Thread.demo8;
//生产者线程
public class ProductThread extends Thread{
    Counter counter;
    public ProductThread(Counter counter) {
        this.counter=counter;
    }

    @Override
    public void run() {
        try {
            counter.add();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}



package com.zyk.Thread.demo8;

//柜台类
public class Counter {
    int num=1;

    /**
     * 生产者调用
     * 每次进来的时候都只能是一个线程
     */
    public synchronized void add() throws InterruptedException {
        while (true){
            if(num==1){
                this.wait();
            }else{
                //先唤醒消费者线程
                this.notify();
                num=1;
                System.out.println("生产者生产了一件产品");
                Thread.sleep(1000);
            }
        }
    }

    /**
     * 消费者调用
     */
    public synchronized void sub() throws InterruptedException {
        while (true){
            if(num==0){
                this.wait();
            }else {
                this.notify();
                num=0;
                System.out.println("消费者消费了一件产品");
                Thread.sleep(1000);
            }
        }
    }
}



package com.zyk.Thread.demo8;
//测试类
public class test {
    public static void main(String[] args) {
        Counter counter=new Counter();
        ProductThread productThread=new ProductThread(counter);
        productThread.start();

        CustomerThread customerThread=new CustomerThread(counter);
        customerThread.start();
    }
}

wait()和sleep()方法的区别

wait():

1.是Object类中的方法,用于同步锁对象调用。

2.需要其他的线程进来唤醒(notifiy() notifiyAll())。

3.wait()会自动释放锁。

sleep():

1.是Thread类中的方法。

2.线程在等待休眠时间过之后会自动唤醒。

3.sleep()不会自动释放锁。

相关推荐
爱干饭的boy2 分钟前
Leetcode—454. 四数相加 II(STL的map AND 基础算法)
开发语言·数据结构·c++·算法·leetcode
码农桃子15 分钟前
PyJWT Subject must be a string
开发语言·python·flask
八股文领域大手子24 分钟前
Redis命令详解--集合
java·服务器·数据库·redis·后端·spring·缓存
C++ 老炮儿的技术栈28 分钟前
squirrel语言全面介绍
开发语言·c++·笔记·学习
我欲混吃与等死33 分钟前
LeetCode 21Merge Two Sorted Lists 合并两个排序链表 Java
java·leetcode·链表
williamdsy1 小时前
【JavaScript】记录一个奇怪的问题,前端一次提交注册,后端收到两次接口调用,网络只显示一个register请求
开发语言·前端·javascript
兩尛1 小时前
Spring Boot02(数据库、Redis)02---java八股
java·数据库·spring boot
松树戈1 小时前
vue3配置代理实现axios请求本地接口返回PG库数据【前后端实操】
java·vue.js·typescript·springboot
工业互联网专业1 小时前
基于springboot+vue的网络海鲜市场
java·vue.js·spring boot·毕业设计·源码·课程设计·网络海鲜市场
虾球xz2 小时前
游戏引擎学习第172天
java·学习·游戏引擎