美文网首页
多线程(一)

多线程(一)

作者: 寂静的春天1988 | 来源:发表于2019-01-15 23:25 被阅读0次

    进程:正在运行的程序,是系统进行资源分配和调度的独立单位。每一个进程都有它的内存空间和系统资源。
    线程:在一个进程内可以执行多个任务。每一个任务就可以看成一个线程。线程是进程的执行单元。是程序使用CPU的最小单位。

    一)使用thread类
    1、继承thread类
    2、重写run方法
    3、启动多线程:new对象,调用它的start()方法(不能调用run方法,否则只是当作普通方法调用)
    4、start方法只能被调用一次

    public class MyThread extends Thread{
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) throws Exception {
    
        MyThread thread1=new MyThread();
        thread1.start();
    }
    

    二)使用runnable
    1、实现Runnable接口
    2、重写run方法
    3、启动多线程:
    3.1创建实现runnable接口的类
    3.2创建thread类。把实现runnable接口的类当作形参放入tread类
    3.3然后用thread类.start()方法!

    public class MyRunable implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(i+"---"+Thread.currentThread().getName());
            }
        }
        public static void main(String[] args) {
            MyRunable runbable1=new MyRunable();
            Thread thread1=new Thread(runbable1);
            Thread thread2=new Thread(runbable1);
            thread1.start();
            thread2.start();
        }
    
    }
    

    三)使用Callable<V>接口
    1、实现Callable接口
    Callable的特点:允许有返回值,依赖于依赖与线程池实现(在多线程二的文章中演示)

    推荐使用runnable的方式:
    1、避免单继承的局限性
    2、便于多个线程共享资源!

    线程调度模型有两种:
    1)分时调度模型,所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
    2)抢占式调度模型,优先让优先级高的线程使用CPU,如果线程的优先级相同,那么随机选择一个。优先级高的线程,使用CPU的时间片会多一些。
    java中采用的就是抢占式调度模型。

    常用方法:
    getName():得到线程名称。
    setName():设置线程名称。
    currentThread():返回当前正在执行的线程引用。
    getPriority():获取线程优先级。(默认优先级是5)
    setPriority():设置线程优先级。(必须在1-10范围内,包含)
    Thread.sleep():线程睡眠
    join():等待这个线程死亡。 (只有它执行完了,其他线程才能走。)
    yield():线程礼让。(让出cpu的使用权,重新抢占cpu,所有它本身还是可能抢占到cpu的)
    setDaemon():后台线程。 将此线程标记为daemon线程或用户线程。 当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。线程启动前必须调用此方法。

    stop():线程中止(已过时)
    interrupt():线程中止。

    stop和interrupt的区别
    stop


    image.png

    interrupt


    image.png

    interrupt:把线程状态中止,并抛出一个InterruptedException
    stop()方法在现在JDK中不推荐使用,原因是stop()方法过于暴力,强行把执行到一半的线程终止,可能会引起一些数据不一致的问题。因此在使用stop()方法时需要自行决定线程何时退出!
    总结:stop()方法执行后,该线程就停止了,不再继续执行了,但是interrupt()方法执行后,它会终止线程的状态,还会继续执行run方法里面的代码
    通常不要用interrupt或者stop方法来中断线程,一般用条件判断结束线程方法。如下图:


    image.png

    线程的生命周期:
    新建:创建线程对象
    就绪:该线程有执行资格,但是没有执行权
    运行:有执行资格,有执行权。
    阻塞:由于一些操作让线程处于了该状态,没有执行资格,没有执行权。而另一些操作可以把它激活,激活后处于就绪状态。
    死亡:线程对象变成垃圾,等待被回收。

    image.png

    经典线程安全问题
    需求1:三个窗口同时卖100张电影票。

    public class MyRunable implements Runnable {
        int p = 100;
    
        @Override
        public void run() {
            while (true) {
                if (p > 0) {
                    try {
                        // 模拟延时
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    p--;
                    System.out.println("卖了" + (100 - p) + "张票,还剩" + p + "张" + "----" + Thread.currentThread().getName());
                } else {
                    break;
                }
    
            }
        }
    
        public static void main(String[] args) {
            MyRunable runbable1 = new MyRunable();
            Thread thread1 = new Thread(runbable1);
            Thread thread2 = new Thread(runbable1);
            Thread thread3 = new Thread(runbable1);
            thread1.start();
            thread2.start();
            thread3.start();
        }
    
    }
    

    运行上面的代码会发现,会发现重复卖同一张票和超卖的问题。

    导致线程安全问题的原因
    A:是否是多线程环境
    B:是否有共享数据
    C:是否有多条语句操作共享数据

    一般而言A,B我们都不能去改变。可以改C。将多条语句操作包装成一个整体,只能同时有一个线程执行,在它运行完之前(释放锁之前)不能被其他线程抢占。(原子性)

    1、使用同步代码块

        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    if (p > 0) {
                        try {
                            // 模拟延时
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        p--;
                        System.out.println("卖了" + (100 - p) + "张票,还剩" + p + "张" + "----" + Thread.currentThread().getName());
                    } else {
                        break;
                    }
                }
    
            }
        }
    

    2、使用同步方法

    public class MyRunable implements Runnable {
        int p = 100;
        int x = 0;
        private Object obj=new Object();
        
        
        @Override
        public void run() {
            while (true) {
                if(x%2==0) {
                    synchronized (obj) {
                        if (p > 0) {
                            try {
                                // 模拟延时
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            p--;
                            System.out.println("卖了" + (100 - p) + "张票,还剩" + p + "张" + "----" + Thread.currentThread().getName());
                        }
                    }
                    
                }else {
                    sell();
                }
                x++;
            }
        }
        
        private synchronized void sell() {
            if (p > 0) {
                try {
                    // 模拟延时
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p--;
                System.out.println("卖了" + (100 - p) + "张票,还剩" + p + "张" + "----" + Thread.currentThread().getName());
            }
        }
    

    这里我们将代码改造了一下,x%2==0的时候走同步代码块,否则走同步方法。同时将同步代码块的锁对象换成了成员变量obj,
    会发现上面的代码出现了线程安全问题。因为这里同步代码块的锁对象和同步方法的锁对象不一致了。这里同步代码块的锁对象应该换成this,就不会出现线程安全问题了。
    这也表明了同步方法的锁对象时this。
    同步静态方法的锁对象呢?是类的class文件(MyRunable.class)

    总结:
    同步代码块的锁对象:形参。
    同步方法的锁对象:this
    静态同步方法的锁对象:类的class文件。
    如果锁对象是this可以使用同步方法,一般而言使用同步代码块。

    同步可以解决安全问题的原因在那个形参上面。那个形参就相当于一把锁,多个线程要使用同一把锁。(这里使用this因为只new了一个MyRunable对象。)

    注意事项:同步代码块的锁对象可以是任意对象,但是多个线程必须是同一个锁对象。
    好处:同步解决了多线程的安全问题。
    坏处:降低了程序的效率。

    提示:Collections中有方法可以将线程不安全的集合类,转换成线程安全类的方法。synchronizedList()、synchronizedMap()...

    相关文章

      网友评论

          本文标题:多线程(一)

          本文链接:https://www.haomeiwen.com/subject/fdnudqtx.html