java多线程开发

1.并发和并行

并发:同一时间段内多个任务同时进行。

并行:同一时间点多个任务同时进行。

2.进程线程

进程(Process):进程是程序的一次动态执行过程,它经历了从代码加载、执行、到执行完毕的一个完整过程;同时也是并发执行的程序在执行过程中分配和管理资源的基本单位,竞争计算机系统资源的基本单位。

线程(Thread):线程可以理解为进程中的执行的一段程序片段,是进程的一个执行单元,是进程内可调度实体,是比进程更小的独立运行的基本单位,线程也被称为轻量级进程。

3.实现多线程的几种方式

(1)继承thread类

示例:

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/4 9:43
 */

class MyThread1 extends Thread {
    @Override
    public void run() {
        //线程的执行体
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}


public class Demo1 {
    public static void main(String[] args) {//程序的入口
        //step2:创建子类对象
        MyThread1 myThread = new MyThread1();
        //step3:调用start()方法
        //myThread.run();//这样写,是普通的方法的调用
        myThread.start();//相当于开启一个新的线程,这个新的线程跟main线程是平级的
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() +":" + i);
        }
    }
}

(2)实现runnable接口

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/5 8:42
 */

class myRunnable1 implements Runnable{

    @Override
    public void run() {
        //线程的执行体
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}


public class Demo2 {
    public static void main(String[] args) {
        //创建实现类的对象
        myRunnable1 myRunnable1 = new myRunnable1();
        Thread thread = new Thread(myRunnable1);
        //启动一个线程
        thread.start();
        //main线程执行
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() +":" + i);
        }

    }
}

 继承Thread 和实现Runnable的区别
如果一个类继承Thread,则不适合资源共享,但是如果实现了Runable接口的话,则很容易的实现资源共享,总结:实现Runnable接口比继承Thread类所具有的优势

  • 适合多个相同的程序代码的线程去共享同一个资源
  • 可以避免java中的单继承的局限性。
  • 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

(3)实现callable接口

 只有一个call方法,带有返回值。返回值是一个泛型格式的。

示例:

package com.example.demo.thread;


import java.util.concurrent.Callable;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/3 16:17
 */

public class Mycallable implements Callable<Integer> {

    private String name ;

    public Mycallable(String name) {
        this.name = name;
    }

    @Override
    public Integer call() throws Exception {
        Integer sum = 0;
        for (int i = 0; i < 20; i++) {
            sum += i;
            System.out.println(name + "开始执行" + i);
        }
        return sum;
    }
}

测试类

    @Test
    public void test() throws ExecutionException, InterruptedException {
        Mycallable name1 = new Mycallable("name1");
        Mycallable name2 = new Mycallable("name2");
        Mycallable name3 = new Mycallable("name3");

        FutureTask<Integer> task1 = new FutureTask<Integer>(name1);
        FutureTask<Integer> task2 = new FutureTask<Integer>(name2);
        FutureTask<Integer> task3 = new FutureTask<Integer>(name3);

        new Thread(task1).start();
        new Thread(task2).start();
        new Thread(task3).start();

        System.out.println(task1.get());
        System.out.println(task2.get());
        System.out.println(task3.get());


    }

结果:

name2开始执行0
name2开始执行1
name2开始执行2
name1开始执行0
name1开始执行1
name1开始执行2
name1开始执行3
name1开始执行4
name1开始执行5
name1开始执行6
name1开始执行7
name1开始执行8
name1开始执行9
name1开始执行10
name1开始执行11
name1开始执行12
name1开始执行13
name1开始执行14
name1开始执行15
name2开始执行3
name2开始执行4
name2开始执行5
name2开始执行6
name2开始执行7
name2开始执行8
name2开始执行9
name2开始执行10
name2开始执行11
name2开始执行12
name2开始执行13
name2开始执行14
name2开始执行15
name2开始执行16
name2开始执行17
name2开始执行18
name2开始执行19
name3开始执行0
name1开始执行16
name1开始执行17
name1开始执行18
name1开始执行19
name3开始执行1
name3开始执行2
name3开始执行3
190
190
name3开始执行4
name3开始执行5
name3开始执行6
name3开始执行7
name3开始执行8
name3开始执行9
name3开始执行10
name3开始执行11
name3开始执行12
name3开始执行13
name3开始执行14
name3开始执行15
name3开始执行16
name3开始执行17
name3开始执行18
name3开始执行19
190

