多线程(上篇)bing

概念

任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(例如函数库,shell程序等等)

内存 VS磁盘:
–内存一般比较小,磁盘比较大。
–造价不停,内存造价比较贵,磁盘便宜。
-内存是以纳秒级别读写速度、磁盘读写单位微秒;内存的操作速度比磁盘快很多。
一内存不能进行持久化,磁盘可以持久化。

多线程编程:
宏观上来看是同时执行的,而微观上来看是轮流执行的。
并发编程:所有程序轮流执行任务。
并行编程:所有程序同时执行任务。

进程组成:
1.PID
2.状态(新建、就绪、运行中、终止、阻塞)
3.优先级
4.一组指针(资源)
5.记账信息(解决资源分配不均的问题)
6.上下文(当没有时间片这时候需要保持状态等待下次执行,这个暂存状态和后面的运行状态就是一个上下文)

进程线程

进程VS线程;
1.进程是系统分配资源的最小单位;线程是系统调度的最小单位。
2.一个进程中可以包含多线程。
3.进程的实际执行单位就是线程。
4.一个进程里面至少包含一个线程,线程的存储必须依托于进程。
5.进程不可以共享资源,而线程可以共享资源(资源的是:1.打开的文件。2.内存)。
图示化:
在这里插入图片描述
主线程:主要执行业务的线程。
子线程:在主线程中创建线程就叫子线程。

Java线程

线程休眠方式

      // 方式一:线程休眠
            Thread.sleep(  1000);

           // 方式2:线程休眠
            TimeUnit.SECONDS.sleep(1); // 休眠 1 秒
           TimeUnit.HOURS.sleep(1); // 休眠 1 小时

        // 方式一3:线程休眠
        Thread.sleep(TimeUnit.SECONDS.toMillis(1));

线程的创建方式

第一种

写法1:

class MyThread extends Thread {
        @Override
        public void run() {
            // 写你的业务代码
            // 打印当前线程的名称
            System.out.println("子线程名称:" +
                    Thread.currentThread().getName());
        }
    }

写法2:

   Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("当前线程名称:" +
                        Thread.currentThread().getName());
            }
        };
        // 运行线程
        thread.start();
    }

这种创建方式的缺点:因为 Java语言的设计是单继承的,继承了Thread 类之后,就不能继承其他类。

第二种

写法1:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程名:" +
                Thread.currentThread().getName());
    }
}

public class ThreadDemo5 {


    public static void main(String[] args) {
        // 创建 Runnable 子对象
        MyRunnable myRunnable = new MyRunnable();
        // 创建线程
        Thread thread = new Thread(myRunnable);
        // 启动线程
        thread.start();
    }
}

写法2:

 public static void main(String[] args) {
        // 创建一个匿名 Runnable 类
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程:" +
                        Thread.currentThread().getName());
            }
        });
        thread.start();
    }

写法3:

  public static void main(String[] args) {
        // lambda + runnable
        Thread thread = new Thread(() -> {
            System.out.println("线程名:" +
                    Thread.currentThread().getName());
        });
        thread.start();
    }

该种写法只有在JDK1.8之上的支持。
当ideal使用lamdba出错时,需要将是用的JDK换成1.8
操作如下:
在这里插入图片描述
在这里插入图片描述

第三种
// 创建线程
    static class MyCallable implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            // 产生随机数
            int num = new Random().nextInt(10);
            System.out.println(String.format("线程:%s,生产了随机数:%d",
                    Thread.currentThread().getName(), num));
            return num;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1.创建 Callable 子对象
        MyCallable callable = new MyCallable();
        // 2.使用 FutrueTask 接收 Callable
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 3.创建线程并设置任务
        Thread thread = new Thread(futureTask);
        // 执行线程
        thread.start();
        // 得到线程的执行结果
        int num = futureTask.get();
        System.out.println("线程返回结果:" + num);
    }

线程创建的数量是不是越多越好?答:不是。
问题:创建多少线程合适?
答:要看实际的情况。
任务分为两种:
1.计算密集型任务;2.读写文件,
线程的数量=CPU核数最好;对于读写文件操作,理论上来讲线程数量越多越好。

线程的构造方法

