java多线程学习笔记

一、线程简介

在操作系统中的运行的程序就是进程,而进程通常采用多进程执行

  • 程序

    指令和数据的有序集合,本身没有任何运行的含义,是静态概念

  • 进程

    是执行程序的一次执行过程,是一个动态概念,是系统资源分配的单位

  • 线程

    一个进程通常包括多个线程,线程是CPU调度和执行的单位

二、线程的实现(重点)

调用方法与调用多线程的区别

run()方法是需要执行的方法体

start()方法是开启多线程的方法体

Thread 类

1.thread使用方法

不建议使用:避免OOP单继承局限性

  1. 自定义线程类继承Thread类
  2. 重写run方法,编写线程执行体
  3. 主线程中创建线程对象,使用start方法启动线程

2.代码实现

class TestThread extends Thread{
    @Override
    public void run() {
        //在run()方法中编写执行体
        for (int i = 0; i < 100; i++) {
            System.out.println("在子线程中执行--" + i);
        }
    }
    public static void main(String[] args) {
        //在主线程中创建线程对象,使用start方法启动线程
        TestThread testThread = new TestThread();
        testThread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("在主线程中执行---" + i);
        }
    }
}
//并行交替执行,轮流占用cpu

Runnable 接口

1.Runable使用方法

推荐使用:避免单继承的局限性,方便同一个对象被多个进程调用

  1. 自定义MyRunable类实现Runable接口
  2. 重写run方法,编写线程执行体
  3. 主线程中创建线程对象,使用start方法启动线程

2.代码实现

class TestThread implements Runnable{
    @Override
    public void run() {
        //在run()方法中编写执行体
        for (int i = 0; i < 100; i++) {
            System.out.println("在子线程中执行--" + i);
        }
    }
    public static void main(String[] args) {
        //创建Runable接口的实现类对象
        TestThread testThread = new TestThread();
        //创建线程对象,通过线程对象来开启线程,也就是代理
        Thread thread =  new Thread(testThread);
        thread.start();
        //也可以缩写为:
        //new Thread(testThread).start();
        for (int i = 0; i < 100; i++) {
            System.out.println("在主线程中执行---" + i);
        }

    }
}
//并行交替执行,轮流占用cpu

Callable接口

1.allable使用方法

(仅作了解即可)

通过服务提交线程

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

2.代码实现

class TestCallable implements Callable<Boolean> {
    private String url;
    private String filename;
    //构造函数传入所需的url和filename
    public TestCallable(String url, String filename) {
        this.url = url;
        this.filename = filename;
    }
    @Override
    public Boolean call() {
        //调用downLoader来获取文件
        webDownLoader webDownLoader = new webDownLoader();
        webDownLoader.downLoader(url, filename);
        System.out.println(filename + "下载完成");
        return true;
    }
    public static void main(String[] args) {
        //传入三张图图片的url
        TestCallable t1 = new TestCallable("https://i0.hdslb.com/bfs/album/" +
                "481ba607ba31e9932b90e383f3698fec4c1d9577.jpg@518w_1e_1c.jpg", "pic01.jpg");
        TestCallable t2 = new TestCallable("https://i0.hdslb.com/bfs/album/" +
                "98d71302c030aa86258eb17a5db084bfadf8ff39.jpg@518w_1e_1c.jpg", "pic02.jpg");
        TestCallable t3 = new TestCallable("https://i0.hdslb.com/bfs/album/" +
                "70849f611883c3e7feffc730c4f1e7b7173c9695.jpg@518w_1e_1c.jpg", "pic03.jpg");

        //创建执行服务(参数为线程池的大小)
        ExecutorService service = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> r1 = service.submit(t1);
        Future<Boolean> r2 = service.submit(t2);
        Future<Boolean> r3 = service.submit(t3);

        //获取结果(不写应该也是可以的,这里应该是作为一种检验参数)
        try {
            boolean rs1 = r1.get();
            boolean rs2 = r2.get();
            boolean rs3 = r3.get();

            System.out.println(rs1);
            System.out.println(rs2);
            System.out.println(rs3);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        //关闭服务
        service.shutdown();

    }
}

三、线程状态及方法

线程的五大状态

在这里插入图片描述

