zoukankan      html  css  js  c++  java
  • 线程应用:(五)传统线程使用实例

    一、创建线程的两种方式

    (1)子类继承Thread,并重写run方法

    public static void main(String[] args) {
        SubThread s1 = new SubThread();
        SubThread s2 = new SubThread();
        s1.start();
        s2.start();
    }
    
    class SubThread extends Thread{
        @Override
        public void run() {
            while(true){
                System.out.println(Thread.currentThread());    //每隔0.5s打印当前线程对象的名称
            }
        }
    }

    (2)实现Runnable接口,传到thread

    public static void main(String[] args) {
        MyRunnable m = new MyRunnable();
        Thread t1 = new Thread(m);  
        Thread t2 = new Thread(m);
        t1.start();
        t2.start();
    }
    
    class MyRunnable implements Runnable{
        @Override
        public void run() {
            while(true){
                System.out.println(Thread.currentThread());    //打印当前线程对象的名称      
            }    
        }
    }

      一般用第二种方式,实现接口不影响继承其他父类。

      如果Thread类既重写了run方法,又传了一个Runnable接口,会调用Thread子类重写的run方法,因为找Runnable对象是在Thread类的run方法执行的,如果被子类覆盖就不会执行。

      采用多线程不一定会让程序运行效率更高,取决于CPU,线程间的切换也会增加消耗。

    二、线程互斥实现案例

      使用synchronized实现互斥。

    //互斥代码案例
    public class ThreadTest {
        public static void main(String[] args) {
            new ThreadTest().init();
        }
    
        //起了两个线程,一个一直输出"AAAAAA",一个一直输出"BBBBBB",但调用方法的对象是同一个
        public void init(){
            final Outputer outputer = new Outputer();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        outputer.output("AAAAAA");
                    }
                }
            }).start();
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        outputer.output2("BBBBBB");
                    }
                }
            }).start();
        }
    }
    
    class Outputer {
        public void output(String name){
            //synchronized (Outputer.class) {
            synchronized (this) {
                int len = name.length();
                for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
                }
                System.out.println("");  //换行
            }
        }
        public synchronized void output2(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 output3(String name){
            int len = name.length();
            for(int i=0;i<len;i++){
                System.out.print(name.charAt(i));
            }
            System.out.println("");
        }
    }
    
    线程不安全下的输出结果:
    BBBBBB
    BBAAAAAA
    AAAAAA

    案例场景:有两个线程,一个一直输出"AAAAAA",一个一直输出"BBBBBB",输出完后会换行,如果没有做任何互斥操作,是线程不安全的,就会出现如图所示在还未输出完B时就会输出A的情况。

    关键点:

    1)如果多个线程要实现互斥,用的锁对象必须要是同一个。

    2)调用output()、output2()方法的是同一个对象,且output()和output2()都是用当前对象作为锁对象,所以即使线程1调用output(),线程2调用output2()也能保证线程安全。

    3)output3()是静态方法,用是类作为锁对象,如果要和output()方法互斥,output()的锁对象要由this改成Outputer.class。

    三、线程同步实现案例

    场景:子线程先单独循环10次,接着主线程再单独循环100次,如此反复50次。使用synchronizedwaitnotify关键字。

    思路:

    1)先保证每个线程一轮的原子性(把要实现互斥的相关联的方法,或是共享同样变量的方法放在同一个类里,线程的同步问题在这个类里考虑)

    2)再让主线程和子线程间交替进行,通过wait,notify等待唤醒,synchronized用的锁对象和wait,notify调用的对象一样

    public class ThreadTest {
        public static void main(String[] args) throws InterruptedException {
            final Business business = new Business();
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int i=1;i<=50;i++){        //循环50轮
                        try {
                            business.forSub(i);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
            
            for(int i=1;i<=50;i++){        //循环50轮
                business.forMain(i);
            }
        }
    }
    
    //加synchronized保证每轮原子性
    //要用到共同数据(包括同步锁)的若干方法应该归于一个类上
    class Business {
        private boolean subGo = true;    //用来让主子线程交替进行的标识
        
        //子线程每轮循环10次
        public synchronized void forSub(int i) throws InterruptedException{
            while(!subGo){        //用while防止伪唤醒
                this.wait();    //不该子线程执行的时候暂停
            }
            
            for(int j=1;j<=10;j++){
                System.out.println("subThread of, 第"+i+"轮, 序列为:"+j);
            }
            subGo = false;    //子线程执行完释放标识
            this.notify();    //唤醒另一个线程
        }
        
        //主线程每轮循环100次
        public synchronized void forMain(int i) throws InterruptedException{
            while(subGo){
                this.wait();    //该子线程执行的时候主线程暂停
            }
            for(int j=1;j<=100;j++){
                System.out.println("mainThread of, 第"+i+"轮, 序列为:"+j);
            }
            subGo = true;    //表示主线程执行完该子线程执行了
            this.notify();
        }
    }

    四、ThreadLocal(一个线程内各模块间共享同一数据,各线程间的数据又是独立的)

      线程范围内的共享变量,每个线程有自己独立的数据。例如每个线程要有自己独立的连接。ThreadLocal本质上是一个map。

    public class ThreadTest3 {
        private static ThreadLocal<Integer> x = new ThreadLocal<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);
                        x.set(data);    //往当前线程ThreadLocal存数据
                        new A().get();
                        new B().get();
                    }
                }).start();
            }
        }
        
        //A模块调用该线程变量
        static class A {
            public void get(){
                int data = x.get();
                System.out.println("A from "+Thread.currentThread().getName()+"has put data:" + data);
            }
        }
        //B模块
        static class B {
            public void get(){
                int data = x.get();
                System.out.println("B from "+Thread.currentThread().getName()+"has put data:" + data);
            }
        }
    }
    
    运行结果:
    Thread-0has put data:-247142886
    Thread-1has put data:-2028241651
    A from Thread-1has put data:-2028241651
    A from Thread-0has put data:-247142886
    B from Thread-1has put data:-2028241651
    B from Thread-0has put data:-247142886

      只要定义一个ThreadLocal变量,往这个变量里放的数据就是和线程相关的。一个ThreadLocal只能放一个变量,如果要存多个变量可以利用实体类。结合单例模式,把ThreadLocal定义在实体类。

    public class ThreadTest4 {
        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);
                        Entity.getInstance().setData(data);        //拿到本线程在这个类里对应的实例,再赋值
                        new A().get();
                        new B().get();
                    }
                }).start();
            }
        }
    }
    
    class A {
        public void get(){
            Entity mydata = Entity.getInstance();
            System.out.println("A from "+Thread.currentThread().getName()+"has put data:" + mydata.getData());
        }
    }
    
    class B {
        public void get(){
            Entity mydata = Entity.getInstance();
            System.out.println("B from "+Thread.currentThread().getName()+"has put data:" + mydata.getData());
        }
    }
    
    //多变量实体类
    class Entity{
        private Entity(){}
        private static ThreadLocal<Entity> map = new ThreadLocal<Entity>();    //在这个类中定义一个ThreadLocal,存每个线程对应的实例
        public static /*synchronized*/ Entity getInstance(){    //一般单例加synchronized会更严谨一点,这里取的就是本线程的实体类,所以可不加
            Entity instance = map.get();
            if(instance == null){        //如果这个线程还没有对应的实例,才往ThreadLocal存
                instance = new Entity();
                map.set(instance);        
            }
            return instance;
        }
        int data;
        public int getData() {
            return data;
        }
        public void setData(int data) {
            this.data = data;
        }
    }
    
    运行结果:
    Thread-0has put data:2114567887
    Thread-1has put data:-746942766
    A from Thread-1has put data:-746942766
    A from Thread-0has put data:2114567887
    B from Thread-0has put data:2114567887
    B from Thread-1has put data:-746942766

      每个线程结束后应该要清空对应的记录,垃圾回收会自动回收结束线程对应的记录,也可以调ThreadLocal的remove();方法移除。

    (*)传统定时器技术,Timer类、开源工具Quartz

    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    
    public class Test {  
        public static void main(String[] args) {
           Timer timer = new Timer();  
           timer.schedule(new TimerTaskTest(), 1000);        //1s后运行
           timer.schedule(new TimerTaskTest(), 1000, 2000); //1s后运行,然后再每隔2s运行一次
           timer.schedule(new TimerTaskTest(), new Date()); //根据具体日期定时
        }
    }
    
    //要执行的任务
    class TimerTaskTest extends TimerTask{
        @Override
        public void run() {
            //定时要执行的代码逻辑
            System.out.println(new Date()+"执行任务");
        }  
    }
  • 相关阅读:
    好用的PasswordTextBox.
    可以修改Autocomplete高度和宽度的TextBox.(ComboBox也试用)
    Show WER and DMP file information
    在webBrowser中触发html页面中的javaScript.
    Trigger in sql server
    黑客来了。。。键盘钩子,听起来很高端。
    Send email
    (VB.net)自定义TableLayoutPanel使它能够在运行时用鼠标改变行高和列宽。
    (C#) Format the cell of DataGridView based on the TextBox.Text
    可以用来测显示屏的inch数。
  • 原文地址:https://www.cnblogs.com/zjxiang/p/9392500.html
Copyright © 2011-2022 走看看