1.多线程两个小问题:
package com.itheima.thread; public class ThreadDemo1 { /** * @param args */ public static void main(String[] args) { // TODO 自动生成的方法存根 new Thread(new Runnable(){ @Override public void run() { // TODO 自动生成的方法存根 System.out.println("runnable"); } }){ @Override public void run(){ System.out.println("thread"); } }.start(); } } /* 问题一: 如果在Thread子类覆盖的run方法中编写了运行代码, 也为Thread子类对象传递了一个Runnable子类对象, 那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码? 子类run方法的代码这是因为在Thread类的run方法: public void run() { if (target != null) { target.run(); } } 一旦复写,不在去找Runnable接口子类对象的run方法 问题二: 多线程机制会提高运行效率吗?为什么会有多线程下载吗? 不会(类比一次拷贝一个文件,一次拷贝多个文件,第二种方式CPU在多个线程之间切换有额外开销,效率低于第一种) 多线程下载,只是为了抢占更多的服务器资源 */
2.定时器与线程(感受思想)
package com.itheima.thread; import java.util.Calendar; import java.util.Timer; import java.util.TimerTask; public class TraditionalTimerTest2 { /** * @param args */ public static void main(String[] args) { // TODO 自动生成的方法存根 new Timer().schedule(new TimerTask(){ @Override public void run() {//run方法中存放此计时器任务要执行的操作。 // TODO 自动生成的方法存根 System.out.println("BOMB!!!!!!!!!!");//10秒后炸弹爆炸,然后每隔3秒一爆炸 } }, 10000,3000);//task - 所要安排的任务。 //delay(延期)- 执行任务前的延迟时间,单位是毫秒。 //period(周期)-执行各后续任务之间的时间间隔,单位是毫秒. while(true){ System.out.println(Calendar.getInstance().get(Calendar.SECOND));//1秒一打印 try{ Thread.sleep(1000);//主线程睡1秒 } catch(Exception e){ } } } }针对以上例子,更复杂一点,如果实现2秒BOMB,4秒BOMB,2秒BOMB….
方法一:两个TimerTask子类:你执行我的任务,我执行你的任务
package com.itheima.thread; import java.util.Calendar; import java.util.Timer; import java.util.TimerTask; public class TraditionalTimerTest3 { /** * @param args */ static class MyTask1 extends TimerTask{ @Override public void run() { // TODO 自动生成的方法存根 System.out.println("BomB!!!!"); new Timer().schedule(new MyTask2(),4000); } } static class MyTask2 extends TimerTask{ @Override public void run() { // TODO 自动生成的方法存根 System.out.println("BoomB!!!!"); new Timer().schedule(new MyTask1(),2000); } } //完成交替炸:2,4,2,4,2,4 public static void main(String[] args) { // TODO 自动生成的方法存根 new Timer().schedule(new MyTask1(),2000); } }方法二:通过一个变量来控制该变量取值(0,1,0,1…)
package com.itheima.thread; import java.util.Calendar; import java.util.Timer; import java.util.TimerTask; public class TraditionalTimerTest3 { /** * @param args */ static int count=0; public static void main(String[] args) { // TODO 自动生成的方法存根 class MyTask extends TimerTask{ @Override public void run() { System.out.println("BOMB!!!!"); count=(count+1)%2; new Timer().schedule(new MyTask(),2000+2000*count);//即使是不同Timer对象,TimerTask对象不能使用同一个 } //否则会报IllegalArgumentException,换成this可验证 } new Timer().schedule(new MyTask(),2000); while(true){ System.out.println(Calendar.getInstance().get(Calendar.SECOND)); try{ Thread.sleep(1000);//主线程睡1秒 } catch(Exception e){ } } } }
3.传统多线程安全问题与通信几种设计方式:(体会思想)
一.如果每个线程执行的代码相同,可以使用同一个Runnable接口子类对象,这个Runnable接口子类对象中有那个共享数据,例如,买票系统就可以这么做。
二.如果每个线程执行的代码不同,这时候需要用不同的Runnable接口子类对象,有如下两种方式来实现这些Runnable接口子类对象之间的数据共享:
1.将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable接口子类对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
2.将这些Runnable接口子类对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable接口子类对象调用外部类的这些方法。
3.上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable接口子类对象作为外部类中的成员内部类或局部内部类。
总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。
//使用方式一 class Ticket implements Runnable{ private int ticket=100; @Override public synchronized void run() { // TODO 自动生成的方法存根 while(true) if(ticket>0) System.out.println(Thread.currentThread().getName()+"..."+ticket—); } } class TicketTest{ public static void main(String[] args){ Ticket t=new Ticket(); new Thread(t).start(); new Thread(t).start(); } }//方式二:1 class Resource{ private int j=0; public synchronized void add(){ j=j+1; System.out.println(Thread.currentThread().getName()+"..."+j); } public synchronized void reduce(){ j=j-1; System.out.println(Thread.currentThread().getName()+"..."+j); } } class addImp implements Runnable{ private Resource r; public addImp(Resource r){//将Resource对象传递过来 this.r=r; } @Override public void run() { // TODO 自动生成的方法存根 r.add(); } } class reduceImp implements Runnable{ private Resource r; public reduceImp(Resource r){//将Resource对象传递过来 this.r=r; } @Override public void run() { // TODO 自动生成的方法存根 r.reduce(); } } class MainClass{ public static void main(String[] args){ Resource r=new Resource();//用的依然是方式一定义的Resource类 for(int i=0;i<2;++i) new Thread(new addImp(r)).start(); for(int i=0;i<2;++i) new Thread(new reduceImp(r)).start(); } }//方式二:2 class MainClass2{ private static int j=0;//共享数据作为这个外部类中的成员变量 public static synchronized void add(){//每个线程对共享数据的操作方法也分配给外部类 j=j+1; System.out.println(Thread.currentThread().getName()+"..."+j); } public static synchronized void reduce(){ j=j-1; System.out.println(Thread.currentThread().getName()+"..."+j); } public static void main(String[] args){//这里静态只能访问静态,以上成员需要static修饰符,根据需要 for(int i=0;i<2;++i){ new Thread(new Runnable(){ @Override public void run() { add(); } }).start(); new Thread(new Runnable(){ @Override public void run() { reduce(); } }).start(); } } }//方式2:3
class MultithreadShare7 { private static Resource r=new Resource(); public static void main(String[] args){ //final Resource r=new Resource();//r作为局部变量,被内部类访问需要final修饰 for(int i=0;i<2;++i){ new Thread(new Runnable(){ @Override public void run() { r.add(); } }).start(); new Thread(new Runnable(){ @Override public void run() { r.reduce(); } }).start(); } } }线程间通信例子:
package com.itheima.thread; /*子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程循环 100,如此循环50次,请写出程序*/ //使用的3思想 class RunCode{ private boolean flag=false;//使用标记进一步控制 public synchronized void subThreadCode(){ while(flag) try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0;i<5;++i) System.out .println(Thread.currentThread().getName() + "..." + i); flag=true; this.notify(); } public synchronized void mainThreadCode(){ while(!flag) try { this.wait(); } catch (InterruptedException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); } for(int i=0;i<3;++i) System.out .println(Thread.currentThread().getName() + "..." + i); flag=false; this.notify(); } } public class ThreadInterviewQuestion4 { /** * @param args */public static void main(String[] args) { final RunCode rc=new RunCode(); new Thread(new Runnable(){ @Override public void run() { for(int i=0;i<10000;++i){//没有按照原题目,为了看是否有”奇迹”发生 rc.subThreadCode(); } } }).start(); for(int i=0;i<10000;++i){ rc.mainThreadCode(); } } } /* 规范代码: 1.多个线程执行代码放在共享资源中(这样做便于管理,扩展) 2.等待唤醒机制一般都要使用标记 3.尝试各种方法,发现最好使用if判断是否wait,然后线程执行代码,置换标记,唤醒(其它方式各种"奇迹") 4.即使两个线程进行通信,依然建议while判断标记,api中一句话:对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用更加安全 */
4.ThreadLocal:
用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。
不使用ThreadLocal达到需求,采用 局部变量+Map集合
package com.itheima.thread; /* 如何达到当0线程执行时,A,B访问的是0线程的data,当1线程执行时,A,B访问的是1线程的data? 方式一.考虑使用同步 那么:A,B访问data也必须加入到同步中,很大的限制:只有A,B都访问完0线程,才能访问1线程,局限性比较强 方式二.使用HashMap集合->将线程与对应的数据绑定 使用局部的data,每个线程在栈中都将对应一个局部变量 不能再使用类变量,依然可能出现线程间的data覆盖问题 */ import java.util.HashMap; import java.util.Map; import java.util.Random; public class ThreadScopeShareData5 { //private static int data; private static Map<Thread,Integer> map=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() { //synchronized(int.class){ int data = new Random().nextInt(); map.put(Thread.currentThread(),data); System.out.println(Thread.currentThread().getName()+ "has put data:" + data); new A().get(); new B().get(); //} } }).start(); } } static class A{ public void get() { int data=map.get(Thread.currentThread()); System.out.println("A " + Thread.currentThread().getName() + "get data :" +data); } } static class B{ public void get() { int data=map.get(Thread.currentThread()); System.out.println("B " + Thread.currentThread().getName() + "get data :" +data); } } }//使用ThreadLocal /*每个线程调用全局ThreadLocal对象的set方法, 就相当于往其内部的map中增加一条记录, key分别是各自的线程,value是各自的set方法传进去的值。*/ public class ThreadLocalTest6 { private static ThreadLocal<Integer> tl=new ThreadLocal<Integer>(); public static void main(String[] args) { for(int i = 0; i <3; i ++) { new Thread(new Runnable() { @Override public void run() { int data = new Random().nextInt(); tl.set(data);//将 当前线程=data 存入ThreadLocal内部集合 System.out.println(Thread.currentThread().getName()+ "has put data:" + data); new A().get(); new B().get(); } }).start(); } } static class A{ public void get() { System.out.println("A " + Thread.currentThread().getName() + "get data :" +tl.get()); } } static class B{ public void get() { System.out.println("B " + Thread.currentThread().getName() + "get data :" +tl.get()); } } }//当有多个变量时,把多个变量封装到类中 /* 一个ThreadLocal对象只能操作一个变量,如上只能操作data. 那么如果有多个变量,data1,data2..... 考虑使用类这些变量封装, 一般做法:ThreadLocal操作该类的对象(ThreadLocal.set(object),ThreadLocal.get(object)) 换一种思想:把ThreaLocal的get()与set()也封装到该类中,对外提供方法,直接返回当前线程绑定的实例 */ class ThreadLocalTest7{ public static void main(String[] args) { for(int i = 0; i <3; i ++) { new Thread(new Runnable() { @Override public void run() { int data=new Random().nextInt(); MyThreadLocalData.getThreadInstance().setName("zhang"+data); MyThreadLocalData.getThreadInstance().setAge(20+data); new A().get(); new B().get(); } } ).start(); } } static class A{ public void get() { System.out.println("A " + Thread.currentThread().getName() + "get data :" +MyThreadLocalData.getThreadInstance().getName()+".."+MyThreadLocalData.getThreadInstance().getAge()); } } static class B{ public void get() { System.out.println("B " + Thread.currentThread().getName() + "get data :"+ MyThreadLocalData.getThreadInstance().getName()+".."+MyThreadLocalData.getThreadInstance().getAge()); } } } class MyThreadLocalData{ private String name; private String sex; private int age; private static ThreadLocal<MyThreadLocalData> tl=new ThreadLocal<MyThreadLocalData>(); //单例懒汉式改造 public static MyThreadLocalData getThreadInstance(){ MyThreadLocalData myData=tl.get();//每个线程对应一个myData,因此不使用同步 if(myData==null){//判断当前线程是否有对应的实例,没有创建->绑定,有返回 myData=new MyThreadLocalData(); tl.set(myData); } return myData; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }