编程语言
首页 > 编程语言> > Java复习笔记:多线程(超详细,随时补充更新,欢迎指正)

Java复习笔记:多线程(超详细,随时补充更新,欢迎指正)

作者:互联网

@

目录

一、基本概念的理解

1、程序、进程、线程

  1. 程序(program):一组指令的集合,一段静态的代码。
  2. 进程(progress):程序的一次执行过程,或正在运行的一个程序。
  3. 线程(thread):一个进程内部的一条执行路径。

2、单核CPU与多个CPU

  1. 单核CPU:只有一个CPU芯片。一个极短的时间内只能执行一个线程任务。由于在线程之间切换的时间极短,给人多线程的假象。
  2. 多核CPU:多个CPU芯片。

3、串行、并行、并发

  1. 串行:一个线程执行完再执行下一个线程。
  2. 并行:多个CPU同时执行多个任务。
  3. 并发:一个CPU同时执行多个任务,交替执行,抢占CPU时间。

二、多线程

1、java中的多线程举例

2、多线程优点

3、何时需要多线程

  1. 程序需要同时执行两个或多个任务。
  2. 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
  3. 需要一些后台运行的程序时。

三、多线程的创建和使用★★★★★

1、方式一:继承Thread类(JDK1.5之前的两种之一)

  1. 代码实现
package com.thread;

public class ThreadTest1 {
    public static void main(String[] args) {
        new MyThread1().start();
    }
}


class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(MyThread1.currentThread().getName() + ":" + i);
        }
    }
}

//当某个线程只适用了一次的时候,也可以使用匿名类匿名对象的方式来创建多线程
package com.thread;

public class ThreadTest2 {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println("我是子线程");
            }
        }.start();
    }
}
  1. 步骤总结
1)  定义子类继承Thread类。 
2)  子类中重写Thread类中的run方法。
3)  创建Thread子类对象,即创建了线程对象。 
4)  调用线程对象start方法:启动线程,调用run方法。
  1. 注意:
  1. Thread类中的方法(详细解释看API文档)

  2. 线程的调度:时间片策略、抢占式

    1. Java中,同优先级线程组成先进先出队列(先到先服务),使用时间片策略
    2. 对高优先级,使用优先调度的抢占式策略
  3. 线程的优先级

    1. 优先级等级
    • MAX_PRIORITY:10
    • MIN _PRIORITY:1
    • NORM_PRIORITY:5
    1. 方法
    • getPriority() :返回线程优先值
    • setPriority(int newPriority) :改变线程的优先级

2、方式二:实现Runnable接口(JDK1.5之前的两种之一)

  1. 代码实现

    public class ThreadTest3 {
        public static void main(String[] args) {
            MyThread3 myThread3 = new MyThread3();
    
            Thread t1 = new Thread(myThread3);
            
            t1.start();
        }
    }
    
    class MyThread3 implements Runnable{
    
        @Override
        public void run() {
            System.out.println("我是子线程");
        }
    }
    
    //当某个线程只适用了一次的时候,也可以使用匿名类匿名对象的方式来创建多线程
    new Thread(new Runnable() {
                @Override
                public void run() {
                    
                }
            }).start();
    
  2. 步骤总结

    1. 创建一个实现了Runnable接口的类
    2. 实现类去实现Runnable中的抽象方法:run()
    3. 创建实现类的对象
    4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    5. 通过Thread类的对象调用start()
    
  3. 比较两种创建线程的方式

    1. 开发中:优先选择:实现Runnable接口的方式

      原因:

      1. 实现的方式没有类的单继承性的局限性
      2. 实现的方式更适合来处理多个线程有共享数据的情况。
    2. 联系:

      1. public class Thread implements Runnable
      2. 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

3、方式三:实现Callable接口

4、方式四:使用线程池(开发中主要是用这个)

四、线程的生命周期

1、线程的生命周期★★★★★

五、线程的同步

1、线程安全问题举例和解决

class Window implements Runnable{

    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            if (ticket > 0){
                //在这里让刚进入的线程阻塞100ms,错票的概率就会增加
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("售票,票号为: " + ticket);
                //如果在这里让刚进入的线程阻塞100ms,重票的概率就会增加
                ticket--;
            }else{
                break;
            }
        }
    }
}
public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.setName("窗口1");
        t1.setName("窗口2");
        t1.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

