Java(复习)线程

基本概念

  • CPU核心数:表示CPU在同一个瞬时时间可以处理的任务数
  • 通过主频CPU来对进程频繁的切换
  • 多线程:一个进程可以分化为并行执行的多个线程

多线程优点

  • 提高应用程序响应,增强用户体验
  • 提高CPU利用率
  • 改善程序结构,将较长的进程划分成多个线程,独立运行,利于理解和修改
    • 比如程序中代码比较长而且前后执行没有因果关系,就可以将没有因果关系的部分放到各自的线程中执行,优化代码结构

多线程使用场景

  • 程序需要同时主席那个两个或者多个任务
  • 程序需要实现一些需要等待的任务时,用户输入、文件读写操作、网络操作、搜索等
  • 需要一些后台运行的程序时:因为多线程是进程的支流,当分支之后就会各不影响,假设在进程上跑的是主程序 ,当其中的第三行代码是开启线程的,那么开启线程之后线程运行的代码就和主程序并行(和主程序不相干)

多线程的创建和启动

  • Thread类特性
    • 每个线程都通过某个特定的Thread对象的run实现,run方法被称作线程体
    • 通过Thread对象的start()方法来调用这个线程
创建线程
/**
* 继承Thread
*/
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("子线程运行的主逻辑");
        for (int i = 0; i < 5; i++) {
            System.out.println("这是多线程的逻辑: "+ i);
        }
    }
}

/**
* 实现Runnable接口
*/
public class MyRunnable implements Runnable {
    public int count = 0;


    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            count++;
            System.out.println(Thread.currentThread().getName() + ":这是Runnable多线程的逻辑: " + count);
        }
    }
}

public static void main(String[] args) {
        MyThread myThread = new MyThread();
        /**
         * 多次运行发现,主线程和子线程两者运行互不影响,各自保持自己的输出顺序,这个就是多线程的异步性,同步存在顺序性
         * 主线程:0
         * 子线程运行的主逻辑
         * 主线程:1
         * 这是多线程的逻辑: 0
         * 主线程:2
         * 这是多线程的逻辑: 1
         * 主线程:3
         * 主线程:4
         * 这是多线程的逻辑: 2
         * 这是多线程的逻辑: 3
         * 这是多线程的逻辑: 4
         */
        myThread.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程:" + i);
        }

        // 使用Runnable实现
        MyRunnable myRunnable = new MyRunnable();
        Thread runnableThread1 = new Thread(myRunnable, "t-1");
        runnableThread1.start();
        /**
         * 这是Runnable多线程的逻辑: 0
         * 这是Runnable多线程的逻辑: 1
         * 这是Runnable多线程的逻辑: 2
         * 这是Runnable多线程的逻辑: 3
         * 这是Runnable多线程的逻辑: 4
         */

        Thread runnableThread2 = new Thread(myRunnable, "t-2");
        runnableThread2.start();
    }
两种实现多线程的方式的联系与区别
  • 联系:
    • 都需要使用Thread父类来实现线程启动
  • 区别:
    • 继承Thread:线程代码存放Thread子类run方法中
    • 实现Runnable:线程代码存放在接口的子类的run方法
  • 使用Runnable接口的好处
    • 避免了单继承的局限性
    • 多个线程可以共享同一个接口实现类对象,适合多个相同线程来处理同一份资源
MyRunnable myRunnable = new MyRunnable();
Thread runnableThread1 = new Thread(myRunnable, "t-1");
Thread runnableThread2 = new Thread(myRunnable, "t-2");

线程相关方法

  • void start() : 启动线程,并执行对象的run()方法
  • void run():线程在被调度时执行的操作
  • String getName():返回线程名称
  • void setName(String name):设置该线程的名称
  • static currentThread():返回当前线程
  • setPriority(int newPriority):设置线程优先级,优先级使用数字1~10表示,数字越大优先级越高会增加被执行的概率,默认优先级为5
class TestRun implements Runnable {
    public int count = 0;


    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            count++;
            System.out.println(Thread.currentThread().getName() + ":这是Runnable多线程的逻辑: " + count);
        }
    }
}
    public static void main(String[] args) {
        TestRun testRun1 = new TestRun();
        TestRun testRun2 = new TestRun();
        Thread thread1 = new Thread(testRun1);
        Thread thread2 = new Thread(testRun2);
        thread1.setPriority(10);
        thread1.start();
        thread2.start();
//        thread1.setName("Thread1"); // 设置线程名称
        System.out.println(thread1.getName()); // Thread-0 如果创建线程时没有指定名称,系统分配给线程的默认名称
        System.out.println(thread2.getName()); // Thread-1
    }
  • static void yield():线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或优先级更高的线程,若队列中没有同优先级的线程,忽略此方法
