Java学习笔记:多线程

Java学习笔记:多线程

1.基本概念

程序Program:为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程Process:程序的一次执行过程,或正在运行的一个程序(动态过程,有生命周期:自身产生、存在、消亡)。

线程Thread:一个程序内部的一个执行路径。

线程生命周期

线程生命周期

并行与并发:
并行:多个CPU同时执行多个任务。如:河师大球场上同时有多场地在进行球赛。
并发:一个CPU(采用时间片)同时执行多个任务。如:某一个场地许多人在打一个球。

线程的优先级:(优先级高优先执行的概率高,并不是优先级高就一定先执行)

MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 --> 默认的优先级
如何设置当前线程的优先级:
getPriority()
setPriority(int p)

2.创建多线程的方式(四种)

方式一:继承Thread类

步骤:

​ 1.创建一个继承于Thread类的子类

​ 2.重写Thread类的run()

​ 3.创建Thread类的子类对象

​ 4.调用Thread类的start() 作用:①启动当前线程②调用当前线程的run()

class MyThread extends Thread {
    //2.重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread t0 = new MyThread();
        //4.调用Thread类的start() 作用:①启动当前线程②调用当前线程的run()
        t0.start();

        MyThread t1 = new MyThread();
        t1.start();

        //如下操作仍在main线程中
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i + "****主线程********");
        }
    }
}

说明:

Thread中的常用方法:
1.start() 启动当前线程;调用当前线程的run()
2.run() 通常需要重写此方法,将创建线程需要执行的操作声明在此方法中
3.currentThread() 静态方法,返回执行当前代码的线程
4.getName() 获取当前线程的名字
5.setName() 设置当前线程的名字
6.yield() 线程让步,释放当前cpu的执行权,(但是下一刻可能又被此线程抢回来)
7.join() 在线程a中调用线程b的join(),此刻线程a进入阻塞状态,需要等待线程b完全执行完后,线程a才结束阻塞状态。
8.stop() 已过时,执行此方法时,强制结束当前线程。
9.sleep(long millis) 让当前线程睡眠,参数是指millis毫秒内,当前线程是阻塞的
10.isAlive() 判断当前线程是否存活(是否执行完run()),返回true和false

class HelloThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + "优先级" + Thread.currentThread().getPriority() + ":" + i);
            }

            if (i % 2 == 20) {

                yield();
            }
        }
    }
}

public class ThreadMethodTest {
    public static void main(String[] args) {
        HelloThread h1 = new HelloThread();
        h1.setName("线程一");
        //设置分线程优先级
//        h1.setPriority(Thread.MAX_PRIORITY);
        h1.start();

        Thread.currentThread().setName("主线程");
        //设置主线程优先级
//        Thread.currentThread().setPriority(2);
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + "优先级" + Thread.currentThread().getPriority() + ":" + i);
            }
            if (i == 20) {
                try {
                    h1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println(h1.isAlive());
    }
}

方式二:实现Runnable接口

步骤: 1.创建一个实现Runnable接口的类

​ 2.实现类去实现接口中的抽象方法run()

​ 3.创建实现类对象

​ 4.将此对象作为参数传递给Thread类构造器,创建Thread类的对象

​ 5.通过Thread类的对象调用start()

//1.创建一个实现Runnable接口的类
class MThread implements Runnable {
    //2.实现类去实现接口中的抽象方法run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest1 {
    public static void main(String[] args) {
        //3.创建实现类对象
        MThread mThread = new MThread();
        //4.将此对象作为参数传递给Thread类构造器,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        //5.通过Thread类的对象调用start()
        t1.start();
    }
}

说明:

比较创建线程的前两种方式:

开发中优先选择实现Runnable接口的方式,原因:

1.实现的方式没有类的单继承的限制。

2.实现的方式更适合来处理多个线程有共享数据的情况。

二者的联系:public class Thread implements Runnable

相同点:两种方式都需要重新run()方法,将需要执行线程的逻辑写在run()方法中。


方式三:实现Callable接口

**步骤:**1.创建一个Callable实现类

​ 2.实现call()方法,将此线程要实现的操作写在此处

​ 3.创建Callable接口实现类的对象

​ 4.将此Callable对象当做参数传递给FutureTask构造器中构造FutureTask对象

​ 5.将FutureTask对象当做参数传递给Thread类构造器

​ 6.调用start()开启线程

​ 7.获取Callbale中call()方法的返回值

