多线程学习资料

多线程学习资料

线程与进程

进程:进程是执行程序的一次执行过程,是系统资源分配的单位。通常一个进程可以包含多个线程。

线程:线程是CPU调度和执行的单位。

  • 线程是独立的执行路径。

  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程。

  • main() 称之为主线程,为系统的入口,用于执行整个程序。

  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序不能人为干预。、

  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。

  • 线程会带来额外的开销,如cpu调度时间,并发控制开销。

  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

线程的创建方式
继承Thread类
  • 自定义线程类继承Thread类

  • 重写run()方法,编写线程执行体

  • 创建线程对象,调用start()方法启动线程

//主方法
//创建线程方式一:1.继承Thread类 2.重写run()方法 3.调用start()开启线程
//注意:线程开启不一定立即执行,由CPU调度执行(线程可能交替执行)
public class Application {
    //main线程,主线程
    public static void main(String[] args) {
​
        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();
​
        //调用start()方法开启线程
        testThread1.start();
​
        for (int i = 0; i <= 100; i++) {
            System.out.println("我在看伤寒杂病论"+i);
        }
    }
}
​
//线程类
public class TestThread1 extends Thread {
    
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i <= 100; i++) {
            System.out.println("我在看黄帝内经"+i);
        }
    }
}
===========================执行输出====================================
我在看伤寒杂病论0
我在看黄帝内经0
我在看黄帝内经1
我在看黄帝内经2
我在看黄帝内经3
我在看伤寒杂病论1
我在看伤寒杂病论2
我在看伤寒杂病论3
我在看伤寒杂病论4
我在看伤寒杂病论5
我在看伤寒杂病论6
......

注:继承Thread类的多线程实现方式具有单继承的局限性。

实现Runnable接口
  • 自定义线程类类实现Runnable接口

  • 重写run()方法,编写线程执行体

  • 创建线程对象,调用start()方法启动线程(需要丢入rnnable接口实现类)

//主方法
//创建线程方式二:实现runnable接口,重写run()方法,执行线程需要丢入runnable实现类,调用star()方法启动线程
public class Application {
    //main线程,主线程
    public static void main(String[] args) {
​
        //创建runnable接口的实现类对象
        TestThread2 testThread2 = new TestThread2();
​
        //创建线程对象,通过线程对象来开启我们的线程(代理)
        Thread thread = new Thread(testThread2);
​
        //调用start()方法开启线程
        thread.start();
​
        for (int i = 0; i <= 100; i++) {
            System.out.println("我在看伤寒杂病论"+i);
        }
    }
}
​
//线程类
public class TestThread2 implements Runnable {
    
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i <= 100; i++) {
            System.out.println("我在看神农本草经"+i);
        }
    }
    
}
​
===========================执行输出====================================
我在看伤寒杂病论0
我在看伤寒杂病论1
我在看伤寒杂病论2
我在看伤寒杂病论3
我在看伤寒杂病论4
我在看伤寒杂病论5
我在看伤寒杂病论6
我在看伤寒杂病论7
我在看伤寒杂病论8
我在看神农本草经0
我在看神农本草经1
我在看神农本草经2
我在看神农本草经3
我在看神农本草经4
我在看神农本草经5
我在看神农本草经6
我在看神农本草经7
我在看神农本草经8
我在看神农本草经9
......

注:避免了单继承的局限性,方便同一个对象被多个线程使用

多个线程操作一个对象示例(模拟卖票):