  1. 创建状态:Thread t = new Thread();
  2. 就绪状态:调用start方法
  3. 运行状态:被cpu调度执行
  4. 阻塞状态:调用sleep、wait或同步锁定时,进入阻塞状态
  5. 死亡状态:线程中断或者结束,一旦死亡就不再启动

线程方法

方法说明
setPriority(int newPriority)更改线程的优先级
static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
void join()等待该线程终止
static void yield()暂停当前正在执行的线程对象,并执行其他线程
void interrupt()中断线程,别用这个方式
boolean isAlive()测试线程是否处于活
1.停止线程
  • 不推荐使用JDK提供的stop()、destroy()。
  • 推荐让线程自己停下来,但也不建议使用死循环
  • 建议使用一个标志位作为终止变量,当flag==false时,线程终止运行
public class TestStop implements Runnable{
    private boolean flag = true;
    @Override
    public void run() {
        int i = 0;                                                    
        while(flag){
            System.out.println("子进程执行中---" + i++);
        }
    }
    //设置个公开方法,利用标志位停止线程
    public void stop(){
        this.flag = false;
    }
    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主进程执行中---" + i);
            if(i == 90){
                testStop.stop();
                System.out.println("子进程结束运行");
            }
        }
    }
}
2.线程休眠
  • sleep()方法的单位是毫秒
  • sleep()存在异常InterruptedException
  • sleep完了线程进入就绪状态,不会直接进入执行态
  • 可以用于模拟网络延时,倒计时等
  • 每个对象都有一个锁,sleep不释放锁
//模拟倒计时
package test;
import java.lang.Math;
import java.util.Scanner;

