zoukankan      html  css  js  c++  java
  • JAVA_基础多线程的生命周期、线程安全、线程锁、线程通信与创建线程(二)

    线程的同步

    同步代码块实现:继承Thread线程安全问题

    ① 操作共享数据的代码,即为需要被同步的代码。(不能包含代码多了,也不能包含代码少了)
    ② 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
    ③ 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
    要求:多个线程必须要共用同一把锁。
    补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。

    class RWindow2 extends Thread {
        public static int ticket = 100;
        private static Object obj = new Object();
        @Override
        public void run() {
            while (true) {
                //正确的
    //            synchronized (obj){
                synchronized (RWindow2.class) {//Class clazz = Window2.class,Window2.class只会加载一次
    //            synchronized (this) {//错误的方式:this代表着t1,t2,t3三个对象
                    if (ticket > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + ":卖票,票号:" + ticket);
                        ticket--;
                    } else {
                        break;
                    }
                }
            }
        }
    }
    public class WindowTest2 {
        public static void main(String[] args) {
            RWindow2 rWindow1 = new RWindow2();
            RWindow2 rWindow2 = new RWindow2();
            RWindow2 rWindow3 = new RWindow2();
            rWindow1.setName("窗口1");
            rWindow2.setName("窗口2");
            rWindow3.setName("窗口3");
            rWindow1.start();
            rWindow2.start();
            rWindow3.start();
        }
    }
    

    同步代码块实现:实现Runnable线程安全问题

    class RWindow implements Runnable {
        private int ticket = 100;
    //    Object obj = new Object();
        @Override
        public void run() {
            while(true) {
    //            synchronized(obj) {//方法一
                  synchronized(this) { //方式二:此时的this:唯一的RWindow的对象
                    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) {
            RWindow rWindow = new RWindow();
            Thread thread1 = new Thread(rWindow);
            thread1.setName("窗口一");
            thread1.start();
            Thread thread2 = new Thread(rWindow);
            thread2.setName("窗口二");
            thread2.start();
            Thread thread3 = new Thread(rWindow);
            thread3.setName("窗口三");
            thread3.start();
        }
    }
    

    同步方法实现:接口Runnable线程安全问题

    ① 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
    ② 非静态的同步方法,同步监视器是:this。
    ③ 静态的同步方法,同步监视器是:当前类本身。

    class RWindow2 implements Runnable {
        private int ticket = 100;
        @Override
        public void run() {
            while (true) {
                show();
            }
        }
        public synchronized void show() {//同步监视器:this
    //        synchronized(this) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号:" + ticket);
                    ticket--;
                }
    //        }
        }
    }
    public class WindowTest2 {
        public static void main(String[] args) {
            RWindow2 rWindow = new RWindow2();
            Thread thread1 = new Thread(rWindow);
            thread1.setName("窗口一");
            thread1.start();
            Thread thread2 = new Thread(rWindow);
            thread2.setName("窗口二");
            thread2.start();
            Thread thread3 = new Thread(rWindow);
            thread3.setName("窗口三");
            thread3.start();
        }
    }
    

    同步方法实现:继承Thread线程安全问题

    class RWindow3 extends Thread {
        private static int ticket = 100;
        @Override
        public void run() {
            while (true) {
                show();
            }
        }
          private static synchronized void show() {//同步监听器:Window4.class
    //    private synchronized void show() {//同步监听器:rWindow1,rWindow2,rWindow3 此种解决方式是错误的
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":卖票,票号:" + ticket);
                ticket--;
            }
        }
    
    
    public class WindowTest3 {
        public static void main(String[] args) {
            RWindow3 rWindow1 = new RWindow3();
            RWindow3 rWindow2 = new RWindow3();
            RWindow3 rWindow3 = new RWindow3();
            rWindow1.setName("窗口一");
            rWindow1.start();
            rWindow2.setName("窗口二");
            rWindow2.start();
            rWindow3.setName("窗口三");
            rWindow3.start();
        }
    }
    

    线程的死锁问题

    死锁:

    • 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁。
    • 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续。

    解决方法:

    • 专门的算法、原则。
    • 尽量减少同步资源的定义。
    • 尽量避免嵌套同步。
    /** 演示死锁问题 */
    public class ThreadTest {
        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.currentThread().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.currentThread().sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        synchronized (s1) {
                            s1.append("d");
                            s2.append("4");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }).start();
        }
    }
    

    Lock(锁)解决线程安全问题

    解决线程安全问题方式三:Lock(锁) ---JDK5.0新增

    synchronizedlock的异同

    相同点:二者都可以解决线程安全问题。

    不相同synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器,Lock需要手动的启动同步(lock()),同时结束同步也需要手动实现(unlock())。

    建议(优先使用顺序)Lock 同步代码块(已经进入了方法体,分配了相应资源)同步方法 (在方法体之外)。

    class Window implements Runnable {
        private int ticket = 100;
        //1.实例化ReentrantLock
        private ReentrantLock lock = new ReentrantLock(true);
        @Override
        public void run() {
            while (true) {
                try {
                    //2.调用clock()
                    lock.lock();
                    //2.调用Lock()
                    if (ticket > 0) {
                        try {
                            Thread.currentThread().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 window = new Window();
            Thread t1 = new Thread(window);
            Thread t2 = new Thread(window);
            Thread t3 = new Thread(window);
            t1.setName("窗口一");
            t2.setName("窗口二");
            t3.setName("窗口三");
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

    线程通信

    涉及到的三个方法:

    • 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) 一旦执行方法,都可以使得当前的线程进入阻塞状态。

    不同点:
    1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
    2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中。
    3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

    class Number {
        private int number = 1;
        private Object obj = new Object();
        public Number(int number) {
            this.number = number;
        }
        public int getNumber() {
            return number;
        }
        // 累加数字
        public void printer(int number){
            synchronized (obj) {
                obj.notify();
                if (number > 0) {
                    System.out.println(Thread.currentThread().getName() + "添加成功。当前数字:" + this.number);
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    this.number += number;
                    try {
                        //使得调用如下wait() 方法的线程进入阻塞状态
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    class Preson implements Runnable {
        private Number number;
    
        public Preson(Number number) {
            this.number = number;
        }
        @Override
        public void run() {
            while (true) {
                if (this.number.getNumber() <= 100) {
                    number.printer(1);
                }else {
                    break;
                }
            }
        }
    }
    public class CommunicationTest {
        public static void main(String[] args) {
            Number number = new Number(1);
            Preson preson = new Preson(number);
            Thread thread = new Thread(preson);
            thread.setName("甲");
            thread.start();
            Thread thread2 = new Thread(preson);
            thread2.setName("乙");
            thread2.start();
        }
    }
    

    JDK5.0新增线程创建方式

    新增方式一:实现Callable接口

    1)与使用Runnable相比, Callable功能更强大些。
    2)相比run()方法,可以有返回值 。
    3)方法可以抛出异常。
    4)支持泛型的返回值 。
    5)需要借助FutureTask类,比如获取返回结果。

    class NumberThread implements Callable{
        /** 2.实现Call方法,将此线程需要执行的操作声明在此方法Call()当中 */
        @Override
        public Object call() throws Exception {
            int sum = 0;
            for (int i = 1; i < 100; i++) {
                if (i % 2 == 0) {
                    System.out.println(i);
                    sum += i;
                }
            }
            return sum;
        }
    }
    public class ThreadNew {
        public static void main(String[] args) {
            /** 3.创建Callable接口实现类的对象 */
            NumberThread numberThread = new NumberThread();
            /** 4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTaskd的对象 */
            FutureTask futureTask = new FutureTask(numberThread);
            /** 5.将此FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法 */
            new Thread(futureTask).start();
            try {
                /** 6.获取Callable中call() 方法的返回值 */
                /** get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值 */
                Object sum = futureTask.get();
                System.out.println("总和为:" + sum);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
    

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

    call()可以有返回值。
    call()可以抛出异常,被外面的操作捕获,获取异常的信息。
    Callable是支持泛型的

    新增方式二:使用线程池

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

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

    好处
    1)提高响应速度(减少了创建新线程的时间)。
    2)降低资源消耗(重复利用线程池中线程,不需要每次都创建)。
    3)便于线程管理 :
    corePoolSize:核心池的大小 。
    maximumPoolSize:最大线程数 。
    keepAliveTime:线程没有任务时最多保持多长时间后会终止。

    class NumberThread1 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (i % 2 != 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }
    }
    class NumberThread2 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);
            /** 设置线程池的属性 */
            /** 由于service是ExecutorService接口,需要使用service的实现类对他进行属性设置 */
            System.out.println(service.getClass());//查看service的实现类
            /** 强转方式一: */
            ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
    //        service1.setCorePoolSize(15);
            /** 强转方式二: */
    //        ((ThreadPoolExecutor) service).setKeepAliveTime(1000);
            /** 2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象 */
            service.execute(new NumberThread1());//适合使用于Runnable
            service.execute(new NumberThread2());//适合使用于Runnable
            service.shutdown();
        }
    }
    
  • 相关阅读:
    浅析堆与垃圾回收
    再探JVM内存模型
    索引使用的基本原则
    常见的索引模型浅析
    初识InnoDB体系架构和逻辑存储结构
    一条update SQL语句是如何执行的
    MySQL一条查询语句是如何执行的
    堆与优先队列
    ibatis BindingException Parameter 'status' not found. Available parameters are [arg1, arg0, param1, param2] 解决方法
    Mysql通过MHA实现高可用
  • 原文地址:https://www.cnblogs.com/BeautifulGirl230/p/14228226.html
Copyright © 2011-2022 走看看