Java随堂笔记07-多线程

多线程

process和Thread简介

  • 程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
  • Process:进程 进程是执行程序的一次执行过程,他是一个动态的概念,是系统资源分配的单位
  • Thread:线程 通常一个进程中可以包含多个线程,当然一个进程中至少要有一个线程。线程是CPU调度和执行的单位
    • 线程就是独立的执行路径
    • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
    • main()称之为主线程,为系统的入口,用于执行整个程序
    • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
    • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
    • 线程会带来额外的开销,如CPU调度时间,并发控制开销
    • 每个线程在自己工作内存交互,内存控制不当会造成数据不一致

注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

Thread创建

第一种方法

  1. 自定义线程类继承Thread类
  2. 重写run()方法,编写线程体
  3. 创建线程对象,调用start()方法启用线程
public 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){
        //主线程
        
        //创建线程对象
        TestThread testThread = new TestThread();
        
        //调用start方法开启线程
        testThread.start();
        
        for(int i = 0;i < 1000;i++){
            System.out.println("主线程---"+i);
        }
    }
}
/*
输出结果不定,两个线程同时执行。但由于启动线程一般有延迟,所以会先执行一部分主线程,即输出一部分”主线程---i”,然后两个结果交替输出,数据越大,效果越明显。
*/

第二种方法

  1. 定义类实现Runnable接口
  2. 重写run()方法
  3. 将实现Runnable接口的类的实例当做参数创建线程对象,调用start()方法启动线程
public 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){
        //主线程
       
        TestThread testThread = new TestThread();
        
        //调用start方法开启线程
        new Thread(testThread).start();
        
        for(int i = 0;i < 1000;i++){
            System.out.println("主线程---"+i);
        }
    }
}

推荐使用第二种,可以避免单继承局限性,方便同一个对象被多个线程使用

并发问题初相识

public class TestThread implements Runnable{

    private int ticket = 10;

    @Override
    public void run() {
        while(ticket > 0){
            try {
                Thread.sleep(200);  //模拟延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"抢到了第"+ticket+"张票");
            ticket--;
        }
    }

    public static void main(String[] args) {
        TestThread testThread = new TestThread();

        new Thread(testThread,"小红").start();
        new Thread(testThread,"小明").start();
        new Thread(testThread,"黄牛").start();
    }
}

运行结果:
image

Lambda表达式

  • 函数式接口的定义:任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口
  • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
public class TestLambda {
    //2.静态内部类
    static class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("I like lambda2");
        }
    }

    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();

        like = new Like2();
        like.lambda();

        //3.局部内部类
        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("I like lambda3");
            }
        }
        like = new Like3();
        like.lambda();

        //4.匿名内部类
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("I like lambda4");
            }
        };
        like.lambda();

        //Lambda表达式简化
        like = ()-> {System.out.println("I like lambda5");};
        like.lambda();
    }
}

//接口
interface ILike{
    void lambda();
}

//1. 实现接口类
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("I like lambda1");
    }
}

静态代理模式(待巩固)

new Thread(()->{System.out.println("...");}).start();

线程状态

五大线程状态

image

image

线程方法

1. 停止线程

  • 不推荐使用JDK提供的stop(),destroy()方法。已废弃
  • 推荐线程自己停下来
  • 推荐使用一个标志位进行终止变量 当flag=false时,终止线程运行
public class TestStop implements Runnable {
    private boolean flag = true;
    @Override
    public void run() {
        while (flag){
            System.out.println("Running ... Thread...");
        }
    }

    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 < 1000; i++) {
            System.out.println("main---"+i);
            if(i == 900){
                testStop.stop();
            }
        }
    }
}

2.线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间到达后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一把锁,sleep不会释放锁
import java.text.SimpleDateFormat;
import java.util.Date;

public class TestSleep {
    public static void main(String[] args)  {
        try {
            countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        //打印当前时间
        while(true){
            Date currentTime = new Date(System.currentTimeMillis());
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(currentTime));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //倒计时
    public static void countDown() throws InterruptedException {
        int num = 10;
        while(true){
            System.out.println(num--);
            Thread.sleep(1000);
            if(num <= 0){
                System.out.println("倒计时结束!");
                break;
            }
        }
    }
}

3.线程礼让

  • 线程礼让,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转化为就绪状态
  • 让CPU重新调度,礼让不一定成功!看CPU心情
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"A").start();
        new Thread(myYield,"B").start();
    }
}