public class demo {
    public static void main(String[] args) {
        try {
            tenDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void tenDown() throws InterruptedException{
        int num=10;
        while(true){
            Thread.sleep(1000);
            System.out.println(num--);
            if(num<0)break;
        }

    }
}
//打印当前时间
package test;
import java.lang.Math;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class demo {
    public static void main(String[] args) {
        Date startTime = new Date(System.currentTimeMillis());
        while(true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime = new Date(System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3.线程礼让(不一定成功)
package test;
import java.util.Scanner;
public class demo {
    public static void main(String[] args) {
        testyield testyield = new testyield();
        new Thread(testyield,"test1").start();
        new Thread(testyield,"test2").start();
        new Thread(testyield,"test3").start();
    }
}
class testyield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}
4.线程加入

join合并线程,只能是当前线程执行完之后才能执行其他线程,对其他线程造成阻塞

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("子线程执行---" + i);
        }
    }
    public static void main(String[] args) {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();
        for (int i = 0; i < 500; i++) {
            if(i == 200){
                try {
                    //阻塞主线程,使子线程优先执行完
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("主线程执行---" + i);
        }
    }
}

观察线程的状态

Thread.State state = thread.getState();

线程的优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
  • 线程的优先级用数字表示,范围从1~10.
    • Thread.MIN_ PRIORITY= 1;
    • Thread.MAX PRIORITY = 10;
    • Thread.NORM_ PRIORITY= 5;
  • 使用以下方式获取或改变优先级
    • getPriority() ,setPriority(int xxx)

注意:

  1. 先设置优先级再启动
  2. main方法的默认优先级为5
  3. 理论上来说优先级越高的越先执行,哪怕他start更晚

守护线程

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕.
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待…
package test;
import java.util.Random;
import java.util.Scanner;
public class demo {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread_god = new Thread(god);
        thread_god.setDaemon(true);
        thread_god.start();

        Thread thread_you = new Thread(you);
        thread_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 < 36500; i++) {
            System.out.println("你一生都开心的活着");
        }
        System.out.println("=======goodbye world!!=============");
    }
}

上帝明明是个死循环,但是进程还是结束了。

因为这里将上帝线程设置为了守护线程,虚拟机不加以考虑

四、线程同步

并发:同一个对象被多个线程同时操作,也就是不同线程同时操作同一个资源地址,造成数据紊乱

同步:多个需要同时访问资源的线程进入对象的等待池,等待前面线程使用完毕

锁:每个对象都有把锁,当获取对象时,独占资源,其他线程必须等待,使用结束后才释放

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

同步方法与同步块

synchronized关键字

synchronized默认锁的是他自身的对象,要是跨对象,通常使用同步块,即锁共享资源所在的对象

1.同步方法
public synchronized void method(int args){}

因为每个对象对应一把锁,使用加了关键字之后,方法一旦执行就独占该锁

缺陷:若将一个大的方法申明为synchronized将会影响效率

2.同步块
synchronized(Obj){}

Obj称之为同步监视器
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是这个对象本身或者是class
同步监视器的执行过程:
第一个线程访问,锁定同步监视器,执行其中代码
第二个线程访问 ,发现同步监视器被锁定,无法访问
第一个线程访问完毕 ,解锁同步监视器
第二个线程访问,发现同步监视器没有锁,然后锁定并访问

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形. 某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.

产生死锁的四个必要条件:

  1. 互斥条件: 一个资源每次只能被一个进程使用
  2. 请求与保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不剥夺条件: 进程已获得的资源,在末使用完之前,不能强行剥夺
  4. 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系
/*死锁代码*/
package test;
import java.util.Random;
import java.util.Scanner;

//队列 + 锁 保证线程的安全
//synchronized关键字 方法和块 锁的是this
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持.
public class demo {
    public static void main(String[] args) {
        Makeup gl = new Makeup(0,"灰姑凉");
        Makeup g2 = new Makeup(1,"白雪公主");
        gl.start();
        g2.start();
    }
}
//口红
class Lipstick{
}
//镜子
class Mirror {
}
class Makeup extends Thread {
    private int choice;
    private String girlNmae;
    Makeup (int choice , String girlNmae){
        this.choice = choice;
        this.girlNmae = girlNmae;
    }
    //要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    @Override
    public void run() {//化妆
        try {
            makeup();
        } catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    //化妆,互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {//获得口红的锁
                System.out.println(this.girlNmae + "获得口红的锁");
                Thread.sleep(1000);
                synchronized (mirror) { //一秒钟后想获得镜子
                    System.out.println(this.girlNmae + "获得镜子的锁");
                }
            }

        } else {
            synchronized (mirror) {//获得镜子的锁
                System.out.println(this.girlNmae + "获得镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick) {// 两秒钟后想获得口红
                    System.out.println(this.girlNmae + "获得口红的锁");
                }
            }

        }
    }
}


/*解除死锁*/
package test;
import java.util.Random;
import java.util.Scanner;

//队列 + 锁 保证线程的安全
//synchronized关键字 方法和块 锁的是this
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持.
public class demo {
    public static void main(String[] args) {
        Makeup gl = new Makeup(0,"灰姑凉");
        Makeup g2 = new Makeup(1,"白雪公主");
        gl.start();
        g2.start();
    }
}
//口红
class Lipstick{
}
//镜子
class Mirror {
}
class Makeup extends Thread {
    private int choice;
    private String girlNmae;
    Makeup (int choice , String girlNmae){
        this.choice = choice;
        this.girlNmae = girlNmae;
    }
    //要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    @Override
    public void run() {//化妆
        try {
            makeup();
        } catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    //化妆,互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {//获得口红的锁
                System.out.println(this.girlNmae + "获得口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror) { //一秒钟后想获得镜子
                System.out.println(this.girlNmae + "获得镜子的锁");
            }
        } else {
            synchronized (mirror) {//获得镜子的锁
                System.out.println(this.girlNmae + "获得镜子的锁");
                Thread.sleep(2000);
            }
            synchronized (lipstick) {// 两秒钟后想获得口红
                System.out.println(this.girlNmae + "获得口红的锁");
            }
        }
    }
}

lock锁

package test;

import java.util.concurrent.locks.ReentrantLock;

public class demo {
    public static void main(String[] args) {
        Buyticket buyticket = new Buyticket();
        new Thread(buyticket,"线程1").start();
        new Thread(buyticket,"线程2").start();
        new Thread(buyticket,"线程3").start();
    }
}
class Buyticket implements Runnable {
    int ticketNums = 500;
    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//加锁
                if (ticketNums > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在取票"+ticketNums--);
                }
                else break;
            }
            finally {
                lock.unlock();//解锁
            }

        }
    }
}

synchronized 与Lock 的对比
1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
4.优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

五、线程通信

生产者消费者模式

主要是用以下方法达到通信的效果:

  1. wait():先释放资源,并开始等待
  2. wait(long timeout):释放资源等待timeout秒
  3. notify():唤醒一个处于等待状态的线程
  4. notifyAll():唤醒同一个对象上所有调用了wait()方法的线程,优先级高的优先调度
1.管程法
package test;

import java.util.LinkedList;
import java.util.Queue;

public class demo {
    public static void main(String[] args) {
        //创建缓冲区
        SynContainer container = new  SynContainer();

        //双线程
        new Producer(container).start();
        new Consumer(container).start();
    }
}
//生产者
class Producer extends Thread{
    SynContainer container;
    public Producer(SynContainer container) {
        this.container = container;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("生产了第" + i + "件产品");
            container.push(new Products(i));
        }
    }
}

//消费者
class Consumer extends Thread{
    SynContainer container;
    public Consumer(SynContainer container) {
        this.container = container;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第" + container.pop().id + "件产品");
        }
    }
}

//产品
class Products{
    int id;
    public Products(int id) {
        this.id = id;
    }
}

//缓冲区
class SynContainer{
    Queue<Products> queue = new LinkedList<Products>();
    int count = 0;
    int size = 10;
    //生产者放入产品
    public synchronized void push(Products product){
        //容器满,等待消费者消费
        if(count == size){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //没满,我们则需要存入成品,并唤醒消费者
        count++;
        queue.offer(product);
        this.notifyAll();
    }
    //消费者消费产品
    public synchronized Products pop(){
        //容器为空,等待生产者生产
        if (count == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //容器有剩,取出物品,并告诉生产者可以生产了
        count--;
        Products productPoll = queue.poll();
        this.notifyAll();
        return productPoll;
    }
}
2.信号灯法

设置一个标记位(类似于容量为1的管程法)

package test;

public class demo {
    public static void main(String[] args) {
        //创建缓冲区
        TheProduct theProduct = new  TheProduct();
        //双线程
        new Producers(theProduct).start();
        new Consumers(theProduct).start();
    }
}
//生产者
class Producers extends Thread{
    TheProduct theProduct;
    public Producers(TheProduct theProduct) {
        this.theProduct = theProduct;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            theProduct.push("产品" + i);
        }
    }
}

//消费者
class Consumers extends Thread{
    TheProduct theProduct;
    public Consumers(TheProduct container) {
        this.theProduct = container;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            theProduct.pop();
        }
    }
}

//产品
class TheProduct{
    boolean flag = false;
    String product;
    //生产者生产,消费者等待
    public synchronized void push(String product){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("生产者生产了" + product);
        this.product = product;
        this.flag = !this.flag;
        this.notifyAll();
    }
    //消费者消费,生产者等待
    public synchronized void pop() {
        if(!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消费者消费了" + product);
        this.flag = !this.flag;
        this.notifyAll();
    }
}

线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用。
好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize: 最大线程数
keepAliveTime: 线程没有任务时最多保持多长时间后会终止

package test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //newFixedThreadPool 参数:线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        //执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //2.关闭连接
        service.shutdown();
    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值