3.线程的常用方法

(1).线程的优先级

我们可以通过传递参数给线程的 setPriority()来设置线程的优先级别
调整线程优先级: Java线程有优先级,优先级高的线程会获得较多的运行机会。优先级:只能反映 线程 的 中或者是 紧急程度,不能决定是否一定先执行
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:

示例:

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/9 12:20
 */

class Thread100 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }
}

class Thread101 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }
}

class Thread102 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }
}

public class Thread11 {
    public static void main(String[] args) {
        Thread100 t1 = new Thread100();
        Thread101 t2 = new Thread101();
        Thread102 t3 = new Thread102();


        Thread th1 = new Thread(t1, "线程1");
        Thread th2 = new Thread(t2, "线程2");
        Thread th3 = new Thread(t3, "线程3");

        th1.setPriority(Thread.MAX_PRIORITY);
        th2.setPriority(Thread.MIN_PRIORITY);
        th3.setPriority(Thread.NORM_PRIORITY);

        th1.start();
        th2.start();
        th3.start();

        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }


}

 结果:

main开始执行0
线程1开始执行0
main开始执行1
线程1开始执行1
main开始执行2
线程1开始执行2
main开始执行3
线程1开始执行3
main开始执行4
线程1开始执行4
线程1开始执行5
线程1开始执行6
线程1开始执行7
线程1开始执行8
线程1开始执行9
线程3开始执行0
线程3开始执行1
线程3开始执行2
线程3开始执行3
线程3开始执行4
线程3开始执行5
线程3开始执行6
线程3开始执行7
线程3开始执行8
线程3开始执行9
线程2开始执行0
线程2开始执行1
线程2开始执行2
线程2开始执行3
线程2开始执行4
线程2开始执行5
线程2开始执行6
线程2开始执行7
线程2开始执行8
线程2开始执行9

(2).线程的休眠

使用线程的sleep()方法让线程休眠指定的毫秒数,在休眠结束的时候继续执行线程。

示例1:

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/4 9:43
 */

class MyThread1 extends Thread {

    private static Integer target = 12;

    public MyThread1(String name) {
        super(name);
    }

    @Override
    public void run() {
        //线程的执行体
        for (int i = 0; i < 4; i++) {
//            System.out.println(Thread.currentThread().getName() + ":" + i);
            System.out.println("卖了一张票,还剩下的票数:" + target--);
        }
    }
}


public class Demo1 {
    public static void main(String[] args){//程序的入口
        //step2:创建子类对象
        MyThread1 myThread1 = new MyThread1("线程一");
        MyThread1 myThread2 = new MyThread1("线程二");
        MyThread1 myThread3 = new MyThread1("线程三");
        //step3:调用start()方法
        //myThread.run();//这样写,是普通的方法的调用
        myThread1.start();//相当于开启一个新的线程,这个新的线程跟main线程是平级的

        try {
            Thread.sleep(1000*2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread2.start();
        myThread3.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() +":" + i);
        }
    }
}

 myThread1线程执行完成之后,进行休眠两秒的操作,其他的线程myThread2和myThread3以及main线程等待两秒之后才会执行。

示例2:

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/9 12:20
 */

class Thread100 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }
}

class Thread101 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            if (i == 2){
                try {
                    System.out.println(Thread.currentThread().getName() + "开始休眠" + "------------------");
                    Thread.sleep(1000*5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
                System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }
}

class Thread102 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }
}

public class Thread11 {
    public static void main(String[] args) {
        Thread100 t1 = new Thread100();
        Thread101 t2 = new Thread101();
        Thread102 t3 = new Thread102();

        new Thread(t1, "线程1").start();
        new Thread(t2, "线程2").start();
        new Thread(t3, "线程3").start();

        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }


}

结果:

main开始执行0
main开始执行1
main开始执行2
main开始执行3
main开始执行4
线程3开始执行0
线程3开始执行1
线程3开始执行2
线程3开始执行3
线程3开始执行4
线程1开始执行0
线程1开始执行1
线程1开始执行2
线程1开始执行3
线程1开始执行4
线程2开始执行0
线程2开始执行1
线程2开始休眠------------------
线程2开始执行2
线程2开始执行3
线程2开始执行4

 代码中创建了3个线程,在加上主线程,线程2中执行休眠方法,让线程2休眠,其他线程继续执行。休眠时间结束后,线程2继续执行。

