谈谈多线程
package thread; /** * 线程 * 线程可以并发执行多段代码,给我们感觉上好像这些代码 * 在"同时运行"。 * * 创建线程有两种方式: * 方式一:继承Thread并重写run方法。 * @author ta * */ public class ThreadDemo1 { public static void main(String[] args) { MyThread1 t1 = new MyThread1(); MyThread2 t2 = new MyThread2(); /* * 启动线程要调用start方法,而不是直接调用线程 * 的run方法。 */ t1.start(); t2.start(); } } /** * 第一种创建线程的方式存在两个不足之处 * 1:由于java是单继承的,这导致继承了Thread后就不能再 * 继承其他类了。在实际开发中经常会继承某个超类来复 * 用其中的方法,这导致两者不能同时继承。 * * 2:继承线程后重写run方法来定义任务,这又导致我们将任 * 务直接定义在线程上,使得线程只能做该任务,无法并发 * 执行其他任务,重用性变差。 * @author ta * */ class MyThread1 extends Thread{ public void run() { for(int i=0;i<1000;i++) { System.out.println("你是谁啊?"); } } } class MyThread2 extends Thread{ public void run() { for(int i=0;i<1000;i++) { System.out.println("我是查水表的!"); } } }
package thread; /** * 第二种从创建线程的方式: * 实现Runnable接口,单独定义线程任务 * @author ta * */ public class ThreadDemo2 { public static void main(String[] args) { //实例化任务 Runnable r1 = new MyRunnable1(); Runnable r2 = new MyRunnable2(); //创建线程 Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); t1.start(); t2.start(); } } class MyRunnable1 implements Runnable{ public void run() { for(int i=0;i<1000;i++) { System.out.println("你是谁啊?"); } } } class MyRunnable2 implements Runnable{ public void run() { for(int i=0;i<1000;i++) { System.out.println("我是查水表的!"); } } }
线程提供了一系列获取当前线程信息的方法
package thread; /** * 线程提供了一系列获取当前线程信息的方法 * @author ta * */ public class ThreadInfoDemo { public static void main(String[] args) { //获取主线程 Thread main = Thread.currentThread(); //获取线程的唯一标识 long id = main.getId(); System.out.println("id:"+id); //获取线程的名字 String name = main.getName(); System.out.println("name:"+name); //获取优先级 int priority = main.getPriority(); System.out.println("priority:"+priority); //显示是否处于活动状态 boolean isAlive = main.isAlive(); //显示是否为守护线程 boolean isDaemon = main.isDaemon(); //显示线程是否被中断 boolean isInterrupted = main.isInterrupted(); System.out.println("是否活着:"+isAlive); System.out.println("是否为守护线程:"+isDaemon); System.out.println("是否被中断:"+isInterrupted); } }
如何创建线程池
package thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 线程池 * 线程池是一个管理线程的机制。它主要解决两个问题: * 1:重用线程 * 2:控制线程数量 * 频繁的创建和销毁线程会给系统带来额外的开销,所以线程应当 * 得以重用。 * 当线程数量过多时,会出现资源消耗增大,CPU出现过度切换导致 * 并发性能降低。对此线程的数量也要得以控制在当前硬件环境所能 * 承受的范围内。 * * @author ta * */ public class ThreadPoolDemo { public static void main(String[] args) { ExecutorService threadPool = Executors.newFixedThreadPool(2); for(int i=0;i<5;i++){ Runnable r = new Runnable() { public void run() { Thread t = Thread.currentThread(); System.out.println(t.getName()+":正在执行任务..."); try { Thread.sleep(5000); } catch (InterruptedException e) { } System.out.println(t.getName()+":执行任务完毕..."); } }; //将任务交给线程池 threadPool.execute(r); System.out.println("将一个任务指派给了线程池!"); } // threadPool.shutdown();//不是立即关闭,只是不再接受新任务 // threadPool.shutdownNow();//立刻停止,任务没执行完都停 // System.out.println("线程池停止了!"); } }
关于线程安全问题
package thread; /** * 多线程并发的安全问题 * 当多个线程并发操作同一资源时,由于线程切换时机的不确定 * 和不可控,会导致操作该资源的代码逻辑执行顺序未按照设计 * 要求运行,出现了操作混乱。严重时可能导致系统瘫痪。 * @author ta * */ public class SyncDemo { public static void main(String[] args) { final Table table = new Table(); Thread t1 = new Thread() { public void run() { while(true) { int bean = table.getBean(); Thread.yield(); System.out.println(getName()+":"+bean); } } }; Thread t2 = new Thread() { public void run() { while(true) { int bean = table.getBean(); Thread.yield(); System.out.println(getName()+":"+bean); } } }; t1.start(); t2.start(); } } class Table{ //桌子上有20个豆子 private int beans = 20; /** * 解决并发安全问题的核心就是将多个线程抢着运行改为 * 有先后顺序的排队运行。 * Java提供了锁机制,强制多个线程同步运行一个方法 * * 当一个方法上使用关键字:synchronized修饰后,该方法 * 称为同步方法,多个线程不能同时在方法内部运行。 * @return */ public synchronized int getBean() { if(beans==0) { throw new RuntimeException("没有豆子了!"); } /* * yield方法会让运行这个方法的线程立刻让出CPU时间 * 在这里是为了模拟发生线程切换。 */ Thread.yield(); return beans--; } }
package thread; /** * 同步块 * 有效的缩小同步范围可以在保证并发安全的前提下尽可能 * 的提高并发效率。 * * 语法: * synchronized(同步监视器对象){ * 需用同步运行的代码片段 * } * @author ta * */ public class SyncDemo2 { public static void main(String[] args) { final Shop shop = new Shop(); Thread t1 = new Thread() { public void run() { shop.buy(); } }; Thread t2 = new Thread() { public void run() { shop.buy(); } }; t1.start(); t2.start(); } } class Shop{ /** * 直接在方法上使用synchroinzed,那么同步监视器 * 对象就是当前方法所属对象,即:方法中看到的this */ // public synchronized void buy() { public void buy() { try { Thread t = Thread.currentThread(); System.out.println(t.getName()+":正在挑衣服..."); Thread.sleep(5000); /* * 同步块有一个要求,多个线程看到的同步监视器 * 对象必须是同一个!否则没有同步运行效果。 * 具体使用哪个对象可以结合将来实际开发需求而定。 */ synchronized (this) { System.out.println(t.getName()+":正在试衣服..."); Thread.sleep(5000); } System.out.println(t.getName()+":结账离开"); } catch (Exception e) { } } }
package thread; /** * 静态方法若使用synchronized修饰后,那么一定具有同步 * 效果。 * @author ta * */ public class SyncDemo3 { public static void main(String[] args) { Thread t1 = new Thread() { public void run() { Boo.dosome(); } }; Thread t2 = new Thread() { public void run() { Boo.dosome(); } }; t1.start(); t2.start(); } } class Boo{ public synchronized static void dosome() { try { Thread t = Thread.currentThread(); System.out.println(t.getName()+":正在运行dosome方法"); Thread.sleep(5000); System.out.println(t.getName()+":运行dosome方法完毕"); } catch (Exception e) { e.printStackTrace(); } } }
互斥锁
package thread; /** * 互斥锁 * 当synchronized同时锁定多段代码片段时,并且他们指定 * 的同步监视器对象是[同一个]时,那么这些代码片段之间就 * 是互斥的,即:多个线程不能同时执行这些代码片段。 * @author ta * */ public class SyncDemo4 { public static void main(String[] args) { final Foo foo = new Foo(); Thread t1 = new Thread() { public void run() { foo.methodA(); } }; Thread t2 = new Thread() { public void run() { foo.methodB(); } }; t1.start(); t2.start(); } } class Foo{ public synchronized void methodA() { try { Thread t = Thread.currentThread(); System.out.println(t.getName()+":正在执行A方法..."); Thread.sleep(5000); System.out.println(t.getName()+":执行A方法完毕"); } catch (Exception e) { } } public void methodB() { synchronized (this) { try { Thread t = Thread.currentThread(); System.out.println(t.getName()+":正在执行B方法..."); Thread.sleep(5000); System.out.println(t.getName()+":执行B方法完毕"); } catch (Exception e) { } } } }
睡眠阻塞
package thread; import java.util.Scanner; /** * 睡眠阻塞 * * static void sleep(long ms) * 该方法会让运行这个方法的线程处于阻塞状态指定的毫秒, * 当超时后,线程会自动回到RUNNABLE状态,等待再次获取 * 时间片并发运行。 * * 注:一个线程进入阻塞状态时,CPU会立刻释放去并发执行 * 其他线程,直到该线程接触阻塞状态为止。 * @author ta * */ public class SleepDemo { public static void main(String[] args) { /* * 程序启动后,输入一个数字,如:100 * 然后每一秒钟递减一次并输出,到0时停止。 */ Scanner scanner = new Scanner(System.in); System.out.println("请输入一个数字:"); int num = Integer.parseInt(scanner.nextLine()); for(int i=num;i>0;i--) { System.out.println(i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("结束."); } }
package thread; /** * 当一个线程调用sleep方法处于阻塞状态时,其他线程调用 * 该线程的中断方法时,那么该线程的sleep方法会立即抛出 * 中断异常并打断睡眠阻塞。 * @author ta * */ public class SleepDemo2 { public static void main(String[] args) { /* * JDK8之前,有一个要求,即: * 当一个方法中的局部内部类想引用这个方法的其他 * 局部变量,那么这个变量必须是final的。 * 好比main方法中的局部内部类huang中的run方法里 * 引用了main方法的局部变量lin,那么这个变量就 * 必须声明为final的。 */ final Thread lin = new Thread() { public void run() { System.out.println("林:刚美完容,睡一会."); try { /* * 当一个线程调用sleep阻塞时,被其他线程 * 中断时会抛出中断异常 */ Thread.sleep(1000000); } catch (InterruptedException e) { System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了相了!"); } System.out.println("林:醒了"); } }; Thread huang = new Thread() { public void run() { System.out.println("黄:开始砸墙!"); for(int i=0;i<5;i++) { System.out.println("黄:80!"); try { Thread.sleep(1000); } catch (InterruptedException e) { } } System.out.println("咣当!"); System.out.println("黄:搞定!"); //中断lin线程 lin.interrupt(); } }; lin.start(); huang.start(); } }
线程优先级
package thread; /** * 线程优先级 * * 线程无法主动获取CPU时间片,唯一可以干涉线程调度工作 * 的方式就是修改线程优先级,最大程度的改善获取CPU时间 * 片的几率。 * 理论上,线程优先级越高的线程获取CPU时间片的次数越多 * * 线程有10个优先级,分别用整数1-10表示。 * @author ta * */ public class PriorityDemo { public static void main(String[] args) { Thread max = new Thread() { public void run() { for(int i=0;i<10000;i++) { System.out.println("max"); } } }; Thread min = new Thread() { public void run() { for(int i=0;i<10000;i++) { System.out.println("min"); } } }; Thread norm = new Thread() { public void run() { for(int i=0;i<10000;i++) { System.out.println("nor"); } } }; max.setPriority(Thread.MAX_PRIORITY); min.setPriority(Thread.MIN_PRIORITY); min.start(); norm.start(); max.start(); } }
join方法
package thread; /** * join方法可以协调线程之间的同步运行 * * 异步运行:代码之间运行没有先后顺序,各干各的。 * 同步运行:代码运行有先后顺序。 * @author ta * */ public class JoinDemo { //标示图片是否下载完毕 public static boolean isFinish = false; public static void main(String[] args) { final Thread download = new Thread() { public void run() { System.out.println("down:开始下载图片..."); for(int i=1;i<=100;i++) { System.out.println("down:已下载"+i+"%"); try { Thread.sleep(50); } catch (InterruptedException e) { } } System.out.println("down:图片下载完毕!"); isFinish = true; } }; Thread show = new Thread() { public void run() { System.out.println("show:开始显示图片..."); /* * 先等在下载线程将图片下载完毕 */ try { /* * 当show线程调用download线程的join方法后就 * 进入了阻塞状态,直到download线程结束才会 * 解除阻塞。 */ download.join(); } catch (InterruptedException e) { } if(!isFinish) { throw new RuntimeException("图片加载失败!"); } System.out.println("show:显示图片完毕!"); } }; download.start(); show.start(); } }
守护线程
package thread; /** * 守护线程 * * 守护线程又称为后台线程,默认创建出来的线程都是普通 * 线程或称为前台线程。只有调用线程的setDaemon方法后 * 才会将该线程设置为守护线程。 * * 守护线程使用上与前台线程一样,但是在结束时机上有一点 * 不同:当进程结束时,所有正在运行的守护线程都会被强制 * 中断。 * 进程的结束:当一个进程中没有任何前台线程时即结束。 * * @author ta * */ public class DaemonThreadDemo { public static void main(String[] args) { Thread rose = new Thread() { public void run() { for(int i=0;i<5;i++) { System.out.println("rose:let me go!"); try { Thread.sleep(1000); } catch (InterruptedException e) { } } System.out.println("rose:啊啊啊啊啊AAAAAAaaaaa...."); System.out.println("噗通!"); } }; Thread jack = new Thread() { public void run() { while(true) { System.out.println("jack:you jump!i jump!"); try { Thread.sleep(1000); } catch (InterruptedException e) { } } } }; rose.start(); /* * 设置守护线程必须要在start方法之前进行。 */ jack.setDaemon(true); jack.start(); } }
static Thread currentThread()
package thread; /** * Thread提供了一个静态方法: * static Thread currentThread() * 该方法可以获取运行这个方法的线程。 * * 后期常用的一个API: ThreadLocal里面就会用到这个方法 * 来实现功能。 * @author ta * */ public class CurrentThreadDemo { public static void main(String[] args) { Thread main = Thread.currentThread(); System.out.println("运行main方法的线程是:"+main); Thread t = new Thread() { public void run() { Thread t = Thread.currentThread(); System.out.println("自定义线程:"+t); dosome(); } }; t.start(); dosome(); } public static void dosome() { Thread t = Thread.currentThread(); System.out.println("运行dosome方法的线程是:"+t); } }