//多个线程操作同一个对象
//并发问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱(后续线程同步部分解决)
public class Application {
    //main线程,主线程
    public static void main(String[] args) {
​
        //创建runnable接口的实现类对象
        SaleTickets saleTickets = new SaleTickets();
​
        //创建线程对象,通过线程对象来开启我们的线程(代理模式)
        Thread thread1 = new Thread(saleTickets,"Tony");
        Thread thread2 = new Thread(saleTickets,"Tim");
        Thread thread3 = new Thread(saleTickets,"Tom");
​
        //调用start()方法开启线程
        thread1.start();
        thread2.start();
        thread3.start();
​
    }
}
​
public class SaleTickets implements Runnable {
    //票数
    private int ticketNums = 10;
​
    @Override
    public void run() {
        while (true){
            if(ticketNums <= 0){
                break;
            }
​
            //模拟延时(可以放大问题的发生性)
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
            //卖票
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
        }
    }
}
​
===========================执行输出====================================
Tom拿到了第10张票
Tim拿到了第10张票
Tony拿到了第9张票
Tony拿到了第8张票
Tom拿到了第8张票
Tim拿到了第8张票
Tom拿到了第7张票
Tim拿到了第7张票
Tony拿到了第7张票
Tom拿到了第6张票
Tony拿到了第6张票
Tim拿到了第6张票
Tony拿到了第5张票
Tim拿到了第5张票
Tom拿到了第5张票
Tim拿到了第4张票
Tom拿到了第4张票
Tony拿到了第4张票
Tom拿到了第3张票
Tony拿到了第3张票
Tim拿到了第3张票
Tom拿到了第2张票
Tony拿到了第2张票
Tim拿到了第2张票
Tony拿到了第1张票
Tim拿到了第1张票
Tom拿到了第1张票

实现Callable接口
  • 实现Callable接口,需要返回值类型

  • 重写call方法,需要抛出异常

  • 创建目标对象

  • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPoo(1);

  • 提交执行:Future<Boolean> result1 = ser.submit(t1);

  • 获取结果:boolean r1 = result1.get();

  • 关闭服务:ser.shutdownNow();

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
​
//主方法
public class Application {
    //main线程,主线程
    public static void main(String[] args) throws ExecutionException, InterruptedException {
​
        //创建callable接口的实现类对象
        TestCallable testCallable1 = new TestCallable("皇帝内经");
        TestCallable testCallable2 = new TestCallable("神农本草经");
        TestCallable testCallable3 = new TestCallable("伤寒杂病论");
​
        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
​
        //提交执行
        Future<Boolean> future1 = ser.submit(testCallable1);
        Future<Boolean> future2 = ser.submit(testCallable2);
        Future<Boolean> future3 = ser.submit(testCallable3);
​
        //获取结果
        boolean result1 = future1.get();
        boolean result2 = future2.get();
        boolean result3 = future3.get();
​
        //关闭服务
        ser.shutdown();
​
    }
}
​
import java.util.concurrent.Callable;
//线程类
public class TestCallable implements Callable<Boolean> {
    private String name;
    public TestCallable(String name){
        this.name = name;
    }
​
    @Override
    public Boolean call() throws Exception {
        System.out.println(name+"执行了");
        return true;
    }
}
​
===========================执行输出====================================
皇帝内经执行了
伤寒杂病论执行了
神农本草经执行了

注:可以定义返回值,可以抛出异常

线程状态
线程状态

创建状态:线程对象一旦创建,就进入到了新生状态[Thread t = new Thread()]。

就绪状态:当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行。

运行状态:获得cpu资源,线程进入运行状态,开始执行线程体的代码块。

阻塞状态:当调用sleep, wait或者同步锁定时,线程进入阻塞状态,代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行。

死亡状态:线程中断或者结束,一旦进入死亡状态,就不能再次启动。

线程方法
方法说明
setPriority(int newPriority)(优先级)更改线程的优先级
static void sleep(long millis)(休眠)在指定的毫秒数内让当前正在执行的线程休眠
void join()(插队)等待该线程终止
static void yield()(礼让)暂停当前正在执行的线程对象,并执行其他线程
void interrupt()(中断)中断线程[不建议使用]
boolean isAlive()(活动)测试线程是否处于活动状态
线程停止(stop)
//主方法
//1.建议线程正常停止==>利用次数,不建议死循环
//2.建议使用标志位==>设置一个标志位
//3.不要使用stop或者destroy等过时或者JDK不建议的方法
public class Application {
    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        Thread thread = new Thread(testStop);
​
        for (int i = 0; i <= 20; i++) {
            System.out.println("main线程正在执行"+i);
            if(i == 10){
                //调用stop方法切换标识位,让线程停止
                testStop.stop();
                System.out.println("stop线程停止");
            }
        }
​
    }
}
​
​
//测试线程停止
public class TestStop implements Runnable{
    //设置一个标识位
    private boolean flag = true;
​
    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("stop线程正在执行"+i++);
        }
    }
