线程应用:(五)传统线程使用实例
一、创建线程的两种方式
(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次。使用synchronized、wait、notify关键字。
思路:
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()+"执行任务"); } }