Thread() 创建线程对象
Thread(Runnable target) 使用 Runnable 对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理,分好的组即使线程组,这个目前我们了解即可

  public static void main(String[] args) {

        ThreadGroup threadGroup = new ThreadGroup("group1");

        Thread t1 = new Thread(threadGroup, new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("选手1达到终点了~");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(threadGroup, new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1200);
                    System.out.println("选手2达到终点了~");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t2.start();

        // 等待所有人员到达终点
        while (threadGroup.activeCount() != 0) {
        }

        System.out.println("宣布比赛结果");
    }

Thread常见的属性

ID getId()
名称 getName()
状态 getState()
优先级 getPriority()(默认权重是5,是CPU执行权重,在1-10之间)
是否后台线程(守护线程) isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

线程分类

1.用户线程(默认创建的线程就是用户线程)—线程类型:Daemon:false
2.守护线程(后台线程)
守护线程是为用户线程服务,当用户线程执行完成之后,守护线程会跟随用户线程一起结束。
守护线程是为用户线程服务。
守护线程的经典使用场景:垃圾回收器。

守护线程需要注意的事项:

1.守护线程的设置必须放在开启线程(start())之前。
2在守护线程中创建的线程默认就是守护线程。

run () 、start ()的区别 :

1.run()方法是一个对象的普通方法,它使用的是主线程来执行任务的。
2.start()是线程的开启方法,它使用新的线程来执行任务的。
3.start()方法只能执行一次,而run()可以调用n次。

线程中断方式

1.自定义全局标识来实现中断;

  // 全局变量
    private static boolean flag = false;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (!flag) {
           
                try {
                    // 休眠线程
                    Thread.sleep(100);
                    System.out.println("我正在转账...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
            System.out.println("啊?差点误了大事。");
        }, "张三");
        // 开启任务
        t1.start();

        // 休眠主线程一段时间
        Thread.sleep(310);

        // 终止线程
        System.out.println("停止交易,有内鬼.");
        flag = true;
    }

在这里插入图片描述

2.使用Thread的intrrupted来中断。
interrupt()、interrupted()和isInterrupted()

  public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (!Thread.interrupted()) {
                try {
                    // 休眠线程
                    Thread.sleep(100);
                    System.out.println("我正在转账...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
            System.out.println("啊?差点误了大事。");
        }, "张三");
        // 开启任务
        t1.start();

        // 休眠主线程一段时间
        Thread.sleep(310);

        // 终止线程
        System.out.println("停止交易,有内鬼.");
        t1.interrupt();

    }

使用系统的 Intrruput() 可以及时的终止线程,而使用自定义全局变量终止线程的方式,比较温柔不能立刻终止。

join()

public void join() 等待线程结束
public void join(long millis) 等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos) 同理,但可以更高精度

当前线程的引用

在这里插入图片描述

sleep()

public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis 毫秒
public static void sleep(long millis, int nanos) throws
InterruptedException 可以更高精度的休眠

查看线程的状态

   public static void main(String[] args) {
        for (Thread.State state : Thread.State.values()) {
            System.out.println(state);
        }

    }

在这里插入图片描述

yield()

线程常用方法: yield【用来让出CPU的执行权】
yield分配执行权不一定成功,要看cpu的最终选择,但总体来说还是基本符合预期。

多线程问题:非线程安全

非线程安全:使用多线程执行任务,最终得到的结果和预期不一致。

线程不全的因素:

1.CPU是强制式执行的(万恶之源)(并不是一个执行完另一个在执行)。
2.通过操作的一个变量。
3.可见性
4.非原子性
5.编译器优化
3
在这里插入图片描述
volatile 禁止L1工作缓存的使用

在这里插入图片描述

public class Main {
    public static boolean flag = false;
    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            while (!flag) {
                System.out.println("线程1");//它的存在与否会存在可见性和编译器优化的双重结果。
            }
            System.out.println("线程1执行结束");
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            System.out.print("->");
            scanner.nextInt();
            flag = true;
        });
        t2.start();

    }
}
如何解决
volatile

volatile总结: volatile可以解决内存不可见和指令重排序的问题,但是不能解决原子性问题。

 public static volatile boolean flag = false;

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            while (!flag) {
            }
            System.out.println("线程1执行结束");
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            System.out.print("->");
            scanner.nextInt();
            flag = true;
        });
        t2.start();

    }

在这里插入图片描述

加锁操作:
1. synchronized ( jvm层的解决方案 )可重入加锁

synchronized的底层是使用操作系统的mutex lock实现的。