​
    //设置一个公开的方法停止线程
    public void stop(){
        this.flag = false;
    }
}
​
===========================执行输出====================================
stop线程正在执行0
stop线程正在执行1
stop线程正在执行2
stop线程正在执行3
stop线程正在执行4
stop线程正在执行5
stop线程正在执行6
main线程正在执行0
main线程正在执行1
main线程正在执行2
main线程正在执行3
stop线程正在执行7
stop线程正在执行8
main线程正在执行4
main线程正在执行5
main线程正在执行6
main线程正在执行7
main线程正在执行8
main线程正在执行9
main线程正在执行10
stop线程停止
main线程正在执行11
main线程正在执行12
main线程正在执行13
main线程正在执行14
main线程正在执行15
main线程正在执行16
main线程正在执行17
main线程正在执行18
main线程正在执行19
main线程正在执行20

线程休眠(sleep)
  • sleep(long millis) 指定当前线程阻塞的毫秒数

  • sleep存在异常InterruptedException

  • sleep时间达到后线程进入就绪状态

  • sleep可以模拟网络延时,倒计时等

  • 每个对象都有一个锁,sleep不会释放锁

//主方法
public class Application {
    public static void main(String[] args) {
        TestSleep testSleep = new TestSleep();
        testSleep.timer();
​
    }
}
​
//测试休眠:模拟计时器
public class TestSleep{
    public void timer(){
        int time = 10;
        while (true){
​
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
            System.out.println("倒计时:"+time--);
            if(time == 0){
                break;
            }
        }
    }
}
​
===========================执行输出====================================
倒计时:10
倒计时:9
倒计时:8
倒计时:7
倒计时:6
倒计时:5
倒计时:4
倒计时:3
倒计时:2
倒计时:1

线程礼让(yeild)
  • 礼让线程,让当前正在执行的线程暂停,但不阻塞

  • 将线程从运行状态变为就绪状态

  • 让cpu重新调度,礼让不一定成功,看cpu心情

//主方法
public class Application {
    public static void main(String[] args) {
        TestYeild testYeild = new TestYeild();
        Thread thread1 = new Thread(testYeild,"A");
        Thread thread2 = new Thread(testYeild,"B");
​
        thread1.start();
        thread2.start();
    }
}
​
//测试礼让线程
public class TestYeild implements Runnable{
​
    @Override
    public void run() {
        for (int i = 0; i <= 10; i++) {
            System.out.println(Thread.currentThread().getName()+"线程开始执行"+i);
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+"线程停止执行"+i);
        }
    }
}
​
===========================执行输出====================================
B线程开始执行0
A线程开始执行0
B线程停止执行0
A线程停止执行0
A线程开始执行1
A线程停止执行1
B线程开始执行1
A线程开始执行2
B线程停止执行1
B线程开始执行2
B线程停止执行2
B线程开始执行3
B线程停止执行3
B线程开始执行4
A线程停止执行2
B线程停止执行4
B线程开始执行5
A线程开始执行3
B线程停止执行5
B线程开始执行6
B线程停止执行6
B线程开始执行7
B线程停止执行7
B线程开始执行8
B线程停止执行8
B线程开始执行9
B线程停止执行9
A线程停止执行3
B线程开始执行10
A线程开始执行4
B线程停止执行10
A线程停止执行4
A线程开始执行5
A线程停止执行5
A线程开始执行6
A线程停止执行6
A线程开始执行7
A线程停止执行7
A线程开始执行8
A线程停止执行8
A线程开始执行9
A线程停止执行9
A线程开始执行10
A线程停止执行10

