java基础之多线程

1. 程序、进程与线程
  1. 程序是为完成特定任务、用某种语言编写的一组指令的集合。
  2. 进程是正在运行的一个程序,是操作系统调度和资源分配的基本单位。 
  3. 线程是一个进程内部的一条执行路径,是CPU调度和资源分配的基本单位。
2. 多线程的优点
  1. 提高应用程序的响应速度,可增强用户体验。
  2. 提高计算机系统CPU的利用率。
  3. 改善程序结构。
 3.并行与并发
  1. 并行是指多个CPU在同一时刻执行多个任务。
  2. 并发是一个CPU在一个时间段内执行多个任务。
4.创建线程
        4.1继承Thread,重写run方法,调用start动线程
/**
 *      继承Thread,重写run方法,调用start启动线程
 */
public class CusThread extends Thread{
    /**
     * main方法的线程是:main
     * run方法的线程是:Thread-0
     * 线程执行体
     */
    @Override
    public void run() {
        System.out.println("run方法的线程是:" + Thread.currentThread().getName());
        System.out.println("线程执行体");
    }
    public static void main(String[] args) {
        System.out.println("main方法的线程是:" + Thread.currentThread().getName());
        CusThread thread = new CusThread();
        thread.start();
    }
}
                4.1.1 能否调用run方法代替start方法?

                        不能,调用run方法只是单纯的执行run方法体,而start方法会创建一个新的线程去执行run方法体。

                4.1.2 已经调用start方法的线程,能否再去调用start方法?

                        不能,会抛出 IllegalThreadStateException 的异常。

public synchronized void start() {
        
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        /**
        *    省略代码...
        */
    }
        4.2 实现Runnable接口, 重写run方法,调用start启动线程
/**
 *      实现Runnable接口, 重写run方法,调用start启动线程
 */
public class CusThread implements Runnable{
    /**
     * main方法的线程是:main
     * run方法的线程是:Thread-0
     * 线程执行体
     */
    @Override
    public void run() {
        System.out.println("run方法的线程是:" + Thread.currentThread().getName());
        System.out.println("线程执行体");
    }
    public static void main(String[] args) {
        System.out.println("main方法的线程是:" + Thread.currentThread().getName());
        Runnable runnable = new CusThread();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

         Thead类实际上也是Runnable接口的实现类

public class Thread implements Runnable {

    private Runnable target;

    private Thread(ThreadGroup g, Runnable target, String name,
                   long stackSize, AccessControlContext acc,
                   boolean inheritThreadLocals) {
                /**
                 * 省略代码...
                 */
                this.target = target;
                /**
                 * 省略代码...
                 */
    }

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}
        4.3 实现Callable接口, 重写run方法,调用start启动线程

        调用get方法获取返回值会阻塞当前线程的执行,等到分线程执行完毕。

/**
 *      实现Callable接口, 重写run方法,调用start启动线程
 */
public class CusThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return 2<<3;
    }

    public static void main(String[] args){
        CusThread cusThread = new CusThread();
        FutureTask<Integer> futureTask = new FutureTask(cusThread);
        Thread thread = new Thread(futureTask);
        thread.start();
        Integer result = null;
        try {
            result = futureTask.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(result);
    }
}
5.常用方法
        5.1 启动线程
public synchronized void start(){}
        5.2 返回当前线程
public static native Thread currentThread();
        5.3 休眠一段时间(不会放弃锁)
public static native void sleep(long millis) throws InterruptedException;
        5.4 线程让步
public static native void yield();
         5.5 阻塞线程,直到join() 方法加入的join 线程执行完为止
public final void join() throws InterruptedException {}
        5.6 是否存活
public final native boolean isAlive();
        5.7.获取线程优先级
public final int getPriority(){}
                5.7.1Thread内部声明的三个优先级常量   
    public static final int MIN_PRIORITY = 1;
    public static final int NORM_PRIORITY = 5;//default
    public static final int MAX_PRIORITY = 10;
6. 线程的生命周期
        6.1 JDK5线程的生命周期

