黑马程序员_线程高级_多线程,同步,互斥,线程数据共享

来源:互联网 发布:直播软件露鸟 编辑:程序博客网 时间:2024/05/22 06:14
一,线程回顾
线程就是进程的独立控制单元。控制着进程的执行。一个进程至少有一个线程。进程就是正在执行的程序。
window操作系统的Ctrl + Alt + Del进入任务管理器就可以看到很多进程。

传统线程的创建方式:
        1,直接继承自Thread类,并且重写Thread类的run方法,将要执行的线程代码放在run方法的方法体内部。
        然后创建该类的对象,调用start()方法,启动线程并执行run方法里面的代码块。
Thread t1 = new Thread() {            @Override            public void run() {                while(true) {                    try {                        Thread.sleep(1000);                        System.out.println(Thread.currentThread().getName());                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        };        t1.start();

2,实现接口Runnable,重写Runnable接口中的run方法,将要执行的线程代码块放在run方法内部。new该类的对象,并new一个Thread对象,将Runnable子类的对象作为参数传递给Thread类的构造方法,调用Thread对象的start方法,启动线程并执行Runnable子类的run方法。

Thread t2 = new Thread(new Runnable() {            @Override            public void run() {                while(true) {                    try {                        Thread.sleep(1000);                        System.out.println(Thread.currentThread().getName());                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }                    });        t2.start();

思考:当创建一个继承Thread类的子类,实现父类的run方法,然后new该子类的对象时传入一个Runnable的子类对象作为参数,调用Thread类的子类的start()方法,这时候到底会调用哪个run方法呢? 

Thread t3 = new Thread(new Runnable() {                        @Override            public void run() {                while(true) {                    try {                        Thread.sleep(1000);                        System.out.println("1---:" + Thread.currentThread().getName());                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }) {            @Override            public void run() {                while(true) {                    try {                        Thread.sleep(1000);                        System.out.println("2+++++:" + Thread.currentThread().getName());                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        };        t3.start();

分析:调用start方法时,Thread类的子类会先调用自己的run方法,也就是第二个run方法,执行该方法里面的代码块。
只有当该run方法不存在时,才会去找父类Thread的run方法,而父类的run方法是没有内容的,这时候会调用Runnnable的
子类的run方法,也就是第一个run方法。


二,计时器
计时器类:Timer。这是一个工具类,线程用它安排以后在后台线程中的执行任务。可安排任务执行一次,也可以安排任务定期执行。

计时器Timer的schedule方法能够安排在指定的时间执行指定的任务。
public void schedule(TimerTask task,Date time)
简单计时器的使用:

public class TraditionalTimerTest {    public static void main(String[] args) {                new Timer().schedule(new TimerTask() {                        @Override            public void run() {                System.out.println("Booming");            }        }, 5000);//5秒后执行run方法内部的代码        //显示当前的秒种        while(true) {            System.out.println(new Date().getSeconds());            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

schedule(TimerTask task,Date firstTime,long period)方法,firstTime指定几秒后执行该task,然后每隔period秒都执行一次task。
下面代码可以让定时器5秒后第一次打印Booming,以后每隔3秒都执行一次打印Booming。

new Timer().schedule(new TimerTask() {                        @Override            public void run() {                System.out.println("Booming");            }        }, 5000,3000);还可以在run方法内部定义计时器new Timer().schedule(new TimerTask() {                        @Override            public void run() {                System.out.println("Booming");                new Timer().schedule(new TimerTask() {                                @Override                    public void run() {                        System.out.println("Booming");                    }                }, 3000);            }        }, 3000);//该方式可以实现指定次数的任务,并且可以设置个任务之间的时间间隔。3秒后炸弹爆炸,炸弹爆炸后过3秒//再次引爆另一个炸弹。

需求:定义一个计时器,2秒后引爆炸弹,然后4秒后引爆下一个炸弹,在2秒后引爆炸弹,在4秒后引爆,
以后按照这种方式一直执行下去。

方式一:使用schedule(TimerTask task,Date firstTime,long period)方法,通过一个变量改变period
值,也就是改变后续执行任务的时间间隔。
定义变量count在0和1之间切换,利用该变量的改变,改变period的值。
int count = 0;
count = (count + 1)%2;
schedule(TimerTask task,Date firstTime,2000 + count)
代码:

public class TraditionalTimerTest {    public static long count = 0;//内部类中不能定义静态变量    public static void main(String[] args) {        //定义一个任务TimerTask类的子类执行指定的任务        class MyTask extends TimerTask {            @Override            public void run() {                count = (count+1) % 2;                System.out.println("Booming");                new Timer().schedule(new MyTask(),2000 + 2000 * count);            }           }        //创建定时器,指定制度性该任务的时间和时间间隔        new Timer().schedule(new MyTask(),2000);    }}

方式二:创建定义两个定时器A和B,A爆炸后,安装B,B爆炸后有安装A,一直安装下去。但是这种方法有局限性,不能一直执行下去。

final Timer t1 = new Timer();        final Timer t2 = new Timer();        t1.schedule(new TimerTask() {            @Override            public void run() {                System.out.println("Booming!!!");                t2.schedule(new TimerTask() {                    @Override                    public void run() {                        System.out.println("Booming!!!");                        t1.schedule(new TimerTask() {                            @Override                            public void run() {                                System.out.println("Booming!!!");                                t2.schedule(new TimerTask() {                                    @Override                                    public void run() {                                        System.out.println("Booming!!!");                                        t1.schedule(new TimerTask() {                                            @Override                                            public void run() {                                                System.out.println("Booming!!!");                                            }                                        }, 2000);                                    }                                }, 4000);                            }                        }, 2000);                    }                }, 4000);            }        }, 2000);

只能交替执行五次,次数有限制。

三,多线程的互斥,同步
多线程在运行的时候存在资源的共享是很常见的情况,当两个线程在共享同一资源的时候,很可能就会出现A线程还没有执行
完,B线程就抢占了该资源,B线程去执行,那么就会出现不可预知的错误。

这就要使用加锁,给共享资源进行加锁,也就是同步。同步的方法有两种:
1,创建同步代码块,将要同步的代码放到同步代码块中:
synchronized(对象) {
同步的代码;
}
如果同步代码块不止一处,要使用同一个对象,才能达到同步的目的,对象可以是任意的。

2,在方法上同步:
在方法上加同步有两种情况,普通方法上加同步,使用的对象是this对象,也就是调用该方法的对象。
如果是静态的方法上加synchronized,那么使用的对象就是该方法所在类的字节码.class文件。

比如下面的程序时存在线程安全问题的:

public class SynchronizedTest {    public static void main(String[] args) {        SynchronizedTest st = new SynchronizedTest();        st.init();    }        public void init() {        final Resource r = new Resource();//共享资源对象        //线程一执行共享资源的方法        new Thread(new Runnable() {            @Override            public void run() {                while(true) {                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    r.output("zhangsanxixixi");                 }            }                    }).start();        //线程二也执行共享资源的方法        new Thread(new Runnable() {            @Override            public void run() {                while(true) {                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    r.output("lisihahah");                }            }                    }).start();    }    //共享资源类    class Resource {        public void output(String name) {            int len = name.length();            for(int i=0;i<len;i++) {                System.out.print(name.charAt(i));            }            System.out.println();        }    }}

方式一加锁:

class Resource {    public void output(String name) {        String str = "";//锁使用的对象        synchronized (str) {            int len = name.length();            for(int i=0;i<len;i++) {                System.out.print(name.charAt(i));            }            System.out.println();        }           }}

所使用的对象如果不是同一个对象,那么也会出现互斥的现象。
方式二加锁:

public synchronized void output(String name) {    int len = name.length();    for(int i=0;i<len;i++) {        System.out.print(name.charAt(i));    }    System.out.println();       }

方法上面加锁使用的对象就是调用该方法的对象。

public static synchronized void output(String name) {    int len = name.length();    for(int i=0;i<len;i++) {        System.out.print(name.charAt(i));    }    System.out.println();       }//static方法上加锁,该锁使用的对象是该方法所在类的字节码.class,这里也就是output.class,//如果想对静态方法使用锁,只有在创建同步代码块时锁使用的对象使用静态方法所在类的字节码即可。//原因就是因为静态方法锁使用的对象是该方法所在类的字节码。

四,线程的一个面试题:
子线程循环10次,接着主线程循环100次,接着子线程又循环10,子线程在循环10次,如此反复执行50次。

分析:将要用到的共同数据(包括同步锁)或共同算法的若干个方法归结到同一个类身上,这种方式体现了高类聚和程序的健壮性。

public class TraditionalSynchronizedTest {    public static void main(String[] args) {        final Business business = new Business();        //创建并启动子线程        new Thread(new Runnable() {            @Override            public void run() {                //子线程执行50次                for(int i=1;i<=50;i++) {                    business.sub();                }            }        }).start();                //主线程的执行代码,主线程也执行50次        for(int i=1;i<=50;i++) {            business.main();        }    }    static class Business {        private boolean subStart = true;                //子线程循环10次的代码        public synchronized void sub() {            while(!subStart) {                try {                    this.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            for(int j=1;j<=10;j++) {                System.out.println("sub loop :" + j);            }            subStart = false;            this.notify();        }                //主线程循环100次的方法        public synchronized void main() {            while(subStart) {                try {                    this.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            for(int j=1;j<=100;j++) {                System.out.println("main loop :" + j);            }            subStart = true;            this.notify();        }    }}

五,线程范围内的共享变量
对于相同程序的代码块,当多个模块在同一个线程运行的时候,共享的时要共享同一份数据,而在另一个线程中共享另一份数据。
我们可以定义静态变量,用来记录共享数据。

public class ThreadShareData {    private static int data = 0;    private static Map<Thread,Integer> threadMap = new HashMap<Thread,Integer>();     public static void main(String[] args) {        //创建两个线程        for(int i=0;i<2;i++) {            new Thread(new Runnable() {                @Override                public void run() {                    int data = new Random().nextInt();                    System.out.println(Thread.currentThread().getName() + "has put data: " + data);                    threadMap.put(Thread.currentThread(),data);                    new A().get();                    new B().get();                }            }).start();        }    }    static class A {        public void  get() {            int data = threadMap.get(Thread.currentThread());            System.out.println("A+++++" + Thread.currentThread().getName() + " get data " + data);        }    }        static class B {        public void get() {            int data = threadMap.get(Thread.currentThread());            System.out.println("B-----" + Thread.currentThread().getName() + " get data " + data);        }    }}

通过类ThreadLocal也可以实现线程范围内的共享数据。

public class ThreadLocalTest {    private static ThreadLocal<Integer> x = new ThreadLocal<>();    private static ThreadLocal<MyThreadData> myData = new ThreadLocal<>();    public static void main(String[] args) {        for(int i=0;i<2;i++) {            new Thread(new Runnable() {                int data = new Random().nextInt();//随机生成data值                @Override                public void run() {                    System.out.println(Thread.currentThread().getName() + "设置了数据" + data);                    x.set(data);//设置不同模块中的data值                    MyThreadData.getInstance().setName("name++++++" + data);                    MyThreadData.getInstance().setAge(data);                    new A().get();                    new B().get();                }            }).start();        }    }    static class A {        public void get() {            int data = x.get();            System.out.println("A模块" + Thread.currentThread().getName() + "拿到数据" + data);            MyThreadData my = MyThreadData.getInstance();            System.out.println("A模块" + Thread.currentThread().getName() + "拿到数据名称" +            my.getName() + "年龄" + my.getAge());        }    }        static class B {        public void get() {            int data = x.get();            System.out.println("B模块" + Thread.currentThread().getName() + "拿到数据" + data);            MyThreadData my = MyThreadData.getInstance();            System.out.println("B模块" + Thread.currentThread().getName() + "拿到的数据名称" +             my.getName() + "年龄" + my.getAge());                    }    }}class MyThreadData {        //利用类似单例设计模式方法设计该类的构造方法    private MyThreadData() {}    public static MyThreadData getInstance() {        MyThreadData instances = map.get();        if(instances == null) {            instances = new MyThreadData();            map.set(instances);        }        return instances;    }    private static ThreadLocal<MyThreadData> map = new ThreadLocal<>();        String name;    int age;    public String getName() {        return name;    }        public void setName(String name) {        this.name = name;    }        public int getAge() {        return age;    }        public void setAge(int age) {        this.age = age;    }}

六,多线程访问共享对象和数据
1,如果过每个线程执行的代码相同,可以使用同同一个Runnable对象,这个Runnable对象中有共享的数据。
比如买票系统就是这样。

public class MultyThreadShareData {    public static void main(String[] args) {        ShareData1 data1 = new ShareData1();        new Thread(data1).start();        new Thread(data1).start();    }}class ShareData1 implements Runnable {    int count = 100;    @Override    public void run() {        while(true) {            count --;        }    }}public class MultyThreadShareData {    public static void main(String[] args) {        ShareData1 data1 = new ShareData1();        new Thread(data1).start();        new Thread(data1).start();    }}class ShareData1 implements Runnable {    int count = 100;    @Override    public void run() {        while(true) {            count --;        }    }}

2,如果线程执的代码不同,这时候要创建不同的Runnable对象,有两种方法可以实现这种方式的数据共享。
(1)将共享数据封装到另一个对象中,然后将这个对象传递给两个Runnable对象。每个线程对数据的操作也
分配到了这个对象上,容易实现针对该数据进行的同步于互斥。

public class MultyThreadShareData {    public static void main(String[] args) {        Share data = new Share();        new Thread(new MyRunnable1(data)).start();        new Thread(new MyRunnable2(data)).start();    }}class Share {    private int j = 0;    public synchronized void increase() {        j++;    }    public synchronized void decrease() {        j--;    }}class MyRunnable1 implements Runnable {    private Share data;    public MyRunnable1(Share data) {        this.data = data;    }    @Override    public void run() {        data.decrease();    }}class MyRunnable2 implements Runnable {    private Share data;    public MyRunnable2(Share data) {        this.data = data;    }    @Override    public void run() {        data.increase();    }}

(2)将这些Runnable对象作为某一类的内部类,共享数据作为这个外部类的成员变量,每个线程对数据的操作也
分配到外部类中,以便实现共享数据的同步于互斥。作为内部类的各个Runnable对象可以调用外部类的成员变量。

public class MultyThreadShareData {    static Share shareData = new Share();//或者将它定义成final类型的,放到main方法里面    public static void main(String[] args) {        new Thread(new Runnable() {                        @Override            public void run() {                shareData.decrease();            }        }).start();        new Thread(new Runnable() {            public void run() {                shareData.increase();            }        }).start();    }}class Share {    private int j = 0;    public synchronized void increase() {        j++;    }    public synchronized void decrease() {        j--;    }}





0 0
原创粉丝点击