(3)线程的让步

Thread.yield()方法作用是: 暂停当前正在执行的线程对象(及放弃当前拥有的up资源),并执行其他线程
yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先的其他线程获得运行机会,因此,使用 yled() 的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。案例:创建两个线程A,B,分别各打印1000次从1开始每次增加1,其中B一个线程,每打印一次就yied一次,观察实验结果。

示例:

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/9 12:20
 */

class Thread100 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
            Thread.yield();
        }
    }
}

class Thread101 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }
}

class Thread102 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }
}

public class Thread11 {
    public static void main(String[] args) {
        Thread100 t1 = new Thread100();
        Thread101 t2 = new Thread101();
        Thread102 t3 = new Thread102();


        Thread th1 = new Thread(t1, "线程1");
        Thread th2 = new Thread(t2, "线程2");
        Thread th3 = new Thread(t3, "线程3");

        //设置线程的执行优先级
        th1.setPriority(Thread.MAX_PRIORITY);
        th2.setPriority(Thread.MAX_PRIORITY);
        th3.setPriority(Thread.NORM_PRIORITY);


        th1.start();
        th2.start();
        th3.start();

        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }


}

结果:

main开始执行0
main开始执行1
main开始执行2
main开始执行3
main开始执行4
线程1开始执行0
线程2开始执行0
线程2开始执行1
线程2开始执行2
线程2开始执行3
线程2开始执行4
线程2开始执行5
线程2开始执行6
线程2开始执行7
线程2开始执行8
线程2开始执行9
线程1开始执行1
线程1开始执行2
线程1开始执行3
线程3开始执行0
线程1开始执行4
线程3开始执行1
线程3开始执行2
线程3开始执行3
线程3开始执行4
线程3开始执行5
线程3开始执行6
线程3开始执行7
线程3开始执行8
线程3开始执行9
线程1开始执行5
线程1开始执行6
线程1开始执行7
线程1开始执行8
线程1开始执行9

sleep()和yield()的区别
sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行:yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出CPU 占有权,但让出的时间是不设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把PU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用 slee 方法,又没有受到IO 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

(4).线程的合并

Thread 中,join()方法的作用是调用线程等待该线程完成后,才能继续往下运行。join是Thread类的一个方法,启动线程后直接调用,join的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止,也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行
为什么要用ioin()方法
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join方法了。

示例:

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/5 8:42
 */

class myRunnable2 implements Runnable {

    private Integer target = 4;

    @Override
    public void run() {
        //线程的执行体
        for (int i = 0; i < 4; i++) {
//            System.out.println(Thread.currentThread().getName() + ":" + i);
            System.out.println("卖了一张票,还剩下的票数:" + --target);
        }
    }
}


public class Demo3 {
    public static void main(String[] args) {
        System.out.println("主线程执行开始");
        //创建实现类的对象
        myRunnable2 myRunnable1 = new myRunnable2();
        Thread thread1 = new Thread(myRunnable1, "线程1");

        //启动一个线程
        thread1.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //main线程执行
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
        System.out.println("主线程执行结束");
    }
}

 结果:

主线程执行开始
卖了一张票,还剩下的票数:3
卖了一张票,还剩下的票数:2
卖了一张票,还剩下的票数:1
卖了一张票,还剩下的票数:0
main:0
main:1
main:2
main:3
main:4
main:5
main:6
main:7
main:8
main:9
主线程执行结束

(5)守护线程

守护线程setDaemon(true):设置守护线程
线程有两类: 用户线程(前台线程) 、守护线程 (后台线程)如果程序中所有前台线程都执行完毕了,后台线程会自动结束。结束垃圾回收器线程属于守护线程

示例1:

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/11 15:09
 */

class Deamon1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }
}

public class ThreadDeamon {
    public static void main(String[] args) {
        Deamon1 deamon1 = new Deamon1();
        Thread th1 = new Thread(deamon1, "线程1");
        th1.setDaemon(true);//设置为守护线程
        th1.start();


        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "开始执行" + i);
        }
    }
}

结果:

