Java线程启动方式
在Java中有两种方式可以启动线程,一种方式是通过继承Thread类,另一种方式通过继承Runnable接口。
public class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub for(int i = 0; i < 10; ++i) { System.out.println(i); } } public static void main(String []args) { MyThread thread = new MyThread(); /**线程的启动的通过调用start方法,直接调用run方法无异于调用类中的方法*/ thread.start(); } }
public class MyRunnable implements Runnable { @Override public void run() { // TODO Auto-generated method stub for(int i = 0 ; i < 10; ++i) { System.out.println(i); } } public static void main(String []args) { MyRunnable task = new MyRunnable(); Thread thread = new Thread(task); thread.start(); } }
创建出Thread类的对象之后通过调用start方法启动线程的运行,而不是run方法。当线程的代码逻辑执行完毕之后,线程会自动结束(就是说当程序执行完run方法体后,线程结束)。值得注意的是,对Java来说,run方法没有任何特别之处,像main方法一样,它只是新线程知道调用的方法名称。因此,在Runnable上或者Thread上调用run方法是合法的,但这并不启动新的线程。
Java线程的同步机制
在Java虚拟机中,每个类和对象在逻辑上都有一个与之关联的监视器,该监视器便是获取类和对象的锁,在任何时候只允许一个线程拥有类和对象的锁。
类锁实际上也是通过对象锁(Class对象)来实现的,当虚拟机加载一个class文件时,会创建一个java.lang.Class类的实例,当锁住类时,实质上是锁住的该类对应的Class对象。
线程可以对同一个对象上锁,对于每一个对象,Java虚拟机维护一个加锁计数器,线程每次获得该对象锁时,计数器就加一,释放锁时,计数器就减一,只有当计数器为零时,锁就被完全释放。
public class NumberTask implements Runnable { public String num; public Demo main; public NumberTask(String num, Demo main) { this.num = num; this.main = main; } @Override public void run() { try { main.run(num); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Demo { public void run(String threadNum) throws InterruptedException { for (int i = 0; i < 5; i++) { System.out.println("[" + threadNum + "] " + i); } } public static void main(String []args) { Demo ma = new Demo(); ExecutorService es = Executors.newFixedThreadPool(2); for (int i = 0; i < 2; i++) { String num = "THREAD_" + String.format("%02d", i+1); es.submit(new NumberTask(num, ma)); } } }
在上述代码中,NumberTask中run方法调用Demo类中run,run方法没有添加synchronized关键字,也就是允许多个线程进入该方法,因此输出结果如下:
[THREAD_01] 0 [THREAD_01] 1 [THREAD_01] 2 [THREAD_02] 0 [THREAD_02] 1 [THREAD_02] 2 [THREAD_02] 3 [THREAD_01] 3 [THREAD_02] 4 [THREAD_01] 4
Java中synchronized关键字,用来控制线程之间同步的,如果在Demo类中的run方法添加synchronized关键字,则只是允许一个线程访问该方法,因此结果输出如下:
[THREAD_01] 0 [THREAD_01] 1 [THREAD_01] 2 [THREAD_01] 3 [THREAD_01] 4 [THREAD_02] 0 [THREAD_02] 1 [THREAD_02] 2 [THREAD_02] 3 [THREAD_02] 4
使用关键词synchronized主要用来实现线程之间的互斥,即同一时刻只有一个线程允许执行特定的代码。通过互斥的方法来保证多个线程访问共享变量时的正确性。除了互斥访问之外,线程之间也需要通过协作的方式来完成某些任务。此时可以使用Object类提供的wait、notify和notifyAll方法。
public class Cache { private static final Integer SIZE = 10; private static List<Integer> caches = new ArrayList<Integer>(); private static int index = 0; public static void putDate(Integer integer) throws InterruptedException { synchronized (caches) { if (caches.size() > SIZE) { // 缓冲区尺寸大于10,线程等待 caches.wait(); // 释放caches对象锁 } caches.add(index++,integer); System.out.println("[" + Thread.currentThread().getName() + "]生成数据" + integer); caches.notify(); // 唤醒等待线程 } } public static void getDate() throws InterruptedException { synchronized (caches) { if (caches.size() <= 0) { caches.wait(); // 释放caches对象锁 } System.out.println("[" + Thread.currentThread().getName() + "]获取数据" + caches.remove(--index)); caches.notify(); } } } public class DateMakeThread extends Thread { public DateMakeThread(String name) { super(name); } @Override public void run() { while(true) { try { Cache.getDate(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class DateTakerThread extends Thread { public DateTakerThread(String name) { super(name); } @Override public void run() { Random random = new Random(); for (int i = 0; i < 10; i++) { try { Cache.putDate(random.nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Main { public static void main(String []args) { DateMakeThread dmt = new DateMakeThread("PRODUCER"); dmt.start(); DateTakerThread dtt = new DateTakerThread("CONSUMER"); dtt.start(); } }
在上述代码,Cache中"生产者-消费者"的缓冲区,DateMakeThread生成数据存入Cache中,Cache类中caches对象是互斥访问的,DateTakerThread用来在Cache中获取数据,值得注意的是:wait,notify,notifyAll方法的使用的前提是获得对象锁,wait方法会使得已获取的对象锁的线程,放弃对象锁。