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

原文地址:https://www.cnblogs.com/zy200128/p/13030293.html