class TestRun implements Runnable {
    public int count = 0;
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            if (i % 2 == 0) {
                Thread.yield(); // 线程让步
            }
            count++;
            System.out.println(Thread.currentThread().getName() + ":这是Runnable多线程的逻辑: " + count);
        }
    }
}
  • join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到调用join()方法加入的join()线程执行完为止
    • 低优先级的线程也可以获得执行
        TestRun testRun1 = new TestRun();
        TestRun testRun2 = new TestRun();
        Thread thread1 = new Thread(testRun1);
        Thread thread2 = new Thread(testRun2);
        thread1.start();
        thread2.start();
        System.out.println(thread1.getName()); // Thread-0 如果创建线程时没有指定名称,系统分配给线程的默认名称
        thread1.join(); // 相当于把thread1的run方法放到这里执行,同步顺序,先不执行main函数的方法,先执行join进来的代码,join线程执行完毕之后,再执行main方法阻塞的代码
        /**
         * 阻塞main方法,Thread-0一定在两次打印名字之间输出
         * Thread-0
         * Thread-0:这是Runnable多线程的逻辑: 1
         * Thread-0:这是Runnable多线程的逻辑: 2
         * Thread-0:这是Runnable多线程的逻辑: 3
         * Thread-1:这是Runnable多线程的逻辑: 1
         * Thread-0:这是Runnable多线程的逻辑: 4
         * Thread-1:这是Runnable多线程的逻辑: 2
         * Thread-0:这是Runnable多线程的逻辑: 5
         * Thread-1:这是Runnable多线程的逻辑: 3
         * Thread-1
         */
        System.out.println(thread2.getName()); // Thread-1
    }
  • sleep(long millis):使当前活动的线程在指定的时间内放弃对CPU的控制,使得其他线程可以执行,时间到后重新排队
@Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000); // 当前线程sleep 1 秒,相当于当前循环没给1秒执行一次
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
            System.out.println(Thread.currentThread().getName() + ":这是Runnable多线程的逻辑: " + count);
        }
    }
  • stop():强制线程生命期结束
thread1.start();
thread2.start();
thread1.stop();
/**
* Thread-0
* Thread-1
* Thread-1:这是Runnable多线程的逻辑: 1
* Thread-1:这是Runnable多线程的逻辑: 2
* Thread-1:这是Runnable多线程的逻辑: 3
* Thread-1:这是Runnable多线程的逻辑: 4
* Thread-1:这是Runnable多线程的逻辑: 5
*/
System.out.println(thread1.isAlive()); // false 判断线程是否还活着

线程的生命周期

  • JDK中用Thread.State枚举表示了线程的状态,在线程的完整的生命周期中都要经历下面五种状态
    • 新建:线程实例的创建
    • 就绪:调用start()方法,将进入阻塞队列进行等待,直到CPU分配给时间片
    • 运行:run方法开始执行,即就绪的线程被调度并获得CPU资源时,进入运行状态
    • 阻塞:当被挂起或者执行I/O操作时,需要将CPU让出,阻塞自己的执行,即run方法暂停执行
    • 终止:线程完成了全部的工作或执行stop()、断电、杀掉进程被强制终止

线程同步

  • 线程共享资源时,一个线程在执行这个方法没有完毕时,另一个线程又开始执行这个方法,导致共享数据的错误
  • 解决方法,先让一个线程整体执行完方法再让另一个线程执行
  • 通过synchronized来实现,可以直接在方法上加这个关键字
  • 在普通方法上加synchronized锁的是整个的对象,不是某一个方法
// 定义一个对象
public class Test2 {
    public static void main(String[] args) {
        Acount acount = new Acount();
        User user_wx = new User(acount, 2000);
        User user_pay = new User(acount, 2000);
        Thread wxThread = new Thread(user_wx, "wx");
        Thread payThread = new Thread(user_pay, "pay");
        wxThread.start();
        payThread.start();
    }
}


class Acount {
    public int money = 3000;


    public synchronized void drawing(int mOut) {
        if (money < mOut) {
            System.out.println("对不起,无法取款:" + money);
        } else {
            String name = Thread.currentThread().getName();
            System.out.println(name + " 操作账户原有金额: " + money);
            System.out.println(name + " 取款:" + mOut);
            money = money - mOut;
            System.out.println(name + " 还剩:" + money);
        }
    }
    public synchronized void drawing1(int mOut) {
        if (money < mOut) {
            System.out.println("对不起,无法取款:" + money);
        } else {
            String name = Thread.currentThread().getName();
            System.out.println(name + " 操作账户原有金额: " + money);
            System.out.println(name + " 取款:" + mOut);
            money = money - mOut;
            System.out.println(name + " 还剩:" + money);
        }
    }
}