线程合并(join)
  • 合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

  • 可以想象成插队

//主方法
public class Application {
    public static void main(String[] args) {
        //启动join线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();
​
        //主线程
        for (int i = 0; i <= 100; i++) {
            if(i == 20){
                try {
                    //插队
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main线程正在执行"+i);
        }
    }
}
​
//测试合并线程
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println("VIP线程正在执行"+i);
        }
    }
}
​
===========================执行输出====================================
main线程正在执行0
    ......
main线程正在执行18
VIP线程正在执行0
    ......
VIP线程正在执行38
main线程正在执行19
VIP线程正在执行39
    ......
VIP线程正在执行99
VIP线程正在执行100
main线程正在执行20
main线程正在执行21
main线程正在执行22
    ......
main线程正在执行99
main线程正在执行100

线程状态

NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;

//主方法
public class Application {
    public static void main(String[] args) {
        TestStatus testStatus = new TestStatus();
        Thread thread = new Thread(testStatus);

        //观察线程状态
        Thread.State state = thread.getState();
        System.out.println(state);

        //观察后启动线程,再次观察线程状态
        thread.start();
        state = thread.getState();
        System.out.println(state);

        //只要线程不终止,就一直输出状态
        while (state != Thread.State.TERMINATED){
            try {
                thread.sleep(100);
                state = thread.getState();
                System.out.println(state);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//测试线程状态
public class TestStatus implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 2; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("测试线程结束");
    }
}

===========================执行输出====================================
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
    ......
TIMED_WAITING
TIMED_WAITING
测试线程结束
TERMINATED

线程优先级
  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。

  • 优先级用数字表示,范围从1~10

    • Thread.MIN_PRIORITY = 1

    • Thread.MAX_PRIORITY = 10

    • Thread.NORM_PRIORITY = 1

  • 使用以下方式改变或获取优先级

    • getPriority() setPriority(int xxx)

//主方法
//注:优先级高的线程并不一定先执行,只是获得cpu调度的概率高
public class Application {
    public static void main(String[] args) {
        //主线程默认优先级(5)
        System.out.println(Thread.currentThread().getName()+"==>"+Thread.currentThread().getPriority());

        TestPriority testPriority = new TestPriority();

        Thread thread1 = new Thread(testPriority);
        Thread thread2 = new Thread(testPriority);
        Thread thread3 = new Thread(testPriority);
        Thread thread4 = new Thread(testPriority);
        Thread thread5 = new Thread(testPriority);
        Thread thread6 = new Thread(testPriority);

        //设置优先级
        thread1.setPriority(1);
        thread2.setPriority(2);
        thread3.setPriority(3);
        thread4.setPriority(6);
        thread5.setPriority(7);
        thread6.setPriority(10);

        //启动线程
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();
        thread6.start();
    }
}

//测试线程优先级
public class TestPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"==>"+Thread.currentThread().getPriority());
    }
}

===========================执行输出====================================
main==>5
Thread-0==>1
Thread-2==>3
Thread-1==>2
Thread-4==>7
Thread-3==>6
Thread-5==>10

守护线程
  • 线程分为用户线程和守护线程

  • 虚拟机必须确保用户线程执行完毕

  • 虚拟机不用等待守护线程执行完毕

  • 如:后台记录操作日志,监控内存,垃圾回收等待...

//测试守护线程
//主方法
public class Application {
    public static void main(String[] args) {
        TestDaemon testDaemon = new TestDaemon();
        TestSubscriber testSubscriber = new TestSubscriber();

        Thread thread1 = new Thread(testDaemon);
        Thread thread2 = new Thread(testSubscriber);

        thread1.setDaemon(true);//设置为守护线程

        thread1.start();
        thread2.start();

    }
}

//守护线程
public class TestDaemon implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <= 10000; i++) {
            System.out.println("守护线程"+i);
        }
    }
}