        6.2 JDK17线程的生命周期
public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

          

7.多线程安全问题

        例如卖票超卖。

/**
 * 多线程出现的问题
 */
public class SaleTicket implements Runnable{

    private static int ticket = 100;
    @Override
    public void run() {

        while (true){
           if(ticket > 0){
               System.out.println(Thread.currentThread().getName() +"卖了第" + ticket + "张票");
               ticket--;
           }else {
               break;
           }
        }
    }
    public static void main(String[] args) {

        Runnable saleTicket = new SaleTicket();
        Thread window1 = new Thread(saleTicket);
        Thread window2 = new Thread(saleTicket);
        Thread window3 = new Thread(saleTicket);

        window1.start();
        window2.start();
        window3.start();
    }
}

        结果:导致重复卖票

Thread-0卖了第100张票
Thread-1卖了第100张票
Thread-2卖了第100张票
Thread-1卖了第98张票
Thread-0卖了第99张票

        原因:操作不是原子操作

        解决办法:java同步机制

8.java同步机制

        同步代码块:

                synchronized (锁){ // 需要被同步的代码; }

                锁可以使用this或者类.class,但必须保证多个线程使用同一把锁。

                需要被同步的代码即是对共享资源的操作。

        使用同步代码块解决问题:

public class SyncSaleTicket implements Runnable {

    private static int ticket = 100;
    @Override
    public void run() {

        while (true){
            synchronized (this) {
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName() +"卖了第" + ticket + "张票");
                    ticket--;
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }else {
                    break;
                }
            }
        }
    }
    public static void main(String[] args) {

        Runnable saleTicket = new SyncSaleTicket();
        Thread window1 = new Thread(saleTicket);
        Thread window2 = new Thread(saleTicket);
        Thread window3 = new Thread(saleTicket);

        window1.start();
        window2.start();
        window3.start();
    }

}

        结果:没有超卖现象

Thread-0卖了第100张票
Thread-2卖了第99张票
Thread-1卖了第98张票
Thread-2卖了第97张票

        使用同步方法:

        非静态同步方法的锁是this,静态同步方法的锁是当前类.class,考虑方法是否非静态来保证多个线程使用同一把锁。

public class SyncMethodSaleTicket implements Runnable {

    private static int ticket = 100;
    private static boolean flag = true;
    @Override
    public void run() {
        while (flag){
            sale();
        }

    }
    public synchronized void sale() {
        if(ticket > 0){
            System.out.println(Thread.currentThread().getName() +"卖了第" + ticket + "张票");
            ticket--;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }else {
            flag = false;
        }
    }
    public static void main(String[] args) {

        Runnable saleTicket = new SyncMethodSaleTicket();
        Thread window1 = new Thread(saleTicket);
        Thread window2 = new Thread(saleTicket);
        Thread window3 = new Thread(saleTicket);

        window1.start();
        window2.start();
        window3.start();
    }

}

        synchronized的好处和弊端

                synchronized解决了线程的安全问题,但是synchronized是串行化执行的,相对无锁结构效率比较低。

9.单例之饿汉式
public class Singleton {
    
    //避免指令重拍
    private static volatile Singleton singleton ;

    private Singleton(){}

    public static Singleton getInstance(){
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2);
    }
}
10.线程死锁

        不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁。

        死锁的案例:

public class DeathLock {

    private static final Object obj1 = new Object();
    private static final Object obj2 = new Object();

    public static void main(String[] args) {

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始执行");
                synchronized (obj1) {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "持有资源obj1,希望得到资源obj2");
                    synchronized (obj2) {
                        System.out.println(Thread.currentThread().getName() + "得到资源obj2");
                    }
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始执行");
                synchronized (obj2) {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "持有资源obj2,希望得到资源obj1");
                    synchronized (obj1) {
                        System.out.println(Thread.currentThread().getName() + "得到资源obj1");
                    }
                }
            }
        });
        thread1.start();
        thread2.start();
        System.out.println("方法结束.....");
    }
}

        结果:可以看出Thread-1得不到obj1,Thread-0得不到obj2,处在一个僵持的状态。