main开始执行0
main开始执行1
main开始执行2
main开始执行3
main开始执行4
main开始执行5
main开始执行6
main开始执行7
main开始执行8
main开始执行9
线程1开始执行0
线程1开始执行1
线程1开始执行2
线程1开始执行3
线程1开始执行4
线程1开始执行5
线程1开始执行6
线程1开始执行7
线程1开始执行8
线程1开始执行9

主线程执行完成之后,即使线程1还没有执行完,也会结束执行。 

4.线程的生命周期

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
(1)新建状态 (New)
当线程对象对创建后,即进入了新建状态,如: Thread t = new MyThread();
(2)就绪状态(Runnable)
当调用线程对象的start方法(t.start),线程进入就绪状态,处于就状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start(),此线程立即就会执行;
(3)运行状态(Running)

当CP开始调度处于就绪状态的线程时,此线程才得以真正执行,即进入到运行状态。就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。

(4)阻塞状态

 处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态,根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait0方法,使本线程进入到等待阻塞状态;
2.同步阻塞-- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3其他阳塞 通过调用线程的sleep或join发出了I/O请求时,线程会进入到塞状态,当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
入就绪状态。

(5)死亡状态

线程执行完了或者因为异常退出了run()方法,该线程结束生命周期。

多线程状态之间的切换

就绪状态转换为运行状态:当此线程得到处理器资源。
运行状态转换为就绪状态:当此线程主动调用yield方法或在运行过程中失去处理器资源
运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。
此处需要特别注意的暴:当调用线程的yielid方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绿状态中的个线程具有一定的随机性,因此,可能会出现A线程调用了yield方法后,接下来CPU仍然调度了A线程的情况。

5.线程安全问题

当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性原子操作: 不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

6.确保线程安全的方法

(1)同步代码块

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。

1.锁对象 可以是任意类型。
2.多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁 谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

不加锁的示例:

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/7 11:51
 */

class TicketDemo implements Runnable{
    public int tikcet = 10;
    Object lock = new Object();

    @Override
    public void run() {
        while (true) {
//            synchronized (lock) {
                if (tikcet > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩下" + tikcet-- + "张票");
                }
            }
//        }
    }
}


public class Ticket {
    public static void main(String[] args) {
        TicketDemo ticketDemo = new TicketDemo();

        new Thread(ticketDemo).start();
        new Thread(ticketDemo).start();
        new Thread(ticketDemo).start();

    }

}

运行结果:

Thread-2卖了一张票,还剩下10张票
Thread-1卖了一张票,还剩下9张票
Thread-0卖了一张票,还剩下10张票
Thread-0卖了一张票,还剩下8张票
Thread-2卖了一张票,还剩下8张票
Thread-1卖了一张票,还剩下8张票
Thread-1卖了一张票,还剩下6张票
Thread-2卖了一张票,还剩下7张票
Thread-0卖了一张票,还剩下7张票
Thread-0卖了一张票,还剩下5张票
Thread-1卖了一张票,还剩下3张票
Thread-2卖了一张票,还剩下4张票
Thread-0卖了一张票,还剩下2张票
Thread-2卖了一张票,还剩下2张票
Thread-1卖了一张票,还剩下2张票
Thread-1卖了一张票,还剩下1张票
Thread-2卖了一张票,还剩下1张票
Thread-0卖了一张票,还剩下1张票

 会出现重复卖票的情况。

加锁的示例:

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/7 11:51
 */

class TicketDemo implements Runnable {
    public int tikcet = 10;
    Object lock = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                if (tikcet > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩下" + tikcet-- + "张票");
                }
            }
        }
    }
}


public class Ticket {
    public static void main(String[] args) {
        TicketDemo ticketDemo = new TicketDemo();

        new Thread(ticketDemo).start();
        new Thread(ticketDemo).start();
        new Thread(ticketDemo).start();

    }

}

结果:

Thread-0卖了一张票,还剩下10张票
Thread-0卖了一张票,还剩下9张票
Thread-0卖了一张票,还剩下8张票
Thread-0卖了一张票,还剩下7张票
Thread-0卖了一张票,还剩下6张票
Thread-0卖了一张票,还剩下5张票
Thread-0卖了一张票,还剩下4张票
Thread-0卖了一张票,还剩下3张票
Thread-0卖了一张票,还剩下2张票
Thread-0卖了一张票,还剩下1张票

结果之后同步代码块,每次只能有一个线程操作此代码。保证了线程安全。 