//用户线程
public class TestSubscriber implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println("用户线程"+i);
        }
    }
}
===========================执行输出====================================
守护线程0
守护线程1
守护线程3
......
用户线程25
守护线程4
用户线程26
用户线程27
用户线程28
守护线程5
用户线程29
用户线程30
守护线程6
守护线程7
守护线程8
守护线程9
守护线程10
守护线程11
守护线程12
守护线程13
用户线程31
......
用户线程100
守护线程14
守护线程15
守护线程16
守护线程17
守护线程18
守护线程19

线程同步

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中访问时的正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起。

  • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

线程不安全示例

线程不安全示例一(模拟卖票)

//线程不安全示例一:不安全的卖票,有重复票和负数票
//主方法
public class Application {
    public static void main(String[] args) {
        SaleTickets saleTickets = new SaleTickets();

        Thread thread1 = new Thread(saleTickets,"朱元璋");
        Thread thread2 = new Thread(saleTickets,"朱允炆");
        Thread thread3 = new Thread(saleTickets,"朱棣");
        Thread thread4 = new Thread(saleTickets,"朱高炽");
        Thread thread5 = new Thread(saleTickets,"朱瞻基");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();

    }
}

//卖票
public class SaleTickets implements Runnable {
    //票数
    int tickets = 10;
    boolean flag = true;

    @Override
    public void run() {
        while (flag){
            sale();
        }
    }

    //卖票
    private void sale(){
        //判断是否有票
        if(tickets <= 0){
            flag = false;
            return;
        }

        //模拟延时(放大问题的发生性)
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+"拿到了第"+tickets--+"张票");
    }
}

===========================执行输出====================================
朱元璋拿到了第10张票
朱允炆拿到了第9张票
朱高炽拿到了第10张票
朱棣拿到了第7张票
朱瞻基拿到了第8张票
朱元璋拿到了第6张票
朱棣拿到了第6张票
朱瞻基拿到了第6张票
朱高炽拿到了第6张票
朱允炆拿到了第6张票
朱高炽拿到了第5张票
朱棣拿到了第5张票
朱瞻基拿到了第5张票
朱允炆拿到了第5张票
朱元璋拿到了第5张票
朱高炽拿到了第4张票
朱允炆拿到了第3张票
朱瞻基拿到了第4张票
朱元璋拿到了第2张票
朱棣拿到了第4张票
朱瞻基拿到了第1张票
朱元璋拿到了第0张票
朱棣拿到了第-2张票
朱允炆拿到了第-1张票
朱高炽拿到了第-3张票

线程不安全示例二(ArrayList集合)

//主方法
public class Application {
    public static void main(String[] args) {
        TestList list = new TestList();
        for (int i = 0; i < 10000; i++) {
            //多个线程同一瞬间操作同一数据,导致数据被覆盖,元素变少
            Thread thread = new Thread(list);
            thread.start();
        }
        
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        list.print();
    }
}

import java.util.ArrayList;
import java.util.List;
//集合线程
public class TestList implements Runnable{
    List<String> list = new ArrayList<String>();

    @Override
    public void run() {
        list.add(Thread.currentThread().getName());
    }

    public void print(){
        System.out.println(list.size());
    }
}

===========================执行输出====================================
9996

线程安全的集合:CopyOnWriteArrayList

//主方法
/*
CopyOnWriteArrayList安全的原因(源码):
//volatile:保证唯一		transient:保证有序	ReentrantLock:保证线程安全
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
*/
public class Application {
    public static void main(String[] args) {

        TestList testList = new TestList();
        for (int i = 0; i < 10000; i++) {
            Thread thread = new Thread(testList);
            thread.start();
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        testList.print();
    }
}

import java.util.concurrent.CopyOnWriteArrayList;

public class TestList implements Runnable{
    CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();

    @Override
    public void run() {
        list.add(Thread.currentThread().getName());
    }

