Java多线程

一、基本概念

进程:资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。

线程:是程序执行时的最小单位,它是进程的一个执行流。

进程与线程的区别:

  • 进程是资源分配的最小单位,线程是程序执行的最小单位;
  • 进程拥有独立的地址空间,线程共享进程中的数据,使用同一个地址空间;
  • 进程间的通信比较复杂,线程间共享同一进程下的数据;

多进程与多线程的优劣:

  • 创建进程比创建线程的开销大;
  • 进程间的通信比线程间的要慢;
  • 多进程比多线程稳定,多线程中的任何一个线程的崩溃都会导致整个进程的崩溃;

二、使用多线程

1.创建新线程

package Thread_demo;

public class CreateThread {
    public static void main(String[] args) {
        //线程启动后执行指定的代码,方法一:从Thread派生一个自定义类,然后重写run()方法
        Thread t = new MyThread();
        //start()方法会在内部自动调用run()方法
        t.start();
    }
}
class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("This is my Tread!");
    }
}
package Thread_demo;

public class CreateThread {
    public static void main(String[] args) {
        //线程启动后执行指定的代码,方法二:创建Thread实例时,传入一个Runnable实例
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run(){
        System.out.println("This is my Tread!");
    }
}
package Thread_demo;

public class CreateThread {
    public static void main(String[] args) {
        //线程启动后执行指定的代码,方法三:使用lambda表达式
        Thread t = new Thread(()->{
            System.out.println("This is my Thread!");
        });
        t.start();
    }
}
  • 线程本身由操作系统调度,程序本身无法确定线程的调度顺序。
  • Thread.setPriority(int n) // 1~10,默认值5,优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。
package Thread_demo;

public class CreateThread {
    public static void main(String[] args) {
        System.out.println("This is the first thread start!");
        //线程启动后执行指定的代码,方法三:使用lambda表达式
        Thread t = new Thread(()->{
            System.out.println("This is the second thread start!");
            System.out.println("This is the second thread end!");
        });
        t.start();
        try {
            //可以在线程中调用Thread.sleep()强迫当前线程暂停一段时间
            Thread.sleep(10);
        }catch (InterruptedException e){}

        System.out.println("This is the first thread end!");
    }
}

2.线程状态

线程的五种状态:

  • New:新创建的线程,尚未执行;
  • Runnable:运行中的线程,正在执行run()方法的Java代码;
  • Blocked:运行中的线程,因为某些操作被阻塞而挂起;
  • Waiting:运行中的线程,因为某些操作在等待中;
  • Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
  • Terminated:线程已终止,因为run()方法执行完毕。

线程终止的原因:

  • 线程正常终止:run()方法执行到return语句返回;
  • 线程意外终止:run()方法因为未捕获的异常导致线程终止;
  • 对某个线程的Thread实例调用stop()方法强制终止。
package Thread_demo;

public class CreateThread {
    public static void main(String[] args) {
        System.out.println("This is the first thread start!");
        Thread t = new Thread(()->{
            System.out.println("This is the second thread start!");
            System.out.println("This is the second thread end!");
        });
        t.start();
        //个线程可以等待另一个线程直到其运行结束
        try {
            t.join();
        }catch (InterruptedException e){}
        System.out.println("This is the first thread end!");
    }
}

3.中断线程

  • 线程一给线程二发送中断请求
package Thread_demo;

public class CreateThread {
    public static void main(String[] args) {
        System.out.println("This is the first thread start!");
        Thread t = new MyThread();
        t.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {}
        //给t线程发送中断请求
        t.interrupt();
        try {
            t.join();
        }catch (InterruptedException e){}
        System.out.println("This is the first thread end!");
    }
}
class MyThread extends Thread {
    @Override
    public void run(){
        int n = 0;
        while (!interrupted()) {
            n++;
            System.out.println(n);
        }
    }
}
  • 线程一给正在等待线程三中断的线程二发送中断请求
package Thread_demo;

public class CreateThread {
    public static void main(String[] args) {
        System.out.println("This is the first thread start!");
        Thread t = new MyThread();
        t.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {}
        //给t线程发送中断请求
        t.interrupt();
        try {
            t.join();
        }catch (InterruptedException e){}
        System.out.println("This is the first thread end!");
    }
}
class MyThread extends Thread {
    @Override
    public void run(){
        Thread helloThread = new HelloThread();
        helloThread.start();
        try {
            helloThread.join();
        }catch (InterruptedException e){
            System.out.println("Interrupt");
        }
        helloThread.interrupt();
    }
}
class HelloThread extends Thread {
    @Override
    public void run(){
        int n = 0;
        while (!interrupted()) {
            n++;
            System.out.println(n);
        }
    }
}
  • 使用running标志位来标识线程是否应该继续运行。
  • volatile关键字是为了告诉虚拟机:每次访问变量时,总是获取主内存的最新值;每次修改变量后,立刻回写到主内存。
  • 在Java虚拟机中,变量的值保存在主内存中,但是,当线程访问变量时,它会先获取一个副本,并保存在自己的工作内存中。如果线程修改了变量的值,虚拟机会在某个时刻把修改后的值回写到主内存,但是,这个时间是不确定的。
  • volatile关键字解决的是可见性问题:当一个线程修改了某个共享变量的值,其他线程能够立刻看到修改后的值。
package Thread_demo;

public class CreateThread {
    public static void main(String[] args) {
        System.out.println("This is the first thread start!");
        MyThread t = new MyThread();
        t.start();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {}

        t.running = false;
        try {
            t.join();
        }catch (InterruptedException e){}
        System.out.println("This is the first thread end!");
    }
}
class MyThread extends Thread {
    public volatile boolean running = true;
    @Override
    public void run(){
        System.out.println("start!");
        int n = 0;
        while (running) {
            n++;
            System.out.println(n);
        }
        System.out.println("end!");
    }
}

4.守护线程

