多线程(上)

来源:互联网 发布:可可英语软件打不开 编辑:程序博客网 时间:2024/06/06 17:20

1:多线程
(1)线程是依赖于进程而存在的。
A:进程 正在运行的应用程序,每一正在运行的程序都会对应一个进程
B:线程 进程的执行路径,执行单元
单线程和多线程的区别:
比如说有如下代码:

public class Test {    public static void main(String[] args) {        代码1;        show1();        代码2;        show2();        代码3...    }    public static void show1(){         代码11;         代码12;    }    public staic void show2(){    代码22;    代码23;    }}

单线程的执行方式为 :
这里写图片描述
多线程的执行方式:
这里写图片描述
我们可以看出在代码执行的时候,由于多线程的存在,效率会很高,
(2)多线程的两种方案:
(1)继承Thread类
(2)实现Runable接口:

public class MyThread extends Thread{    //1.继承Thread类    //2.重写run方法,重写run方法中的代码之后,当我们启动了这个线程之后,我们的这个线程就会执行run方法中的代码    @Override    public void run() {        //需求:开启该线程之后,执行一个for循环        for (int i = 0; i < 10; i++) {            System.out.println(i);        }    }}
public class MyThread implements Runnable{//实现runnable接口    @Override    public void run() {        //启动该线程对象之后,需要执行的代码        for (int i = 0; i < 10; i++) {            System.out.println(i);        }       }}

(3)多线程的问题:
A:启动线程我们可以利用线程对象调用start()方法
B:start()和run()的区别
start()是在启动线程后执行run方法里里面的代码,而直接利用调用run()方法来执行方法,是在主线程里面运行该方法
start():1.开启线程 2.执行run()方法里面的代码
run():执行的是线程里面执行的代码,并不会开启线程
C:为什么要重写run()
因为每个线程需要执行的代码都是都是不一样的,
我们需要将每个线程自己独立执行的代码写到run()方法中执行
D:线程不可以被多次启动,会出现:java.lang.IllegalThreadStateException
因为在执行同一个线程的时候,一个线程刚开始执行,但是同时又重新开始,所以会抛出异常;
注意:如果是俩个不同的线程,可以同时执行

public class MyThread extends Thread{    @Override    public void run() {        for (int i = 0; i < 100; i++) {            System.out.println(i);        }    }}public class Test {    public static void main(String[] args) {        //B:start()和run()的区别        // 创建一个线程独享        MyThread mt = new MyThread();        MyThread mt2 = new MyThread();        //mt.start();//1.首先开启了一个独立的线程 2.让这个独立的线程执行run方法中的代码        System.out.println("--------");        //mt.run();//1.普通的创对象,调方法  2.代码实在主线程执行,不会开辟新线程        //D:线程可以多次启动吗        mt.start();        //mt2.start();//如果是不同的线程对象,是可以同时开启的        //mt.start();//线程是不能多次启动的    }}

(4)线程的调度和控制
线程休眠(Thread.sleep(毫秒值))
线程名称(setName(),getName();)
线程的调度及优先级setPriority(10)(注意默认值是5,区间在1-10之间)

线程优先级:
当我们多个线程开始运行的时候,线程会枪战CPU的执行权,线程优先级就是你抢到CPU执行权的概率。

public class MyThread extends Thread{    @Override    public void run() {        for (int i = 0; i < 100; i++) {            //线程休眠(Thread.sleep(毫秒值))            try {                Thread.sleep(1000);//在此处出现的异常我们只能抓取,不能抛出            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            //获取执行线程的姓名            System.out.println(this.getName()+i);        }    }}public class Test {    public static void main(String[] args) {        //线程名称(setName(),getName();)        //创建3个线程对象        MyThread t1 = new MyThread();        MyThread t2 = new MyThread();        MyThread t3 = new MyThread();        //给三个线程设置姓名        t1.setName("刘备");        t2.setName("张飞");        t3.setName("关羽");        //设置线程的优先级        //线程的调度及优先级setPriority(10)(注意默认值是5,区间在1-10之间)        //t1.setPriority(100);//设置的区间必须在1-10之间        t1.setPriority(10);        //开启线程        t1.start();        t2.start();        t3.start();     }}

运行结果:
这里写图片描述
由结果我们可以看出,将刘备的执行权设置高之后,每次运行时候,刘备的线程会先运行
(5)多线程案例
5.1继承Thread卖票

public class MyThread extends Thread{    //共有100张票,将ticket改为静态之后,被类的所有对象所共享    static int ticket = 100;    @Override    public void run() {        //用一个while true循环模拟三个窗口一直处于打开的状态        while (true) {            //只有当ticket>0的时候,才可以出售票            if (ticket>0) {                System.out.println(getName()+"正在出售第:"+ticket--+"张票");            }        }    }}//5.1继承Thread卖票public class Test {    public static void main(String[] args) {        //创建三个线程模拟三个售票窗口        MyThread mt1 = new MyThread();        MyThread mt2 = new MyThread();        MyThread mt3 = new MyThread();        //给线程设置名称        mt1.setName("窗口一");        mt2.setName("窗口二");        mt3.setName("窗口三");        //启动线程,开启售票        mt1.start();        mt2.start();        mt3.start();    }}

5.2实现Runnable卖票

public class MyThread implements Runnable{    int ticket = 100;    @Override    public void run() {        while (true) {            if (ticket>0) {                System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");            }        }    }}public class Test {    public static void main(String[] args) {        //创建MyThread这个对象        MyThread mt = new MyThread();        //创建3个窗口        Thread t1 = new Thread(mt);        Thread t2 = new Thread(mt);        Thread t3 = new Thread(mt);        //给线程对象设置名称        t1.setName("窗口一");        t2.setName("窗口二");        t3.setName("窗口三");        //启动窗口开始售票        t1.start();        t2.start();        t3.start();    }}

注意:在实现runnable借口的时候,我们可以不用把ticket设置成static,因为在实现runnable的时候,我们本来就创建了一个实现类对象,然后将是实现类对象转换成三个不同的thread对象,所以我们的ticket本开就是共享的

在实际生活中,我们买票的时候跟定会有时间上的延迟,所以我们给线程中加入延迟,按照真实的情景加入了延迟,确发现出现了这样的两个问题:
A:相同的票卖了多次
CPU的一次操作必须是原子性的(操作是CPU执行一次就可以直接完成的)
B:出现了负数的票
随机性和延迟导致的
这里写图片描述
出现上面的问题称为线程安全问题。

(6)出现多线程安全问题的条件:
A:是否是多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据

(7)如何解决多线程安全问题
* 线程安全执行效率就低
A:同步代码块
synchronized(对象) {
需要被同步的代码。
}
需求:1.测试不是同一把锁的时候线程安全吗?
这里写图片描述
答:当我们每个线程进入程序的时候,我们都会出创建一个把锁,这样执行还是会出错的。
2.如果是同一把锁线程安全吗?
如果我们利用同一把锁,则不会出现上面的问题,线程安全
1.synchronized的对象是什么
答:任意对象 ,相当于是一把锁,只要线程进去就把锁锁上
2.需要同步的代码?
答:被线程执行的代码

C:锁对象问题
a:同步代码块(定义一个抽象类,里面专门定义一个锁)
任意对象

public class MyThread implements Runnable{    //定义100张票    int ticket = 100;    Object obj = new Object();    @Override    public void run() {        while (true) {            //同步代码块            //synchronized (new Object()) {//t1,t2,t3三个线程不共享同一把锁每个线程都有自己的议案锁            synchronized (obj) {//这样3个线程才可以共享同一把锁               if (ticket>0) {                    //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果                    try {                        Thread.sleep(100);                        /**                         * 分析:为什么会出现两张100张票                         * t1抢占到cpu的执行权,此时ticket=100,但是此刻休眠了                         * 此时被t2抢占到了cpu的执行权,此时ticket=100,                         * t1,t2分别睡了100毫秒之后,分别醒来了。。                         * t1此时出售第100张票                         * t2此时出售第100张票                         */                        /**                         * 分析:为什么会出现0张票和-1张票                         * 假设此时票池中仅剩1张票了,                         * t1进来休眠了                         * t2进来休眠了                         * t3进来休眠了                         */                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");                    /**                     * t1醒来,出售的是第1张票,此时tickt=0                     * t2醒来,出售第0张票,此时ticket=-1                     * t3醒来,出售第-1张票,此时ticket=-2                     */                    /**                     * ticket--这个动作一共包含几步:                     * 1.打印出ticket此刻本身的值                     * 2.ticket自减1                     * 3.将自减之后的ticket的最新的值赋值给变量ticket                     */                }            }            //当被同步的代码执行完毕之后,t1手里拿着的obj这个锁才会被释放,            //t1,t2,t3重新抢占cpu的执行权,谁抢到了继续拿着obj这个锁,执行同步代码块中的内容        }    }

b:同步方法(仅适用于实现runable接口)
public synchronized void sellTicket(){同步代码}
this

private static synchronized void sellTicket() {     if (ticket>0) {            //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果            try {                Thread.sleep(100);            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");    }}}

将synchronized关键字加到方法上

c:静态同步方法
类的字节码对象
public static synchronized void sellTicket() {
需要同步的代码
}

(8)匿名内部类的方式使用多线程

new Thread() {            public void run() {                ...            }        }.start();    new Thread(new Runnable(){            public void run() {                ...            }        }).start();

利用匿名内部类启动多线程

public void run() {        while (true) {            if (x%2==0) {                synchronized (MyThread.class) {//这样3个线程才可以共享同一把锁                        if (ticket>0) {                            //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果                            try {                                Thread.sleep(100);                            } catch (InterruptedException e) {                                // TODO Auto-generated catch block                                e.printStackTrace();                            }                            System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");                        }                    }

案例:利用匿名内部类,启动多个线程,验证单例设计模式之懒汉式所存在的缺陷,

public class SingleIntanceDemo {    //私有化构造    private SingleIntanceDemo(){}    private static SingleIntanceDemo instance = null;    public  static SingleIntanceDemo getInstance(){        try {            Thread.sleep(100);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        if (instance==null) {            instance = new SingleIntanceDemo();        }        return instance;    }}public class Test {    public static void main(String[] args) {        //启动第一个线程        new Thread(){            @Override            public void run() {                System.out.println(SingleIntanceDemo.getInstance());                //com.edu_12.SingleIntanceDemo@2d7fc1e7            }        }.start();        //启动第二个线程        new Thread(){            public void run() {                System.out.println(SingleIntanceDemo.getInstance());                //com.edu_12.SingleIntanceDemo@2a8b83e3            };        }.start();    }}

这里写图片描述
当我们利用多线程来验证单例模式的时候,发现俩个对象的地址值不同,即不是同一个对象,我们可以利用同步方法来改进
在创建对象的方法上加上 synchronized关键字就可以
(9)JDK5的Lock锁,我们之前造的所有的锁都没有手动释放锁
static Lock lock = new ReentrantLock();
枷锁:lock.lock();
释放锁:lock.unlock();
可以让我们明确的知道在哪里加锁和释放锁。
依然写一个卖票的案例,用lock枷锁释放锁,
为了保证我们创建的锁一定会被释放,用一下代码进行改进
try{….}finally{…..}

public class MyThread implements Runnable{    //定义100张票    int ticket = 100;    Object obj = new Object();    //创建一个锁    Lock lock = new ReentrantLock();    @Override    public void run() {        while (true) {            try{                //加上锁,获取锁                lock.lock();                if (ticket>0) {                        //考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果                        try {                            Thread.sleep(100);                        } catch (InterruptedException e) {                            // TODO Auto-generated catch block                            e.printStackTrace();                        }                        System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");                    }            }finally{                //这里面的代码一定会被执行                //释放锁                lock.unlock();            }            }    }}public class Test {    public static void main(String[] args) {        //创建MyThread对象        MyThread mt = new MyThread();        //创建三个窗口        Thread t1 = new Thread(mt);        Thread t2 = new Thread(mt);        Thread t3 = new Thread(mt);        //给每一个窗口设置姓名        t1.setName("窗口一");        t2.setName("窗口二");        t3.setName("窗口三");        //开启窗口进行售票        t1.start();        t2.start();        t3.start();    }}

(10)死锁问题
同步嵌套,锁里面套了一个锁,出现同步嵌套

package com.edu_14;public class DieThread extends Thread{    boolean flag;    //提供一个有参构造    public DieThread(boolean flag){        this.flag = flag;    }    @Override    public void run() {        if (flag) {            synchronized (MyLock.objA) {                System.out.println("if"+"objA");                synchronized (MyLock.objB) {                    System.out.println("if"+"objB");                }            }        }else {            synchronized (MyLock.objB) {                System.out.println("else"+"objB");                synchronized (MyLock.objA) {                    System.out.println("else"+"objA");                }            }        }    }}package com.edu_14;public abstract class MyLock {    //定义两个锁    public static final Object objA = new Object();    public static final Object objB = new Object();}package com.edu_14;public class Test {    public static void main(String[] args) {        //创建两个线程,分别设置不同的布尔值        DieThread dt = new DieThread(true);        DieThread dt2 = new DieThread(false);        //开启两个线程        dt.start();        dt2.start();    }}

(11)线程等待和唤醒机制(waitThread,NotifyThread,MyLock,Test
锁对象调用wait() 锁对象调用notify()

package com.edu_15;public abstract class MyLock {    public static final Object obj = new Object();}package com.edu_15;public class WaitThread extends Thread{    @Override    public void run() {        synchronized (MyLock.obj) {            //让等待线程处于等待状态            try {                MyLock.obj.wait();//当线程处于等待状态的时候,线程就不会继续往下执行了                                  //线程在处于等待的时候,会释放掉自己手中的锁                                 //sleep()这个方法,在线程休息的时候会释放锁码?                                //答:不会            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }        System.out.println("我被唤醒了");    }}package com.edu_15;public class NotifyThread extends Thread{    @Override    public void run() {        synchronized (MyLock.obj) {            //唤醒等待线程            MyLock.obj.notify();//唤醒正在等待的线程,唤醒的等待线程的锁对象,必须和等待线程的锁对象一致        }    }}package com.edu_15;/** *  (11)线程等待和唤醒机制(案例演示:waitThread,NotifyThread,MyLock,Test)        锁对象调用wait():线程的等待        锁对象调用notify():线程的唤醒 *      注意:        wait和sleep的区别        线程等待,在等待的同时释放锁,而sleep()方法在执行的过程中是不会释放锁的 */public class Test {    public static void main(String[] args) {        //创建等待线程,让等待线程处于一个等待状态        WaitThread wt = new WaitThread();        wt.start();        //睡上5秒钟之后唤醒等待线程        try {            Thread.sleep(5000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        //创建唤醒线程对象        NotifyThread nt = new NotifyThread();        nt.start();    }}

注意:
wait和sleep的区别
线程等待,在等待的同时释放锁,而sleep()方法在执行的过程中是不会释放锁的

原创粉丝点击