    public void print(){
        System.out.println(list.size());
    }
}

===========================执行输出====================================
10000

同步方法
  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法(public synchronized void(){})和synchronized块(synchronized(obj){})。

    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class(反射中详解)

    • obj可以是任何对象,但是推荐使用共享资源作为同步监视器

      • 同步监视器的执行过程

          1. 第一个线程访问,锁定同步监视器,执行其中代码

          2. 第二个线程访问,发现同步监视器被锁定,无法访问

          3. 第一个线程访问完毕,解锁同步监视器

          4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

  • synchronized方法控制对 "对象" 的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。(缺点:若一个方法申明为synchronized将会影响效率)

synchronized方法

//线程不安全示例一解决:安全的卖票,无重复票和负数票(方法加锁)
//主方法
public class Application {
    public static void main(String[] args) {
        SaleTickets saleTickets = new SaleTickets();

        Thread thread1 = new Thread(saleTickets,"朱元璋");
        Thread thread2 = new Thread(saleTickets,"朱允炆");
        Thread thread3 = new Thread(saleTickets,"朱棣");
        Thread thread4 = new Thread(saleTickets,"朱高炽");
        Thread thread5 = new Thread(saleTickets,"朱瞻基");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();

    }
}

//卖票
public class SaleTickets implements Runnable {
    //票数
    int tickets = 1000;
    boolean flag = true;

    @Override
    public void run() {
        while (flag){
            sale();
        }
    }

    //卖票
    //synchronized,同步方法,锁的是this(类本身)
    private synchronized void sale(){
        //判断是否有票
        if(tickets <= 0){
            flag = false;
            return;
        }

        //模拟延时(放大问题的发生性)
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+"拿到了第"+tickets--+"张票");
    }
}

===========================执行输出====================================
朱元璋拿到了第10张票
朱元璋拿到了第9张票
朱元璋拿到了第8张票
朱元璋拿到了第7张票
朱元璋拿到了第6张票
朱瞻基拿到了第5张票
朱瞻基拿到了第4张票
朱瞻基拿到了第3张票
朱元璋拿到了第2张票
朱元璋拿到了第1张票

synchronized块

//主方法
public class Application {
    public static void main(String[] args) {

        TestList testList = new TestList();
        for (int i = 0; i < 1000; i++) {
            Thread thread = new Thread(testList);
            thread.start();
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        testList.print();
    }
}

import java.util.ArrayList;
import java.util.List;
//集合线程
public class TestList implements Runnable{
    List<String> list = new ArrayList<String>();

    @Override
    public void run() {
        synchronized (list){
            list.add(Thread.currentThread().getName());
        }
    }

    public void print(){
        System.out.println(list.size());
    }
}

===========================执行输出====================================
1000

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止的情形。某一个同步块同时拥有 "两个以上对象的锁" 时,就可能发生死锁问题。

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。

  2. 请求与保持条件:一个进程因请求资源阻塞时,对已获得的资源保持不放。

  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

避免上述任意一个或多个条件,就能避免死锁的发生。

//死锁:多个线程互相抱着对方需要的锁,然后形成僵持
public class DeadLock {
    public static void main(String[] args) {
        Paint paint1 = new Paint(0,"顾恺之");
        Paint paint2 = new Paint(1,"吴道子");

        paint1.start();
        paint2.start();
    }
}

//笔
class Pen{

}

//墨
class Ink{

}

//模拟画画
class Paint extends Thread {
    //需要的资源只有一份,用static来保证只有一份
    static Pen pen  = new Pen();
    static Ink ink = new Ink();

    int choice;//选择画画顺序:0-先拿笔,再研磨   1-先研磨再拿笔
    String painter;//画家

    Paint(int choice, String painter){
        this.choice = choice;
        this.painter = painter;
    }