//1.创建一个Callable实现类
class NumThread implements Callable {
    //class NumThread implements Callable<Integer>{

    //2.实现call()方法,将此线程要实现的操作写在此处
    @Override
    public Object call() throws Exception {
//        public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                sum += i;
                System.out.println(i);
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable对象当做参数传递给FutureTask构造器中构造FutureTask对象
        FutureTask futureTask = new FutureTask(numThread);
//        FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
        //5.将FutureTask对象当做参数传递给Thread类构造器
        Thread thread = new Thread(futureTask);
        //6.调用start()开启线程
        thread.start();

        try {
            //7.获取Callbale中call()方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()方法的返回值
            Object o = futureTask.get();
//            Integer o = futureTask.get();
            System.out.println("sum为:" + o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

说明:

如何理解实现Callable接口的方式创建多线程比实现Runnable接口的方式强大?

1.call()可以有返回值。

2.call()可以抛出异常,打印一下即可捕获。

3.Callable支持泛型。


方式四:线程池

**步骤:**1.提供指定线程数量的线程池

​ 2.执行指定的线程操作。需要提供Runnable或Callable接口的实现类对象作为参数

​ 3.关闭线程池

class NumberThread implements Runnable {

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


public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //设置线程池属性
        //ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
        //service1.setMaximumPoolSize(15);
        //2.执行指定的线程操作。需要提供Runnable或Callable接口的实现类对象作为参数
        NumberThread a1 = new NumberThread();
        service.execute(a1);//适用于Runnable接口

        //service.submit(Callable callable)//适用于Callbale接口

        //3.关闭线程池
        service.shutdown();
    }
}

说明:

好处:1.提高响应速度(减少创建新线程的时间)

​ 2.降低资源消耗(重复利用线程池中的线程,不需要每次都创建)

​ 3.便于线程管理:

​ corePoolSize:核心池的大小

​ MaximumPoolSize:最大线程数

​ KeepAliveTime:线程没有任务时最多多长时间后终止


3.线程安全问题

**引入:**创建三个窗口买票,总共100张,用实现Runnable接口的方式

class Window1 implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName()+"票号:"+ticket);
                ticket--;
            }else break;
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w1 = new Window1();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

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

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

此例子存在线程的安全问题,
1.问题:卖票过程中出现了重票、错票----> 出现线程的安全问题
2.问题出现的原因:在某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
3.如何解决:当一个线程a在操作共享数据(ticket)的时候,其他线程不能参与进来(即使a阻塞也不行),直到线程a操作完ticket,其他线程才可以开始操作ticket。

4.在Java中,我们通过同步机制,来解决线程的安全问题。

5.同步的方式,解决了线程的安全问题–好处

操作同步代码块时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低–局限性


解决线程安全问题的方式一:同步代码块
synchronized(同步监视器){
*       //需要被同步的代码
*   }

说明: 1.操作共享数据的代码,即为需要被同步的代码

​ 2.共享数据:多个线程共同操作的数据

​ 3.同步监视器:俗称锁。任何一个类的对象都可以充当锁

​ 要求:多个线程必须共用同一把锁。

​ 补充:实现Runnable接口创建多线程的方式中,可以考虑使用this当同步监视器。在使用同步代码块解决继承Thread类的方式解决线程安全问题,慎用this当同步监视器。

使用同步代码块解决继承Thread类的方式解决线程安全问题:

class Window2 extends Thread {
    private static int ticket = 100;
    static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (Window2.class) { //Class clazz = Window.class,Window.class只会加载一次
                //synchronized (obj)是正确的
                //synchronized (this)是错误的
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "票号:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}


public class WindowTest2 {
    public static void main(String[] args) {
        Window2 window1 = new Window2();
        Window2 window2 = new Window2();
        Window2 window3 = new Window2();

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

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

使用同步代码块解决实现Runnable接口的方式解决线程安全问题:

class Window1 implements Runnable {
    private int ticket = 100;
//    Object obj = new Object();


    @Override
    public void run() {
        while (true) {
            synchronized (this) { //此时this:唯一的Window1的对象   //synchronized (obj){
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "票号:" + ticket);
                    ticket--;
                } else break;
            }
        }
    }
}

public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w1 = new Window1();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

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

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

解决线程安全问题的方式二:同步方法

如果操作共享数据的代码完整声明在一个方法中,我们不妨将此方法声明成同步的。

​ 1.同步方法依然涉及同步监视器,只是不需要我们显示声明。

​ 2.非静态的同步方法,同步监视器是 this(当前类创建的一个对象w1)
静态的同步方法,同步监视器是 当前类本身

使用同步方法解决继承Thread类的方式解决线程安全问题:

class Window4 extends Thread{
    private static int ticket = 100;
    static Object obj = new Object();

    @Override
    public void run() {
        while (true){
             show();
        }
    }
    private static synchronized void show(){  //同步监视器为Window4.class
        //private synchronized void show(){  同步监视器window1,window2,window3,此种方法是错误的
        if (ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"票号:"+ticket);
            ticket--;
        }
    }
}


public class WindowTest4 {
    public static void main(String[] args) {
        Window4 window1 = new Window4();
        Window4 window2 = new Window4();
        Window4 window3 = new Window4();

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

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

用同步方法解决实现Runnable接口创建多线程的方式中的安全问题:

class Window3 implements Runnable {
    private int ticket = 100;
//    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //此时this:唯一的Window1的对象   //synchronized (obj){
            show();
        }
    }