2、同步代码块

  1. 语法

    synchronized(锁){
        需要被同步的代码
    }
    
  2. 说明

    1. 锁:即同步监视器,任何一个对象都可以充当锁。但是要求,多个线程必须共用一把锁(就是说,new对象这一步,必须是只发生一次)
    2. 被同步的代码:这一部分是操作共享数据的代码。但是不能包多--->如果我们把while也包起来,就成了一个窗口卖所有的票。
  3. 对window的改进

    1. 对继承的方式的改进
    /*
    说明:
    	1、继承的方式的特点就是我们需要new好几个对象,所以需要这些对象都共享票数和锁,声明为static即可解决这个问题。
    	2、还可以用Window1.class来充当锁。
    */
    
    class Window1 extends Thread{
    
        private static int ticket = 100;
    
        static Object obj = new  Object();
    
        @Override
        public void run() {
            while (true) {
                synchronized (obj) {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("ticket_number=" + ticket);
                        ticket--;
                    }else{
                        break;
                    }
                }
            }
        }
    }
    
    public class Window_Thread_Security1 {
        public static void main(String[] args) {
    
            Window1 w1 = new Window1();
            Window1 w2 = new Window1();
            Window1 w3 = new Window1();
    
            w1.start();
            w2.start();
            w3.start();
        }
    }
    
    1. 对实现方式的改进
    /*
    说明:
    	1、实现的方式的特点是只new了一个类的对象,因此票数和锁自动就是共享的。
    	2、还可以用this来充当锁。
    */
    class Window2 implements Runnable{
    
        private int ticket = 100;
    
        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("ticket_number=" + ticket);
                        ticket--;
                    }else{
                        break;
                    }
                }
            }
        }
    }
    public class Window_Thread_Security2 {
        public static void main(String[] args) {
            Window2 window2 = new Window2();
    
            Thread t1 = new Thread(window2);
            Thread t2 = new Thread(window2);
            Thread t3 = new Thread(window2);
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
  4. 优缺点

    1. 优:解决了线程安全问题
    2. 缺:操作同步代码时,只有一个线程参与,相当于是单线程,效率低。

3、同步方法

  1. 语法:synchronized放在方法声明中,表示整个方法是同步方法

    public synchronized void show (String name){
    	//方法体
    }
    
  2. 对window问题的改进

    1. 对继承方式的改建
    class Window3 extends Thread{
    
        private static int ticket = 100;
    
        private static synchronized void show(){//因为我们这里的默认省略了锁,
            // 如果不加static,锁就是this,这显然多个对象对对应多个锁
            //加了static以后,锁就成了Window3.class,是惟一的锁
                if (ticket > 0) {
    
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    System.out.println("ticket_number=" + ticket);
                    ticket--;
                }
        }
    
        @Override
        public void run() {
            while (true) {
                show();
            }
        }
    }
    
    public class Window_Thread_Security3 {
        public static void main(String[] args) {
    
            Window1 w1 = new Window1();
            Window1 w2 = new Window1();
            Window1 w3 = new Window1();
    
            w1.start();
            w2.start();
            w3.start();
        }
    }
    
    1. 对实现方式的改进
    class Window4 implements Runnable{
    
        private int ticket = 100;
    
        private synchronized void show(){//这时,锁是this,只创建了一个Window4的对象,是唯一的.
            if (ticket > 0) {
    
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                System.out.println("ticket_number=" + ticket);
                ticket--;
            }
        }
    
        @Override
        public void run() {
            while (true) {
                show();
            }
        }
    }
    public class Window_Thread_Security4 {
        public static void main(String[] args) {
            com.thread.Window2 window2 = new com.thread.Window2();
    
            Thread t1 = new Thread(window2);
            Thread t2 = new Thread(window2);
            Thread t3 = new Thread(window2);
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

4、死锁

  1. 概念:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
  2. 尽量避免死锁
    1. 专门的算法、原则。
    2. 尽量减少同步资源的定义
    3. 尽量避免嵌套同步
  3. 死锁的演示
package com.thread3;

public class DeadLock {
    public static void main(String[] args) {

        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");

                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                synchronized (s2){
                    s1.append("b");
                    s2.append("2");

                    System.out.println("s1 = " + s1);
                    System.out.println("s2 = " + s2);
                }
            }
        }.start();


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

                    s1.append("c");
                    s2.append("3");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");

                        System.out.println("s1 = " + s1);
                        System.out.println("s2 = " + s2);
                    }


                }

        }).start();
    }
}
/*
输出结果有哪些可能
    线程1先执行,线程2后执行:ab  12  adcd  1234
    线程2先执行,线程1后执行:cd  34  cdab  3412
    都阻塞:
 */

