zoukankan      html  css  js  c++  java
  • Java学习笔记 -多线程1

    概述

    内存
    • 进程之间的内存独立不共享

    • Java中两个线程:
      1)共享堆内存和方法区
      2)栈内存各自独立 -两个栈

    多线程并发下,数据修改会存在线程安全问题,如何解决?
    • 线程排队执行,用排队执行解决

    • 这种机制称为:线程同步机制

    • 异步编程模型:多线程并发

    • 同步编程模型:线程排队执行

    创建新线程的三种方法

    编写自定义线程类

    public class ThreadTest01{
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
    
            //start()方法的作用:开辟一个新的栈空间,开辟完就结束了
            //有了新的栈空间就代表线程启动成功,此时run()方法和main()方法具有了相同的地位
            //如果只调用run()方法,而没有先分配栈空间,则没有新的线程,run()会使用主线程的栈
            myThread.start();
            
            for (int i = 0; i < 1000; i++) {
                System.out.println("主线程运行:  " + i);
            }
        }
    }
    
    //继承Thread类,重写run()方法
    class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("分支线程运行:  " + i);
            }
        }
    }
    

    直接实现Runnable接口

    public class ThreadTest02 {
        public static void main(String[] args) {
            //面向接口编程(推荐)
            Thread t1 = new Thread(new MyRunnable());
            
            //匿名内部类的实现方式
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        System.out.println("分支线程运行t2:  " + i);
                    }
                }
            });
            
            t1.start();
            t2.start();
            
            for (int i = 0; i < 1000; i++) {
                System.out.println("主线程运行:  " + i);
            }
        }
    }
    
    //直接实现Runnable接口,重写run()方法
    class MyRunnable implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("分支线程运行t1:  " + i);
            }
        }
    }
    

    实现Callable接口

    相比之前的两种方式,这个实现方式有明显的不同:

    • 优点:可以获取到线程的执行结果
    • 缺点:当前线程受阻,需要等待t线程执行完毕返回结果,效率较低
    public class FutureThreadTest {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            //1.创建一个“未来任务类对象”,需要传递一个Callable接口的实现对象
            FutureTask task =new FutureTask(new Callable() {
                @Override
                public Object call() throws Exception {
                    System.out.println(Thread.currentThread().getName() + "执行");
                    Thread.sleep(1000 * 5);
                    System.out.println(Thread.currentThread().getName() + "结束");
                    return 1; //自动装箱
                }
            });
    
            //2.创建线程对象
            Thread t1 = new Thread(task);
            t1.setName("t1");
    
            t1.start();
    
            //3.在主线程中如何获取t1线程的执行结果?
            //使用task.get()方法
            task.get();
    
            //因为要切换到另一个线程执行,当前线程需要阻塞,等待另一个线程执行完毕后,返回执行结果
            System.out.println("主线程执行");
        }
    }
    

    线程常用方法

    • void setName(String name)
    • String getName()
    • static Thread currentThread() 返回对当前正在执行的线程对象的引用
    • static void sleep(long millis) 参数是毫秒,在哪个线程调用,就让哪一个线程进入休眠
    • void interrupt() 通过产生异常的方式,中断线程的睡眠

    示例程序:

    public class ThreadTest04 {
        public static void main(String[] args) {
    
    
            Thread t1 = new Thread(new MyRunnable2());
            Thread t2 = new Thread(new MyRunnable2());
    
            //设置线程的名字
            t1.setName("t1");
            t2.setName("t2");
            //获取线程的名字
            System.out.println(t1.getName());
            System.out.println(t2.getName());
    
            t1.start();
            t2.start();
        }
    }
    class MyRunnable2 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                Thread currentThread = Thread.currentThread();
                System.out.println(currentThread.getName() +":  " + i);
            }
        }
    }
    

    提前终止线程的方法

    使用interrupt()方法

    public class ThreadTest05 {
        public static void main(String[] args) throws InterruptedException {
           Thread t = new Thread(new Runnable() {
               @Override
               public void run() {
                   try {
                       Thread.sleep(1000 * 60 * 60 * 24);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   Thread tmp = Thread.currentThread();
                   System.out.println(tmp.getName() + "线程 运行了");
               }
           });
    
           //产生新的线程
            t.setName("t");
            t.start();
    
            //主线程睡眠5s之后,叫醒t线程
            Thread.sleep(1000 * 5);
           //中断睡眠,通过产生异常的方式
           t.interrupt();
        }
    }
    

    设置标志信号量 -推荐

    public class ThreadTest06 {
        public static void main(String[] args) {
            MyRunnable3 myRunnable3 = new MyRunnable3();
            Thread t = new Thread(myRunnable3);
    
            t.setName("t");
    
            t.start();
    
            //模拟5s后终止分支进程
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myRunnable3.run = false;
        }
    }
    
    class MyRunnable3 implements Runnable{
    
        boolean run = true;
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                if(run){
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    return;
                }
            }
        }
    }
    

    线程同步机制

    什么情况下需要线程同步机制?

    多个线程同时修改同一内存这的数据时会发生线程安全问题,需要线程同步机制。
    正是因为这一点,所以局部变量一定不会存在线程安全问题。

    线程同步代码块 synchronized()

    ()中传递的“数据”相当关键

    这个数据必须是多线程共享的对象,才能达到多线程排队
    假设只希望t1 t2 t3排队 t4 t5不需要排队
    那么一定要在()写一个t1 t2 t3共享的对象,而这个对象对于t4 t5来说是不共享的
    强调: 共享 对象
    对象:参数应该是一个对象
    共享:多个需要同步的线程执行到synchronized()检测的对象应该是同一个
    举一个例子:字符串"abc",那么所有线程都会检测字符串常量池中的"abc",则所有线程都处于线程排队的状态

    注意事项
    • 同步代码块中的代码要尽可能的短小,提高执行效率

    • 如果方法没有包含synchronized() 则此方法在执行的时候不会去锁池了寻找对象锁,会直接执行

    • 在实例方法上可以使用synchronized修饰:
      例如:public synchronized void withdraw(double money);
      此时:锁默认是当前对象this,且方法体中全部的代码都需要同步;所以这种方法不灵活

    • 如果synchronized修饰在静态方法上,表示类锁,类锁只有一把;而对象锁每一个对象都有一把

    示例程序:

    public class Accout {
        private double balance;
    
        public Accout() { }
        public Accout(double balance) {
            this.balance = balance;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
    
        
        public void withdraw(double money){
    
            double after;
            synchronized (this){
                double before = getBalance();
                after = before - money;
                
                //模拟网络延迟1s
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                setBalance(after);
            }
    
            System.out.println(Thread.currentThread().getName() + "取款:" + money + "  余额: " + after);
        }
    }
    
    public class AccoutThread {
        public static void main(String[] args) {
            MyRunnable mr = new MyRunnable(new Accout(1000));
    
            //实例化两个取款线程类,模拟线程并发
            Thread t1 = new Thread(mr);
            Thread t2 = new Thread(mr);
    
            t1.setName("t1");
            t2.setName("t2");
    
            t1.start();
            t2.start();
        }
    
    
    }
    
    //取款线程类
    class MyRunnable implements Runnable{
        private Accout accout;
    
        public MyRunnable() {
        }
    
        public MyRunnable(Accout accout) {
            this.accout = accout;
        }
    
        public Accout getAccout() {
            return accout;
        }
    
        public void setAccout(Accout accout) {
            this.accout = accout;
        }
    
        @Override
        public void run() {
            accout.withdraw(500);
        }
    }
    

    总结 -解决线程安全问题的步骤?

    • synchronized会让程序的执行效率降低,则系统的用户吞吐量降低,用户体验差。在不得已的情况下再选择线程同步机制

    • 第一种方案:尽量使用局部变量代替"实例变量"和"静态变量"

    • 第二种方案:如果必须使用实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了

    • 第三种方案:synchronized

  • 相关阅读:
    django实例(1)
    django笔记补充
    django笔记
    Docker自学纪实(六)搭建docker私有仓库
    Docker自学纪实(四)搭建LNMP部署wordpress
    Docker自学纪实(三)Docker容器数据持久化
    Docker自学纪实(二)Docker基本操作
    Docker自学纪实(一)Docker介绍
    CentOS 7.4 基于LNMP搭建wordpress
    CentOS7安装配置VSFTP
  • 原文地址:https://www.cnblogs.com/zy200128/p/13030293.html
Copyright © 2011-2022 走看看