  • 守护线程是为其他线程服务的线程;
  • 所有的非守护线程都执行完毕后,虚拟机退出;
  • 守护线程不能持有需要关闭的资源。
package Thread_demo;

public class CreateThread {
    public static void main(String[] args) {
        System.out.println("This is the first thread start!");
        MyThread t = new MyThread();
        //标记t为守护线程
        t.setDaemon(true);
        t.start();
        System.out.println("This is the first thread end!");
    }
}
class MyThread extends Thread {
    @Override
    public void run(){
        System.out.println("start!");
        int n = 0;
        while (true) {
            try {
                Thread.sleep(1000);
                n++;
                System.out.println(n);
            } catch (InterruptedException e) {
                break;
            }
        }
    }
}

5.线程同步

  • 多线程同时读写共享变量时,会造成逻辑错误,因此需要通过synchronized同步;
  • 同步的本质就是给指定对象加锁,加锁后才能继续执行后续代码,加锁对象必须是同一个实例;
  • 对JVM定义的单个原子操作不需要同步。
package threadDemo;

public class demo {
    public static void main(String[] args) {
        Thread add = new AddThread();
        Thread dec = new DecThread();
        add.start();
        dec.start();
        try {
            add.join();
        } catch (InterruptedException e) {}
        try {
            dec.join();
        } catch (InterruptedException e) {}
        System.out.println(Count.count);
    }
}
class Count{
    public static int count = 0;
    //创建一个共享实例lock
    //JVM只保证同一个锁在任意时刻只能被一个线程获取,但两个不同的锁在同一时刻可以被两个线程分别获取。
    public static final Object lock = new Object();
}
class AddThread extends Thread{
    public void run(){
        for (int i = 0; i < 1000; i++)
            //获得锁后加锁,代码块执行完毕自动释放锁
            synchronized (Count.lock
            ){
                Count.count++;
            }
    }
}
class DecThread extends Thread{
    public void run(){
        for (int i = 0; i < 1000; i++)
            synchronized (Count.lock
            ){
                Count.count--;
            }
    }
}
  • 不需要加锁的操作(JVM规范定义了几种原子操作):

(1)基本类型(long和double除外)赋值;

(2)引用类型赋值;

6.同步方法

  • 用synchronized修饰方法可以把整个方法变为同步代码块,synchronized方法加锁对象是this;
package threadDemo;

public class demo {
    public static void main(String[] args) {
        Count c1 = new Count();
        Thread thread1 = new Thread(()->{
            c1.add(3);
        });
        thread1.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(c1.get());

        Thread thread2 = new Thread(()->{
            c1.dec(3);
        });
        thread2.start();
        try {
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(c1.get());
    }
}
class Count{
    private int count = 0;
    public void add(int n){
        synchronized (this){
            count+=n;
        }
    }
    //等价于add方法加锁
    public synchronized void dec(int n){
            count-=n;
    }
    public int get(){
        return count;
    }
}
  • 一个类被设计为允许多线程正确访问,我们就说这个类就是“线程安全”的;
  • 一个类没有特殊说明,默认不是thread-safe;
  • 对static方法添加synchronized,锁住的是该类的Class实例。

7.死锁

