线程应用:(五)传统线程使用实例

一、创建线程的两种方式

(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()+"执行任务");
    }  
}

 

posted @ 2018-08-13 20:47  湮天霸神666  阅读(197)  评论(0编辑  收藏  举报