5、lock

  1. 介绍

    1. 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。(Lock是一个接口)
    2. ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
  2. 语法

class A{
    private final ReentrantLock lock = new ReentrantLock();
    
    public void method(){
        lock.lock();//请求锁
        try{
            代码逻辑
        }finnaly{
            lock.unlock();//释放锁
        }
    }
}
  1. 用Lock解决window卖票的线程安全问题
class Window5 extends Thread{

    private static int ticket = 100;

    private static final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
			lock.lock();
            try {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("ticket_number=" + ticket);
                    ticket--;
                }else{
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {

        Window5 w1 = new Window5();
        Window5 w2 = new Window5();
        Window5 w3 = new Window5();

        w1.start();
        w2.start();
        w3.start();
    }
}
  1. 面试题
    1. synchronized 与 Lock的异同?
      1. 同:都是用于解决线程安全问题
      2. 异:①、Lock是显示锁,需要我们自己手动开启和关闭;synchronized是隐式锁,出了作用域就会自动释放锁。②、Lock只有代码块锁,synchronized有代码块锁和方法锁。③、Lock锁性能好,拓展性好。
    2. 如何解决线程安全问题,有几种方式?
      1. 两大类。一类是Lock,一类是synchronized
  2. 开发中优先使用:Lock--->同步代码块--->同步方法

六、线程的通信

  1. 线程通信的例子:使用两个线程打印 1-10。线程1, 线程2 交替打印
//1、要先分析是不是多线程为题;
//2、在分析是否有共享数据,有---同步机制来解决

class MyPrinter implements Runnable {

    private int number = 1;

    @Override
    public synchronized void run() {
        while (true) {
            this.notify();
            if (number <= 10) {
//                this.notify();//如果我把notify放在这里,程序就不会自动结束了?  因为,放在这里,
                //最后一个循环的情况下,打印机二带着10进入,可以notify打印机一,再下一轮,打印机一拿着11
                //,无法进入if,无法notify打印机二,打印机二就一直阻塞在那里
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + number);
                number++;

                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                break;
            }
        }
    }
}

public class ThreadCommunicationTest {
    public static void main(String[] args) {
        MyPrinter myPrinter = new MyPrinter();
        Thread t1 = new Thread(myPrinter);
        Thread t2 = new Thread(myPrinter);

        t1.setName("打印机一");
        t2.setName("打印机二");

        t1.start();
        t2.start();
    }
}
  1. 涉及到的三个方法:
    1. wait():当前线程阻塞,并释放锁
    2. notify():当前线程唤醒被wait的其他一个线程,谁的优先级高就唤醒谁
    3. notifyAll():当前线程唤醒all被wait的线程
  2. 对这三个方法的说明
    1. 个方法必须使用在同步代码块或同步方法中。
    2. 三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常。
      1. 我这里用的是this;如果我自己new一个Object类,那么this就要改为Object类的对象。
    3. 三个方法是定义在java.lang.Object类中。
  3. 面试题:sleep() 和 wait()的异同?★★★★★
    1. 同:两者都可以使线程阻塞
    2. 异:
      1. 声明的位置不同:sleep()是声明在Thread类中;wait()是声明在Object类中
      2. 使用的位置不同:sleep()使用在任意位置;wait()使用在同步代码块或同步方法中
      3. 使用完了以后做的事不同:sleep()不会释放锁,wait()会释放锁

七、生产者和消费者问题

/**
 * 线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印
 *
 * 涉及到的三个方法:
 * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
 * notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
 * notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
 *
 * 说明:
 * 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
 * 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
 *    否则,会出现IllegalMonitorStateException异常
 * 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
 *
 * 面试题:sleep() 和 wait()的异同?
 * 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
 * 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
 *          2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
 *          3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
 *
 * @author shkstart
 * @create 2019-02-15 下午 4:21
 */
class Number implements Runnable{
    private int number = 1;
    private Object obj = new Object();
    @Override
    public void run() {

        while(true){

            synchronized (obj) {

                obj.notify();

                if(number <= 100){

                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;

                    try {
                        //使得调用如下wait()方法的线程进入阻塞状态
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }else{
                    break;
                }
            }

        }

    }
}


public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}

标签:Java,Thread,void,指正,new,线程,ticket,多线程,public
来源: https://www.cnblogs.com/liuzhixian666/p/13943259.html