  • JVM允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁,就叫做可重入锁。
  • Java的synchronized锁是可重入锁;
  • 死锁产生的条件是多线程各自持有不同的锁,并互相试图获取对方已持有的锁,导致无限等待;
  • 避免死锁的方法是多线程获取锁的顺序要一致。

8.wait和notify用于多线程协调运行

package threadDemo;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;

public class demo {
    public static void main(String[] args) throws InterruptedException{
        TaskQueue taskQueue = new TaskQueue();
        ArrayList<Thread> task = new ArrayList();
        //创建5个线程等待获取task
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread() {
                public void run() {
                    //执行task
                    while (true) {
                        try {
                            String s = taskQueue.getTask();
                            System.out.println("execute: "+ s);
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                }
            };
            t.start();
            task.add(t);
        }
        //创建一个线程不断添加10个添加task
        Thread add = new Thread(()->{
           for (int i = 0; i < 10; i++) {
               String t = "t-" + Math.random();
               System.out.println("add: " + t);
               taskQueue.addTask(t);
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {}
           }
        });
        add.start();
        add.join();
        Thread.sleep(100);
        for (Thread t: task){
            t.interrupt();
        }
    }
}
class TaskQueue {
    Queue<String> queue = new LinkedList<>();

    public synchronized void addTask(String s) {
        this.queue.add(s);
        //唤醒所有等待线程
        this.notifyAll();
    }

    public synchronized String getTask() throws InterruptedException{
        while (queue.isEmpty()) {
                //使线程进入等待状态
                this.wait();
        }
        return queue.remove();
    }
}

9.ReentrantLock

  • ReentrantLock可以替代synchronized锁进行同步,但获取锁更加安全;
  • 需要先获取到锁,再进入try{}代码块,最后在finally中释放锁;
  • 可以使用tryLock()尝试获取锁。
//对比
class Count1 {
    private int count;

    public void add(int n) {
        synchronized (this) {
            count += n;
        }
    }
}
class Count2 {
    private int count;
    //创建一个锁
    private final Lock lock = new ReentrantLock();

    public void add(int n) throws InterruptedException{
        //获取锁, 1s后没有获取到锁tryLock返回false
        if (lock.tryLock(1, TimeUnit.SECONDS)) {
            try {
                count += n;
            } finally {
                lock.unlock();
            }
        }
    }
}

10.Condition

  • 使用Condition对象来实现ReentrantLock的wait和notify;
class TaskQueue1 {
    private final Lock lock = new ReentrantLock();
    //必须从lock对象获取
    private final Condition condition = lock.newCondition();
    private Queue<String> queue = new LinkedList<>();

    public void add(String s) {
        lock.lock();
        try {
            queue.add(s);
            //类似于notify
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
    public String get() throws InterruptedException{
        lock.lock();
        try {
            while (queue.isEmpty()) {
                //类似于wait
                condition.await();
            }
            return queue.remove();
        }finally {
            lock.unlock();
        }
    }
}
  • await()可以在等待指定时间后,没有被signal()唤醒的话,自己醒来;
if (condition.await(1, TimeUnit.SECONDS)) {
                    //其他线程唤醒
                }else {
                    //自己唤醒
                };

11.ReadWriteLock

  • ReadWriteLock只允许一个线程写入,允许多个线程在没有写入时同时读取,可以提高读取效率,适合读多写少的场景。
class Counter {
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock rLock = readWriteLock.readLock();
    private final Lock wLock = readWriteLock.writeLock();
    private int[] counts = new int[10];

    public void inc(int i) {
        wLock.lock();
        try {
            counts[i]++;
        }finally {
            wLock.unlock();
        }
    }
    public int[] get() {
        rLock.lock();
        try {
            return Arrays.copyOf(counts, counts.length);
        }finally {
            rLock.unlock();
        }
    }
}

12.StampedLock

  • 乐观锁:乐观的估计读的过程中大概率不会有写入;
  • 悲观锁:读的过程中拒绝有写入;
  • StampedLock是不可重入锁;
package threadDemo;

import java.util.concurrent.locks.StampedLock;

public class Point {
    private final StampedLock stampedLock = new StampedLock();

    private double x;
    private double y;

    public void move(double x_a, double y_a) {
        //获取写锁
        long stamp = stampedLock.writeLock();
        try {
            x = x_a;
            y = y_a;
        }finally {
            //释放写锁
            stampedLock.unlockWrite(stamp);
        }
    }

    public double get() {
        //获得一个乐观读锁
        long stamp = stampedLock.tryOptimisticRead();
        double current_x = x;
        double current_y = y;
        //检查乐观锁读锁后是否有其他写锁发生
        if (!stampedLock.validate(stamp)) {
            //获取一个悲观锁
            stamp = stampedLock.readLock();
            try {
                current_x = x;
                current_y = y;
            }finally {
                //释放一个悲观锁
                stampedLock.unlock(stamp);
            }
        }
        return current_x + current_y;
    }
}

13.Concurrent集合

  • java.util.concurrent包提供的线程安全的并发集合可以大大简化多线程编程。

14.Atomic

  • java.util.concurrent.atomic包提供了一组原子操作的封装类。

15.线程池

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值