(2)同步方法

使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

语法:

只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
线程退出同步方法时,会释放相应的互斥锁标记。
如果方式是静态,锁是类名.class。

示例:

锁相当于this。

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/7 12:11
 */

class Ticket2Demo implements Runnable {

    public Integer ticket = 10;
    Object lock = new Object();

    @Override
    public void run() {
        maipiao();
    }

    public synchronized void maipiao() {
        while (true) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩下" + ticket-- + "张票");
                }
        }
    }
}


public class Ticket2 {
    public static void main(String[] args) {
        Ticket2Demo ticket2Demo = new Ticket2Demo();

        new Thread(ticket2Demo).start();
        new Thread(ticket2Demo).start();
        new Thread(ticket2Demo).start();

    }

}

也可以使用静态方法,锁相当于当前类名。

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/7 12:11
 */

class Ticket2Demo implements Runnable {

    public static Integer ticket = 10;
    Object lock = new Object();

    @Override
    public void run() {
        maipiao();
    }

    public synchronized static void maipiao() {
        while (true) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩下" + ticket-- + "张票");
                }
        }
    }
}


public class Ticket2 {
    public static void main(String[] args) {
        Ticket2Demo ticket2Demo = new Ticket2Demo();

        new Thread(ticket2Demo).start();
        new Thread(ticket2Demo).start();
        new Thread(ticket2Demo).start();

    }

}

synchronized注意点
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法我们使用当前方法所在类的字节码对象(类名.class)。

(3)lock

当两个线程同时执行add方法,造成线程安全问题,可以通过创建一个lock的方式,在方法的开始加锁,方法执行完之后解锁。

package com.example.demo.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/8 12:06
 */

public class MyThread2 implements Runnable{
    private int ticket = 10;
    private final Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while(this.sale()) {}
    }
    public boolean sale() {
        lock.lock();
        try{
            if(ticket == 0) {
                System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);
                return false;
            }
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return true;
    }

}
package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/8 12:08
 */

public class MyThread2T {
    public static void main(String[] args) {
        MyThread2 myThread2 = new MyThread2();
//        MyThread3 myThread2 = new MyThread3();

        new Thread(myThread2,"售票员1").start();
        new Thread(myThread2,"售票员2").start();
        new Thread(myThread2,"售票员3").start();
    }
}

 结果:

售票员1 正在售票,还剩余票数为:10
售票员1 正在售票,还剩余票数为:9
售票员1 正在售票,还剩余票数为:8
售票员2 正在售票,还剩余票数为:7
售票员2 正在售票,还剩余票数为:6
售票员2 正在售票,还剩余票数为:5
售票员2 正在售票,还剩余票数为:4
售票员2 正在售票,还剩余票数为:3
售票员3 正在售票,还剩余票数为:2
售票员1 正在售票,还剩余票数为:1
售票员1的票已经全部售完,此时的票数量为:0
售票员2的票已经全部售完,此时的票数量为:0
售票员3的票已经全部售完,此时的票数量为:0

7.线程通信

(1)概述

多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
为什么要处理线程间通信

多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源
多个线理在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即---等待唤醒机制。

等待唤醒机制
这是多个线程间的一种协作机制,谈到线程我们经想到的是线程间的竞争,比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制,就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。就是在一个线程进行了规定操作后,就进入等待状态(wait),等待其他线程执行完他们的指定代码过后再将其唤醒(notify);在有多个线程进行等待时,如果需要,可以使用 notifyAl来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。

 示例:

package com.example.demo.thread;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/5 19:40
 */

public class WaitNotify {

    public static void main(String[] args) {

        Object lock = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("线程1:顾客1告诉老板要早餐");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("顾客1开始吃早餐");
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("线程2:顾客2告诉老板要早餐");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("顾客2开始吃早餐");
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock) {
                    System.out.println("老板告诉顾客做好了");
                    lock.notifyAll();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("线程3:顾客3告诉老板要早餐");
                    try {
                        lock.wait();
//                        lock.wait(4000);会等待指定的时间,如果没有唤醒,就自己唤醒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("顾客3开始吃早餐");
                }
            }
        }).start();


    }
}

运行结果:

线程1:顾客1告诉老板要早餐
线程2:顾客2告诉老板要早餐
线程3:顾客3告诉老板要早餐
老板告诉顾客做好了
顾客3开始吃早餐
顾客2开始吃早餐
顾客1开始吃早餐