class User implements Runnable {
    private Acount mAcount;
    private int money;
    public User(Acount acount, int mmoney) {
        this.mAcount = acount;
        this.money = mmoney;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("wx")) {
            mAcount.drawing(money);
        } else{
            mAcount.drawing1(money);
        }
        /**
         * 和原来执行的结果一致,所以锁住的是Account这个对象
         * pay 操作账户原有金额: 3000
         * pay 取款:2000
         * pay 还剩:1000
         * 对不起,无法取款:1000
         */
    }
}
// 定义两个对象
public class Test2 {
    public static void main(String[] args) {
        Acount acount = new Acount();
        Acount acount1 = new Acount();
        User user_wx = new User(acount, 2000);
        User user_pay = new User(acount1, 2000);
        Thread wxThread = new Thread(user_wx, "wx");
        Thread payThread = new Thread(user_pay, "pay");
        wxThread.start();
        payThread.start();
    }
}


class Acount {
    public static int money = 3000; // 静态变量内存中共享money


    public synchronized void drawing(int mOut) {
        if (money < mOut) {
            System.out.println("对不起,无法取款:" + money);
        } else {
            String name = Thread.currentThread().getName();
            System.out.println(name + " 操作账户原有金额: " + money);
            System.out.println(name + " 取款:" + mOut);
            money = money - mOut;
            System.out.println(name + " 还剩:" + money);
        }
        /** 
        * pay 操作账户原有金额: 3000 
        * wx 操作账户原有金额: 3000 
        * pay 取款:2000 
        * wx 取款:2000 
        * pay 还剩:1000 
        * wx 还剩:-1000 */
    }

    public synchronized void drawing1(int mOut) {
        if (money < mOut) {
            System.out.println("对不起,无法取款:" + money);
        } else {
            String name = Thread.currentThread().getName();
            System.out.println(name + " 操作账户原有金额: " + money);
            System.out.println(name + " 取款:" + mOut);
            money = money - mOut;
            System.out.println(name + " 还剩:" + money);
        }
    }
}

class User implements Runnable {
    private Acount mAcount;
    private int money;

    public User(Acount acount, int mmoney) {
        this.mAcount = acount;
        this.money = mmoney;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("wx")) {
            mAcount.drawing(money);
        } else{
            mAcount.drawing1(money);
        }
    }
}
  • 所以说在方法前加synchronized针对同一个对象好用,不同对象不好用
  • 但是如果是static方法加synchronized,对于所有对象都是同一个锁
// 如果是静态的方法加synchronized,则对于所有的对象都是同一个锁
public static synchronized void drawing2(int mOut) {
    if (money < mOut) {
        System.out.println("对不起,无法取款:" + money);
    } else {
        String name = Thread.currentThread().getName();
        System.out.println(name + " 操作账户原有金额: " + money);
        System.out.println(name + " 取款:" + mOut);
        money = money - mOut;
        System.out.println(name + " 还剩:" + money);
    }
}
@Override
public void run() {
    Acount.drawing2(money);
    /**
     * wx 操作账户原有金额: 3000
     * wx 取款:2000
     * wx 还剩:1000
     * 对不起,无法取款:1000
     */
}
  • 总结
    • synchronized修饰方法锁对象,因为所有对象是共享同一个static变量的
    • 静态synchronized修饰方法锁class,对所有对象都生效

synchronized锁代码块

  • 不同的对象使用同一个锁,synchronized(this)是对象锁
public void drawing3(int mOut) {
    // 用this表示当前对象, 该对象的代码块被加了同步锁
    synchronized (this) {
        if (money < mOut) {
            System.out.println("对不起,无法取款:" + money);
        } else {
            String name = Thread.currentThread().getName();
            System.out.println(name + " 操作账户原有金额: " + money);
            System.out.println(name + " 取款:" + mOut);
            money = money - mOut;
            System.out.println(name + " 还剩:" + money);
        }
    }
}

public void drawing4(int mOut) {
    // 表示当前对象的代码块被加了同步锁
    synchronized (this) {
        if (money < mOut) {
            System.out.println("对不起,无法取款:" + money);
        } else {
            String name = Thread.currentThread().getName();
            System.out.println(name + " 操作账户原有金额: " + money);
            System.out.println(name + " 取款:" + mOut);
            money = money - mOut;
            System.out.println(name + " 还剩:" + money);
        }
    }
}