    private synchronized void show() { // 此时同步监视器是this
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "票号:" + ticket);
            ticket--;
        }
    }
}

public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w1 = new Window3();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

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

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

解决线程安全问题的方式三---->Lock锁 JDK5.0新增

步骤:1.实例化ReentrantLock 2.调用锁定方法:lock() 3.调用解锁方法:unLock()

建议优先使用顺序:Lock–>同步代码块–>同步方法

==面试题:==synchronized与Lock异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码块后,自动释放同步监视器; Lock需要手动的启动同步(lock()),同时结束同步也需要手动实现(unlock())

用Lock锁方式解决线程安全问题:

class Window implements Runnable {
    private int ticket = 100;

    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                //2.调用锁定方法:lock()
                lock.lock();
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖票  票号:" + ticket);
                    ticket--;
                } else break;

            } finally {
                //3.调用解锁方法:unLock()
                lock.unlock();
            }
        }
    }
}


public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

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

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

线程安全问题小练习:使用同步机制将单例模式中的懒汉式改写为线程安全的

public class Bank {
    private Bank() {
    }

    private static Bank instance = null;

    public static Bank getInstance() {
        //方式一:效率稍差
//        synchronized (Bank.class) {
//            if (instance == null) {
//                instance = new Bank();
//            }
//            return instance;
//        }
        //方式二:效率更高
        if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

4.线程通信

引入:演示线程死锁

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

2.说明:
1)出现死锁后,不会出现异常,不会出现提示,所有线程处于阻塞状态无法继续。
2)使用同步时,尽量避免死锁。

代码:

public class DeadLockTest {
    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(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2) {
                        s1.append("b");
                        s2.append(2);
                        System.out.println(s1);
                        System.out.println(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);
                        System.out.println(s2);
                    }
                }

            }
        }).start();
    }
}
解决方法:线程通信

线程通信的例子:打印100个数,两个线程交替打印

涉及三个方法:

1.wait():一旦执行此方法,线程进入堵塞状态,并释放同步监视器。

2.notify():一旦执行此方法,就会唤醒一个被wait的线程,如果有多个线程被wait则唤醒优先级高的。

3.notifyAll():一旦执行此方法,唤醒所有被wait的线程。

说明:

1.wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中,lock不适用。

2.wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则会出现IllegalMonitorStateException异常。

3.wait()、notify()、notifyAll()三个方法是定义在java.lang.Object类中。

class Number implements Runnable {
    private int i = 1;
    Object object = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                //省略this,this.notify(),this指当前Number类
                notify();
                if (i < 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                    i++;
                } else break;

                try {
                    //省略this,this.wait(),this指当前Number类
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


public class CommunicateTset {
    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();
    }
}

面试题: sleep()与wait()异同?

1)相同点:执行时都会使当前线程进入阻塞状态。

2)不同点:

​ 1.两方法声明位置不同:Thread类中声明sleep(),Object类中声明wait()。

​ 2.调用的要求不同:sleep()可以再任意地方调用,wait()只能以同步代码块或同步方法中的同步监视器调用。

​ 3.关于释放同步监视器:sleep()调用时不会释放同步监视器,wait()会释放同步监视器。


注:这是我跟着B站视频尚硅谷免费公开视频学习java时自己记得笔记,头一次发有许多不足或者不合适的地方,希望多多指正,多多包涵^^

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值