    @Override
    public void run() {
        try{
            if(choice == 0){
                synchronized (pen){
                    System.out.println(this.painter+"获得笔的锁");
                    Thread.sleep(1000);
                    synchronized (ink){
                        System.out.println(this.painter+"获得墨的锁");
                    }
                }
            } else{
                synchronized (ink){
                    System.out.println(this.painter+"获得墨的锁");
                    Thread.sleep(1000);
                    synchronized (pen){
                        System.out.println(this.painter+"获得笔的锁");
                    }
                }
            }
        } catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}

===========================执行输出====================================
顾恺之获得笔的锁
吴道子获得墨的锁
    
/*********************************************************************/

//死锁问题解决:
public class DeadLock {
    public static void main(String[] args) {
        Paint paint1 = new Paint(0,"顾恺之");
        Paint paint2 = new Paint(1,"吴道子");

        paint1.start();
        paint2.start();
    }
}

//笔
class Pen{

}

//墨
class Ink{

}

//模拟画画
class Paint extends Thread {
    //需要的资源只有一份,用static来保证只有一份
    static Pen pen  = new Pen();
    static Ink ink = new Ink();

    int choice;//选择画画顺序:0-先拿笔,再研磨   1-先研磨再拿笔
    String painter;//画家

    Paint(int choice, String painter){
        this.choice = choice;
        this.painter = painter;
    }

    @Override
    public void run() {
        try{
            if(choice == 0){
                synchronized (pen){
                    System.out.println(this.painter+"获得笔的锁");
                    Thread.sleep(1000);
                }

                synchronized (ink){
                    System.out.println(this.painter+"获得墨的锁");
                }
            } else{
                synchronized (ink){
                    System.out.println(this.painter+"获得墨的锁");
                    Thread.sleep(1000);
                }

                synchronized (pen){
                    System.out.println(this.painter+"获得笔的锁");
                }
            }
        } catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}

===========================执行输出====================================
顾恺之获得笔的锁
吴道子获得墨的锁
顾恺之获得墨的锁
吴道子获得笔的锁

Lock锁
  • 从JKD5.0开始,Java提供了更强大的线程同步机制:通过显示定义同步锁对象来实现同步。同步锁使用Locd对象充当。

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能由一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

  • ReetrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。

  • Lock锁是显示锁,手动开启和关闭锁;synchronized是隐式锁,出了作用域自动释放。

  • Lock锁只有代码块锁;synchronized有代码块锁和方法锁

  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)。

  • 优先使用顺序:

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

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    public static void main(String[] args) {
        Lock lock = new Lock();
        Thread thread1 = new Thread(lock,"孔丘");
        Thread thread2 = new Thread(lock,"李耳");

        thread1.start();
        thread2.start();
    }
}

class Lock implements Runnable{
    //票数
    int tickets = 10;

    //退出标识位
    boolean flag = true;

    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (flag){
            try{
                //加锁
                lock.lock();
                if(tickets > 0){
                    System.out.println(Thread.currentThread().getName()+"获得了第"+tickets--+"张票");
                } else {
                    break;
                }
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                //解锁
                lock.unlock();
            }
        }
    }
}

===========================执行输出====================================
李耳获得了第10张票
李耳获得了第9张票
李耳获得了第8张票
李耳获得了第7张票
孔丘获得了第6张票
孔丘获得了第5张票
孔丘获得了第4张票
孔丘获得了第3张票
孔丘获得了第2张票
孔丘获得了第1张票

线程协作(生产者消费者问题)
方法名作用
wait()表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout)指定等待的毫秒数
notify()唤醒一个处于等待状态的线程
notifyAll()唤醒同一个对象上所用调用wait()方法的线程,优先级别高的线程优先调度
管程法:利用缓冲区解决
//生产者消费者为题:管程法
public class TestPC {
    public static void main(String[] args) {
        Container container = new Container();

        new Productor(container).start();
        new Consumer(container).start();
    }

}

//生产者
class Productor extends Thread{
    Container container;

    public Productor(Container container){
        this.container = container;
    }

    //生产
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("生产了"+i+"份商品");
            container.push(new Goods(i));
        }
    }
}

//消费者
class  Consumer extends Thread{
    Container container;