线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主内存中
线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量
synchronized用的锁是存在Java对象头里面的。
在这里插入图片描述
synchronized 实现分为:
1.操作系统层面,它是依靠互斥锁mutex.
2.针对.JVM,monitor 米实现。
3.针对Java语言来说,是将锁信息存放在对象头(标识,标识锁状态/锁的拥有者)

synchronized 的3种使用场景:
1.使用synchronized修饰代码块(可以给任意对象进行加锁)

 synchronized (ThreadDemo31.class) {//也可以是个对象
                        // 代码1
                        number++;
                    }

2.使用synchronized 来修饰静态方法(对当前的类进行加锁)

  // 全局变量
    private static int number = 0;
    // 循环次数
    private static final int maxSize = 100000;

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                increment();
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                decrement();
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("最终执行结果:" + number);

    }

    // 相加
    public synchronized static void increment() {
        for (int i = 0; i < maxSize; i++) {
            number++;
        }
    }

    // 相减
    public synchronized static void decrement() {
        for (int i = 0; i < maxSize; i++) {
            number--;
        }
    }

3.使用synchronized来修饰普通方法(对当前类实例进行加锁)

public class ThreadDemo {
    // 全局变量
    private static int number = 0;
    // 循环次数
    private static final int maxSize = 100000;

    public static void main(String[] args) throws InterruptedException {

        ThreadDemo threadDemo = new ThreadDemo();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo.increment();
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo.decrement();
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("最终执行结果:" + number);
    }

    // 相加
    public synchronized void increment() {
        for (int i = 0; i < maxSize; i++) {
            number++;
        }
    }

    // 相减
    public synchronized void decrement() {
        for (int i = 0; i < maxSize; i++) {
            number--;
        }
    }

}
  1. 手动锁Lock 的方式。
    在这里插入图片描述
    在这里插入图片描述
 // 全局变量
    private static int number = 0;
    // 循环次数
    private static final int maxSize = 100000;


    public static void main(String[] args) throws InterruptedException {

        ThreadDemo34 threadDemo33 = new ThreadDemo34();

        // 1.创建lock实例
        Lock lock = new ReentrantLock(true);

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    // 2.加锁
                    lock.lock();
                    try {
                        // 业务操作
                        number++;
                    } finally {
                        // 3.释放锁
                        lock.unlock();
                    }
                }
            }
        });
        t1.start();

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < maxSize; i++) {
                    lock.lock();
                    try {
                        number--;
                    } finally {
                        lock.unlock();
                    }
                }
            }
        });
        t2.start();

        t1.join();
        t2.join();
        System.out.println("最终执行结果:" + number);
    }

操作锁的流程:
1.尝试获取锁
2.使用锁(这一步骤是具体业务)
3.释放锁

公平锁和非公平锁

非公平锁的性能更高。
公平锁调度:
1.一个线程释放锁。
2.(主动)唤醒“需要得到锁”的队列来得到锁。
非公平锁:当一个线程释放锁之后,另一个线程刚好执行到获取锁的代码就可以直接获取锁。

在Java语言中所有的锁的默认实现方式都是非公平锁。
synchronized是非公平锁。

ReentrantLock 默认是非公平锁,但也可以显示的声明为公平锁。
显示的声明公平锁:
在这里插入图片描述

  public static synchronized  void main(String[] args) throws InterruptedException {
        Lock lock=new ReentrantLock(true);
Runnable runnable=new Runnable() {
    @Override
    public void run() {
        for(char a:"abcd".toCharArray()){
            lock.lock();
            try{
                System.out.println(a);
            }
            finally {
                lock.unlock();
            }
        }
    }
};
Thread a=new Thread(runnable);
        Thread b=new Thread(runnable);
        Thread.sleep(100);
        a.start();
        b.start();

    }

synchronized 和Lock 的区别:

1…synchronized自定进行加锁和释放锁,而Lock 需要手动加锁和解锁。
2.Lock 是 Java 层面的锁的实现,而synchronized 是JVM层面的实现。
3.synchronized 和 Lock 适用范围不同,Lock 只能用来修饰代码块,而synchronized既可以修饰代码块,又可以用来修饰静态方法和普通方法。
4.synchronized 锁的模式只有非公平锁模式,而 Lock 既可以使用公平锁的模式又可以使用非公平锁的模式。

5.Lock 的灵活性更高( tryLock )。
下一篇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月屯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值