第一,首先说一下为什么要使用多线程。
线程是大多数操作系统调度的基本单元,一个程序作为一个进程来执行,程序运行过程中能够创建多个线程,而一个线程在一个时刻只能运行在一个处理器核心上。也就是说单线程程序只能使用一个处理器核心,那么加入再多的处理器核心也无法显著提升程序的执行效率。相反,如果该程序使用多线程技术,将计算逻辑分配到多个处理器核心上,就会显著减少程序的处理时间,并且随着更多处理器核心的加入而变得更有效率。
第二,线程的创建。
目前有3种创建线程的方式:
1.继承Thread类的方式:
class SubThread extends Thread {
private int count;
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("current thread: " + Thread.currentThread().getName());
}
}
public class ThreadTest {
public static void main(String[] args) {
Thread thread1 = new SubThread();
thread1.setName("线程1");
thread1.start();
}
}
以上代码,用Thread子类的构造器产生一个Thread子类对象并向上转型为Thread类型。之后可以给线程对象设置名字、设置优先级,然后调用
start()方法启动线程。如果不给线程设置名字,则线程的名字默认为Thread-0、Thread-1这种格式的。如果不给线程设置优先级,则线程的优先级默认是5,即是Thread.NORM_PRIORITY。
如果想在创建线程的时候就设置名字,则可创建一个String类型的成员变量,然后调用以此变量为参数的构造器就可得到一个线程对象,值得注意的是,必须要在此构造器中调用父类也就是Thread的参数是String类型的构造器:
class SubThread extends Thread {
private int count;
private String name;
public SubThread(String name) {
super(name);
this.name = name;
}
public SubThread() {
super();
}
public void run() {
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("current thread: " + Thread.currentThread().getName());
}
}
public class ThreadTest {
public static void main(String[] args) {
Thread thread1 = new SubThread("线程1");
thread1.start();
}
}
之前一直以为,继承Thread类创建线程的方式,多个线程不能共享Thread子类的成员变量。其实这是不对的。如果这个成员变量是一般类型(基本类型、String类型、Date类型、集合类型),这种说法是成立的,毕竟每一次调用new SubThread()方法就创建了一个新的对象,这些个成员变量都是不同对象的成员变量。但是如果这个成员变量是自定义的类类型,而且线程对象是调用以此自定义类类型变量为参数的构造器生成的,则此时多个线程可以共享Thread子类的成员变量,毕竟调用有参构造器时传入的参数是同一个:
class Account {
private int count = 500;
public void dec() {
while (count > 1) {
synchronized (this) {
count = count - 1;
System.out.println("current thread: " + Thread.currentThread().getName() + ",count=" + count);
}
}
}
}
class SubThread extends Thread {
private String name;
private Account account;
public SubThread(String name, Account account) {
super(name);
this.name = name;
this.account = account;
}
public SubThread() {
super();
}
public void run() {
account.dec();
}
}
public class ThreadTest {
public static void main(String[] args) {
Account account = new Account();
Thread thread1 = new SubThread("线程1", account);
Thread thread2 = new SubThread("线程2", account);
thread1.start();
thread2.start();
}
}
如上,调用第三方类变量作为参数的构造器来创建线程,当传入同一个第三方类实例时,创建的多个线程就可以共享这个第三方类实例,当然也就共享这个第三方类实例的成员变量了。
2.实现Runnable接口的方式:
class ThreadImpl implements Runnable {
private int count;
@Override
public void run() {
while (true) {
count = count + 1;
System.out.println("count="+count);
if (count > 100) {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Runnable target = new ThreadImpl();
Thread thread1 = new Thread(target);
thread1.start();
}
}
以上,其实是用了Thread(Runnable target) 构造器,传入一个Runnable对象就可以得到一个线程对象。如果想在创建线程的时候给线程设置名字,则可以用另一个构造器Thread(Runnable target, String name) ,第二个参数传线程名字就好了。其实Thread类还有一个public的无参构造器,但是用此无参构造器生成的线程对象调用start()启动后执行的run()方法是Thread类的run()方法,什么也不做,所以此无参构造器实际上没有啥作用。
如果创建N个线程传的是同一个Runnable对象,则这N个线程共享此Runnable对象的成员变量。
class ThreadImpl implements Runnable {
private int count;
@Override
public void run() {
while (true) {
count = count + 1;
System.out.println("当前线程:" + Thread.currentThread().getName() + ",count:" + count);
if (count > 100) {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Runnable target = new ThreadImpl();
Thread thread1 = new Thread(target, "线程1");
Thread thread2 = new Thread(target, "线程2");
thread1.start();
thread2.start();
}
}
以上,用两个线程对同一个变量account进行增长操作,在打印结果中线程1和线程2交叉出现,顺序不一定,account的值从1一直升到101,中间顺序可能紊乱,也能有几条相同,这就是线程不安全。
3.实现Callable接口的方式:
class CallableImpl implements Callable<Integer> {
private int count;
@Override
public Integer call() {
while (true) {
count = count + 1;
System.out.println("当前线程:" + Thread.currentThread().getName()
+ ",count=" + count);
if (count > 100) {
break;
}
}
return count;
}
}
public class ThreadTest {
public static void main(String[] args) {
CallableImpl callableImpl = new CallableImpl();
FutureTask<Integer> futureTask1 = new FutureTask<Integer>(callableImpl);
new Thread(futureTask1, "线程1").start();
try {
System.out.println("线程1的返回值: " + futureTask1.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
以上,创建了Callable接口的实现类,并重写了Callable的call方法。创建该Callable实现类的对象,并传入到FutureTask的构造器FutureTask(Callable<V> callable)中,得到FutureTask对象。因为FutureTask类实现了Runnable接口,所以该FutureTask对象可以传到Thread的构造器Thread(Runnable target)中,从而得到一个线程对象。同Runnable实现类的run方法体是线程的执行体一样,Callable实现类的call方法体即为线程的执行体。不同的是,call方法是有返回值的,调用FutureTask对象的重载的get()方法可以得到线程的返回值,而至于返回值用不用,则看具体业务。
以这种方式创建线程,如果想多个线程共享一个成员变量,只需创建一个Callable对象,然后用该Callable对象创建多个FutureTask对象,再创建多个线程即可,这样多个线程就可以共同操作那一个Callable对象的成员变量了。见以下代码:
class CallableImpl implements Callable<Object> {
private int count;
@Override
public Object call() {
while (true) {
count = count + 1;
System.out.println("当前线程:" + Thread.currentThread().getName()
+ ",count=" + count);
if (count > 100) {
break;
}
}
return null;
}
}
public class ThreadTest {
public static void main(String[] args) {
CallableImpl callableImpl = new CallableImpl();
FutureTask<Object> futureTask1 = new FutureTask<Object>(callableImpl);
FutureTask<Object> futureTask2 = new FutureTask<Object>(callableImpl);
new Thread(futureTask1, "线程1").start();
new Thread(futureTask2, "线程2").start();
}
}
注意,以上代码也存在线程安全问题。
第三,线程的优先级:
线程的优先级是用1到10的整数表示的,数值越大,表示优先级越高,获得时间片从而被CPU执行的机会越大。
Thread类有3个final的静态变量,Thread.MIN_PRIORITY、Thread.NORM_PRIORITY、Thread.MAX_PRIORITY,值分别是1、5、10,分别对应着线程的最低优先级、普通优先级和最大优先级。值得注意的是,每个线程在new出来之后的优先级都是NORM_PRIORITY,调用getPriority()方法可以得到线程的优先级。而如果要提升或者降低优先级,则只需要调用setPriority(int newPriority)方法即可。