方法结束.....
Thread-1开始执行
Thread-0开始执行
Thread-1持有资源obj2,希望得到资源obj1
Thread-0持有资源obj1,希望得到资源obj2

        诱发死锁的原因:

                互斥条件、占用且等待、不可抢夺、循环等待。

        解决死锁:

  1. 互斥条件无法被破坏,因为线程需要互斥解决安全问题。
  2. 可以考虑一次性申请所需要的资源,这样就不存在等待问题。
  3. 占用资源的线程在申请其他资源的时候,如果申请不到,就考虑放弃已经占有的资源。
  4. 可以将资源改为线性顺序,申请资源的时候先申请序号较小的,这样避免循环等待的问题。
11. Lock

        从JDK 5.0开始,Java提供的显式定义同步锁,在java.util.concurrent.locks包下的接口,需要确保多线程使用同一个Lock,需要声明为static final。

package java.util.concurrent.locks;
public interface Lock {}
public class LockSaleTicket implements Runnable {

    private static int ticket = 100;
    private static boolean flag = true;
    private static final Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (flag){
            sale();
        }
    }
    public void sale() {
        lock.lock();
        if(ticket > 0){
            System.out.println(Thread.currentThread().getName() +"卖了第" + ticket + "张票");
            ticket--;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }else {
            flag = false;
        }
        lock.unlock();
    }
    public static void main(String[] args) {

        Runnable saleTicket = new LockSaleTicket();
        Thread window1 = new Thread(saleTicket);
        Thread window2 = new Thread(saleTicket);
        Thread window3 = new Thread(saleTicket);

        window1.start();
        window2.start();
        window3.start();
    }
}
  12.synchronized 与Lock 的对比  

        优先使用顺序:

                Lock——>同步代码块(已经进入了方法体,分配了相应资源)——> 同步方法 (在方法体之外)。

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放。
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁。
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有 更好的扩展性(提供更多的子类)
13.线程通信

        线程通信是指线程利用wait() 与notify() 和notifyAll()进行一个协调合作,以上都是object的方法,此三个方法都要在同步代码块中使用,调用者而必须是同步监视器。

        例如交替打印1-100。

public class PrintNumber implements Runnable {

    private static int num = 1;
    @Override
    public void run() {

        while (true){
            synchronized (this) {
                this.notifyAll();
                if(num <= 100){
                    System.out.println(Thread.currentThread().getName() +"打印了" + num );
                    num++;
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }else {
                    break;
                }
            }
        }
    }
    public static void main(String[] args) {

        Runnable saleTicket = new PrintNumber();
        Thread window1 = new Thread(saleTicket);
        Thread window2 = new Thread(saleTicket);

        window1.start();
        window2.start();
    }

}

        结果 :

Thread-0打印了1
Thread-1打印了2
Thread-0打印了3
Thread-1打印了4
Thread-0打印了5
Thread-1打印了6
14.sleep 和 wait 的区别
  1. sleep是Thread的一个静态方法,wait是Object的一个方法。
  2. sleep和wait调用后都会进入阻塞状态
  3. sleep不会释放锁资源,wait会释放锁资源进入等待池中。
15. 线程池

        提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中.

        好处:

  1. 提高响应速度(减少了创建新线程的时间)。
  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)。
  3. 便于线程管理。

        线程池的使用:

        

public class ThreadPool {

    private static final ExecutorService service = Executors.newFixedThreadPool(10);
    public static void main(String[] args) {
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable .... 1");
            }
        };
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable .... 2");
            }
        };
        service.execute(runnable1);
        service.execute(runnable2);
        System.out.println("main ....");
        service.shutdown();
    }
}
        15. 1线程池的核心创建

        依次为核心线程数、最大线程数、存活时间、存活时间单位、队列、线程工厂、拒绝策略

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值