要理解Java多线程,首先要区分进程和线程的概念。
进程 每个进程是一个应用程序,都有独立的内存空间。在同一个操作系统中,可以同时启动多个进程。
线程 线程是一个进程中的执行场景,一个进程可以启动多个线程。那么多线程有什么作用呢?多线程不是为了提高执行速度,而是提高应用程序的使用率,给人的感觉是多个线程在同时并发执行。
创建线程 创建线程一共有三种方式,在这里我们总结一下常用的两种:
继承 Thread类 创建一个类,通过继承Thread类来开辟一个线程,然后在main()方法中h创建子类对象,调用start()方法启动线程,当前类中我们关注创建的子类对象启动的线程和main线程。
1 2 3 4 5 6 7 8 9 10 11 public class Rabbit extends Thread { @Override public void run () { for (int i =0 ;i<1000 ;i++) { System.out.println("兔子跑了" +i+"步" ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Test { public static void main (String[] args) { Rabbit rab = new Rabbit(); Tortoise tor = new Tortoise(); rab.start(); tor.start(); for (int i = 0 ;i<1000 ;i++) { System.out.println("main" +i); } } }
但是通过继承Thread类开辟多线程有一个弊端,因为Java只能单继承,当我们写的这个类需要实现其他类的功能时,就会遇到麻烦,因此我们通常采用第二种方式。
实现Runnable接口 实现Runnable接口创建线程有两个好处:1、避免了单继承的局限性;2、实现了资源的共享。我们通过一个例子来研究。
1 2 3 4 5 6 7 8 9 10 public class Programmer implements Runnable { @Override public void run () { for (int i =0 ;i<1000 ;i++) { System.out.println("敲代码" ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Test { public static void main (String[] args) { Programmer pro = new Programmer(); Thread proxy = new Thread(pro); proxy.start(); for (int i = 0 ;i<1000 ;i++) { System.out.println("聊天" ); } } }
线程的状态与终止线程 当我们创建了一个线程之后,它并不会立即进入运行状态,先进入就绪状态,当获得调度后才取得了CPU的使用权,当所分得的时间片结束,如果运行完线程就进入死亡状态,如果没有运行完,则回到就绪队列,等待下一次调度;在运行状态中发生导致阻塞的事件,进程就会进入阻塞状态,解除阻塞状态线程仍然会回到就绪状态,而不是运行状态。
终止线程 在停止线程的操作中要注意我们将不再使用Thread类提供的stop()方法,而是在线程体中设置一个私有的标识,对外提供一个公开的方法,操作该标识来停止线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Study implements Runnable { private boolean flag = true ; @Override public void run () { while (flag) { System.out.println("study thread..." ); } } public void stop () { this .flag = false ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Test { public static void main (String[] args) { Study s = new Study(); new Thread(s).start(); for (int i = 0 ; i<100 ;i++) { if (50 ==i) { s.stop(); } System.out.println("main...-->" +i); } } }
线程阻塞 线程阻塞我们通过Thread类提供的join()方法和yield()方法来进行演示,join()方法实则是线程的合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Test extends Thread { public static void main (String[] args) throws InterruptedException { Test test = new Test(); Thread t = new Thread(test); t.start(); for (int i =0 ;i<1000 ;i++) { if (50 ==i) { t.join(); } System.out.println("main..." +i); } } @Override public void run () { for (int i = 0 ;i<1000 ;i++) { System.out.println("join..." +i); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class yield extends Thread { public static void main (String[] args) { yield y = new yield(); Thread t = new Thread(y); t.start(); for (int i = 0 ; i < 1000 ; i++) { if (i % 20 == 0 ) { Thread.yield(); } System.out.println("main..." +i); } } @Override public void run () { for (int i =0 ;i<1000 ;i++) { System.out.println("yield..." +i); } } }
sleep sleep设置休眠的时间,单位毫秒,当一个线程遇到sleep的时候,就会睡眠,进入到阻塞状态,放弃CPU使用权,腾出CPU时间片,给其他线程用,所以在开发中通常我们会这样做,使其他的线程能够取得CPU时间片,当睡眠时间到达了,线程会进入可运行状态,得到CPU时间片继续执行,如果线程在睡眠状态被中断了,将会抛出IterruptedException。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class SleepTest1 { public static void main (String[] args) throws InterruptedException { Date endTime = new Date(System.currentTimeMillis()+10 *1000 ); long end = endTime.getTime(); while (true ) { System.out.println(new SimpleDateFormat("mm:ss" ).format(endTime)); Thread.sleep(1000 ); endTime = new Date(endTime.getTime()-1000 ); if (end-10000 >endTime.getTime()) { break ; } } } public static void test () throws InterruptedException { int num = 10 ; while (true ) { System.out.println(num--); Thread.sleep(1000 ); if (num<=0 ) { break ; } } } }
线程同步 线程同步,指某一个时刻,只允许一个线程来访问共享资源,线程同步其实是对对象加锁,如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上的问题,我们只要保证线程一操作S变量时,线程2不允许操作即可,只有线程一使用完S后,再让线程二来使用S变量。那么为什么要引入线程同步呢?这是为了数据的安全,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制。同步通常有两种方式,一是同步方法,而是同步代码块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Web implements Runnable { private int num = 10 ; private boolean flag = true ; @Override public void run () { while (flag) { test3(); } } public void test2 () { synchronized (this ) { if (num<=0 ) { flag = false ; return ; } try { Thread.sleep(500 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"抢到了" +num--); } } public synchronized void test () { if (num<=0 ) { flag = false ; return ; } try { Thread.sleep(500 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"抢到了" +num--); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Test { public static void main (String[] args) { Web web = new Web(); Thread t1 = new Thread(web,"工程师" ); Thread t2 = new Thread(web,"程序员" ); Thread t3 = new Thread(web,"黄牛甲" ); t1.start(); t2.start(); t3.start(); } }
线程调度 关于线程的调度,我们通过提供的TimerTask类来进行学习。
1 2 3 4 5 6 7 8 9 10 11 12 public class Test { public static void main (String[] args) { Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run () { System.out.println("so easy..." ); } }, new Date(System.currentTimeMillis()+1000 ),1000 ); } }