class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"--->start");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"--->end");
    }
}

image

4.Join

join合并线程,待此线程执行完之后,再执行其他线程,其他线程阻塞。可以想象成插队

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

    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main"+i);
            if(i==30){
                thread.join();
            }
        }
    }
}

线程状态

image

public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()-> {
            for (int i = 0; i <10 ; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
        });

        Thread.State state = thread.getState();
        System.out.println(state);

        thread.start();
        state = thread.getState();
        System.out.println(state);

        while(state!=Thread.State.TERMINATED){
            Thread.sleep(100);
            state = thread.getState();
            System.out.println(state);
        }
    }
}

线程优先级

线程优先级1-10; MIN_PRIORITY = 1; MAX_PRIORITY = 10; NORM_PRIORITY = 5;

public class TestPriority {
    public static void main(String[] args) {
        System.out.println("main优先级"+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);
        Thread t6 = new Thread(myPriority);

        //先设置优先级后执行
        t1.setPriority(5);
        t1.start();
        t2.setPriority(3);
        t2.start();
        t3.setPriority(4);
        t3.start();
        t4.setPriority(6);
        t4.start();
        t5.setPriority(Thread.MAX_PRIORITY);
        t5.start();
        t6.setPriority(9);
        t6.start();

    }
}

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

经过测试发现,优先级高的并不一定比优先级低的先执行,只是被CPU调度的概率增大了一点,主要还是看CPU“心情”。

守护线程(daemon)

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
public class TestDaemon {
    public static void main(String[] args) {
        You you = new You();
        God god = new God();

        Thread thread = new Thread(you);
        Thread thread1 = new Thread(god);

        thread1.setDaemon(true);

        thread.start();
        thread1.start();
    }
}

class You implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(i+"年过去了");
            if(i == 100){
                System.out.println("=======Bye,world!=======");
            }
        }
    }
}

class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("守护");
        }
    }
}

线程同步机制

  • 并发 :同一个对象被多个线程同时操作
  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的进程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问是的正确性,在访问时加入了锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题
    • 一个线程持有锁会导致所有需要此锁的线程挂起
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

三大不安全案例及解决方案(Synchronized)

锁变化的对象

public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket,"你").start();
        new Thread(buyTicket,"我").start();
        new Thread(buyTicket,"小黄牛").start();
    }
}

class BuyTicket implements Runnable{

    private int ticketNum = 10;
    boolean flag = true;

    @Override
    public void run() {
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void buy() throws InterruptedException {
        if(ticketNum<=0){
            this.flag = false;
            return;
        }
        //模拟延时
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNum--+"张票");
    }
}
public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account("结婚基金",100);
        DrawMoney you = new DrawMoney(account,50,"你");
        DrawMoney girlFriend = new DrawMoney(account,100,"女朋友");

        you.start();
        girlFriend.start();
    }
}

class Account{
    String name;
    int money;

    public Account(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

class DrawMoney extends Thread{
    Account account;
    int drawMoney;
    public DrawMoney(Account account,int drawMoney,String name){
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }

    @Override
    public void run() {
        synchronized (account){
            if(account.money-drawMoney<0){
                System.out.println("余额不足");
                return;
            }

            //模拟延时
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money = account.money - drawMoney;
            System.out.println(this.getName()+"取款成功!"+account.name+"余额为"+account.money);
        }
    }
}
import java.util.ArrayList;
import java.util.List;

public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized(list){
                   list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        Thread.sleep(3000);
        System.out.println(list.size());
    }
}

死锁

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

  1. 互斥条件:一个资源只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

打破其一即可破解死锁

Lock锁

  • 从JDK5.0开始,Java提供了更加强大的线程同步机制—通过显示定义同步锁对象来实现同步。
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现的Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁。
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();

        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}

class TestLock2 implements Runnable{
    int ticketNum = 10;
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        lock.lock();
        try {
            while (true){
                if(ticketNum>0){
                    System.out.println(ticketNum--);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }
        } finally {
            lock.unlock();
        }
    }
}

synchronized 和 Lock比较

  • Lock是显式锁(手动开关),synchronized是隐式锁,出作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的拓展性
  • 优先使用顺序
    • Lock > 同步代码块 > 同步方法
posted @ 2022-02-10 23:38  送你  阅读(38)  评论(0)    收藏  举报