    public Consumer(Container container){
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("消费了"+container.consum().id+"份商品");
        }
    }
}

//产品
class Goods{
    //商品编号
    int id;
    public Goods(int id){
        this.id = id;
    }

}

//缓冲区
class Container{
    //容器大小
    Goods[] goods = new Goods[2];

    //容器计数器
    int count = 0;

    //生产者放入产品
    public synchronized void push(Goods good){
        //如果容器满了,就需要等待消费者消费
        if(count == goods.length){
            //通知消费者消费,生产者等待
            try{
                this.wait();
                System.out.println("生产者等待");
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }

        //如果容器没有满,就放入产品
        goods[count] = good;
        count++;

        //通知消费者消费
        this.notifyAll();
    }

    //消费者消费产品
    public synchronized Goods consum(){
        //判断能否消费
        if(count == 0){
            //等待生产者生产,消费者等待
            try {
                this.wait();
                System.out.println("消费者等待");
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }

        //如果可以消费
        count--;
        Goods good = goods[count];

        //吃完了,通知生产者生产
        this.notifyAll();
        return good;
    }

}

===========================执行输出====================================
生产了0份商品
生产了1份商品
生产了2份商品
消费了1份商品
生产者等待
生产了3份商品
生产了4份商品
消费了2份商品
消费了3份商品
生产者等待
生产了5份商品
生产了6份商品
消费了4份商品
消费了5份商品
生产者等待
生产了7份商品
生产了8份商品
消费了6份商品
消费了7份商品
生产者等待
生产了9份商品
消费了8份商品
消费了9份商品
消费了0份商品

信号灯法:标识位解决
public class TestPC {
    public static void main(String[] args) {
        Goods goods = new Goods();
        new Productor(goods).start();
        new Consumer(goods).start();
    }

}

//生产者
class Productor extends Thread{
    Goods goods;
    public Productor(Goods good){
        this.goods = good;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            this.goods.product("当归");
        }
    }
}

//消费者
class  Consumer extends Thread{
    Goods goods;
    public Consumer(Goods good){
        this.goods = good;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            this.goods.consum();
        }
    }
}

//产品
class Goods{
    String good;//产品
    boolean flag = true;

    //生产产品
    public synchronized void product(String good){

        if(!flag){
            try{
                this.wait();
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }

        System.out.println("生产了"+good);

        //通知消费者消费
        this.notifyAll();
        this.good = good;
        this.flag = false;
    }

    //消费产品
    public synchronized void consum(){
        if(flag){
            try{
                this.wait();
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("消费了"+good);

        //通知生产者生产
        this.notifyAll();
        this.flag = true;
    }

}

===========================执行输出====================================
生产了当归
消费了当归
生产了当归
消费了当归
生产了当归
消费了当归
生产了当归
消费了当归
生产了当归
消费了当归
生产了当归
消费了当归
生产了当归
消费了当归
生产了当归
消费了当归
生产了当归
消费了当归
生产了当归
消费了当归

线程池
  • 背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能的影响很大。

  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的交通工具。

  • 好处:

    • 提高了响应速度(减少了创建新线程的时间)

    • 较低了资源消耗(重复利用线程池中的线程,不需要每次都创建)

    • 便于线程管理

      • corePoolSize:核心池的大小

      • maximumPoolSize:最大线程数

      • keepAliveTime:线程没有任务时最多保持多长时间后会终止

  • JDK5.0起提供了线程池相关API:ExecutorService 和 Executors

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable

    • <T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行Callable

    • void shutdown():关闭连接池

    • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestPool {
    public static void main(String[] args) {
        //1.创建服务,创建线程池(参数为线程池大小)
        ExecutorService service = Executors.newFixedThreadPool(10);

        //执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //2.关闭连接
        service.shutdown();


    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

===========================执行输出====================================
pool-1-thread-1
pool-1-thread-3
pool-1-thread-2
pool-1-thread-4
pool-1-thread-5
pool-1-thread-6

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值