注意: 所有的等待、通知方法必须在对加锁的同步代码块中。
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
1.wait:线程不再活动,不再参与调度,进入 wait set(锁池)中,因此不会浪费 CPU资源,也不会去竞争锁了,这时的线程状态即是 WAITING,它还要等着别的线程执行一个特别的动作,也即是“通知(notify) 。使这个对象上等待的线程从wait set中释放出来,重新进入到调度队列(ready queue)中wait(long m):wait方法如果在指定的毫秒之后,还没有被notify唤醒,就会自动醒来。
sleep(long m):不会释放锁。

2. notify: 则选取所通知对象的 wait set 中的一个线程释放,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。

3.notifyAll: 则释放所通知对象的 wait set 上的全部线程。

注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。总结如下:
。如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
。否则,从 wait set 出来,又进入 entry set,线程就从 WAITING状态又变成 BLOCKED 状态调用wait和notify方法需要注意的细节
1.wait方法与notiy方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notiy唤醒使用同一个锁对象调用的wait方法后的线程

2. wait方法与notify方法是属于Object类的方法的,因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。

3.wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

8.线程死锁

线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

示例:

public class DeadLockDemo {
    private static Object resource1 = new Object();//资源 1
    private static Object resource2 = new Object();//资源 2

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "线程 1").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "线程 2").start();
    }
}

结果:

Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1

线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过Thread.sleep(1000);让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。

9.线程池

(1).概述

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。合理利用线程池能够带来三个好处:
1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

3.提高线程的可管理性,可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下。每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

(2).创建方式

Java里面线程池的顶级接口是java.util.concurrent.Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是java.util.concurrent.ExecutorService。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较忧的,因此在java.util.concurent.Executors 线程工厂
类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象.
Java类库提供了许多静态方法来创建一个线程池.
Executors类中创建线程池的方法如下:
a、newFixedThreadPool 创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。

b、newCachedThreadPool 创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模无限制。

c、newSingleThreadPoolExecutor 创建一个单线程的Executor,确保任务串行执行。
d、newScheduledThreadPool 创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer。

(3)示例1

package com.example.demo.thread;

import com.example.demo.domain.BmsBillMemo;
import org.jetbrains.annotations.NotNull;

import java.util.concurrent.*;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/8 11:54
 */

public class ThreadPool {
    public static void main(String[] args) {
        //创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。
//        ExecutorService executorService = Executors.newFixedThreadPool(4);
        //创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模无限制。
//        ExecutorService executorService = Executors.newCachedThreadPool(Executors.defaultThreadFactory());
        //创建一个单线程的Executor,确保任务串行执行。
        ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
        //创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer。
        //ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);

        run1 r1 = new run1();
        run1 r2 = new run1();
        run1 r3 = new run1();

        //executorService.schedule(r1,3, TimeUnit.SECONDS);



        executorService.submit(r1);
        executorService.submit(r2);
        executorService.submit(r3);

        executorService.shutdown();

    }
}

class run1 implements Runnable {

    Object lock = new Object();

    @Override
    public void run() {

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock) {
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + "    " + i);
            }
        }
    }
}

示例2

package com.example.demo.thread;

import com.example.demo.domain.BmsBillMemo;
import org.jetbrains.annotations.NotNull;

import java.util.concurrent.*;

/**
 * @author linaibo
 * @version 1.0
 * Create by 2023/2/8 11:54
 */

public class ThreadPool {
    public static void main(String[] args) {
        //创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。
//        ExecutorService executorService = Executors.newFixedThreadPool(4);
        //创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模无限制。
//        ExecutorService executorService = Executors.newCachedThreadPool(Executors.defaultThreadFactory());
        //创建一个单线程的Executor,确保任务串行执行。
//        ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());
        //创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer。
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);

        run1 r1 = new run1();
        //run1 r2 = new run1();
       // run1 r3 = new run1();

        executorService.schedule(r1,3, TimeUnit.SECONDS);



      //  executorService.submit(r1);
      //  executorService.submit(r2);
      //  executorService.submit(r3);

      //  executorService.shutdown();

    }
}

class run1 implements Runnable {

    Object lock = new Object();

    @Override
    public void run() {

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock) {
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + "    " + i);
            }
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值