// 同一对象没问题
// 不同对象锁不住

  • synchronized (acount)根据不同的对象使用不同的锁,是对象锁,针对同一个对象有效
public void drawing5(int mOut, Acount acount) {
    // 表示从方法中传递进来的对象的同步锁,不同的对象就有不同的同步锁
    synchronized (acount) {
        if (money < mOut) {
            System.out.println("对不起,无法取款:" + money);
        } else {
            String name = Thread.currentThread().getName();
            System.out.println(name + " 操作账户原有金额: " + money);
            System.out.println(name + " 取款:" + mOut);
            money = money - mOut;
            System.out.println(name + " 还剩:" + money);
        }
    }
}


// 不同对象
User user_wx = new User(acount, 2000);
User user_pay = new User(acount1, 2000);

mAcount.drawing5(money, mAcount);

wx 操作账户原有金额: 3000
wx 取款:2000
wx 还剩:1000
pay 操作账户原有金额: 3000
pay 取款:2000
pay 还剩:-1000

// 相同对象
User user_wx = new User(acount, 2000);
User user_pay = new User(acount, 2000);

mAcount.drawing5(money, mAcount);


pay 操作账户原有金额: 3000
pay 取款:2000
pay 还剩:1000
对不起,无法取款:1000

和上述this相同
  • 总结:
    • 普通方法加同步锁->锁当前方法对应的对象,当前对象所有加了同步锁的方法公用一个同步锁,例一
    • 静态方法加同步锁->所有当前的对象都使用同一把锁
    • synchronized(this)->所有当前对象的synchronized(this)同步的代码块都是公用一个同步锁
    • synchronized(object)->同synchronized(this)
    • 如果针对对象加同步锁加载方法上
    • 如果针对某一段代码加同步锁,那么就在代码块上加同步锁

死锁

  • 不同的线程分别占用对方的同步资源且互相等待对方释放同步资源,造成死锁

死锁解决

  • 银行家算法预测
  • 安全序列
  • 尽量减少同步资源的定义,尽量避免锁未释放的场景

线程通信

  • wait():把当前线程挂起进入就绪状态
  • notify():唤醒在阻塞队列中线程优先级最高的线程
  • notifyAll():换醒所有阻塞队列中的线程
  • 上述三个方法只能用在有同步锁的方法或者有同步锁代码块中
synchronized (acount) {


        String name = Thread.currentThread().getName();
        if (name.equals("wx")) {
            try {
                acount.wait(); // 当前线程进入就绪状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if (money < mOut) {
            System.out.println("对不起,无法取款:" + money);
        } else {
            System.out.println(name + " 操作账户原有金额: " + money);
            System.out.println(name + " 取款:" + mOut);
            money = money - mOut;
            System.out.println(name + " 还剩:" + money);
        }
        if (name.equals("pay")) {
            acount.notify(); // 唤醒当前优先级最高的线程进入就绪状态
        }


        /**
         * 先执行pay,后执行wx
         * pay 操作账户原有金额: 3000
         * pay 取款:2000
         * pay 还剩:1000
         * 对不起,无法取款:1000
         */
    }
}

生产者和消费者


public static void main(String[] args) {



        Clerk clerk = new Clerk();
        // 生产者, 生产时不消费
        new Thread(() -> {
            synchronized (clerk) {
                // 一直生产
                while (true) {
                    if (clerk.productNum == 0) {
                        System.out.println("产品为0, 开始生产");
                        while(clerk.productNum<4) {
                            clerk.productNum++;
                            System.out.println("库存:" + clerk.productNum);
                        }
                        System.out.println("生产结束,产品数:" + clerk.productNum);
                        // 唤醒消费者
                        clerk.notify();
                    } else {
                        try {
                            // 生产者阻塞
                            clerk.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }, "Product").start();



        new Thread(() -> {
            synchronized (clerk) {
                // 一直消费
                while (true) {
                    if (clerk.productNum == 4) {
                        System.out.println("产品为4, 开始消费");
                        while(clerk.productNum>0) {
                            clerk.productNum--;
                            System.out.println("库存:" + clerk.productNum);
                        }
                        System.out.println("消费结束,产品数:" + clerk.productNum);
                        // 唤醒生产者
                        clerk.notify();
                    } else {
                        try {
                            // 消费者阻塞
                            clerk.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        },"Consumer").start();
    }




class Clerk {
    public int productNum = 0;



}

产品为4, 开始消费
库存:3
库存:2
库存:1
库存:0
消费结束,产品数:0
产品为0, 开始生产
库存:1
库存:2
库存:3
库存:4
生产结束,产品数:4
发布了175 篇原创文章 · 获赞 56 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_39424143/article/details/104360560
今日推荐