多线程基础知识第一篇:创建线程3种方式

原创 2015年07月08日 17:30:13

第一,首先说一下为什么要使用多线程。

线程是大多数操作系统调度的基本单元,一个程序作为一个进程来执行,程序运行过程中能够创建多个线程,而一个线程在一个时刻只能运行在一个处理器核心上。也就是说单线程程序只能使用一个处理器核心,那么加入再多的处理器核心也无法显著提升程序的执行效率。相反,如果该程序使用多线程技术,将计算逻辑分配到多个处理器核心上,就会显著减少程序的处理时间,并且随着更多处理器核心的加入而变得更有效率。

第二,线程的创建。

目前有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)方法即可。

Java创建线程的三种方式及其对比

Java中创建线程主要有三种方式: 一、继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()...
  • shenggaofei
  • shenggaofei
  • 2016年09月23日 20:54
  • 3934

黑马程序员——多线程—创建线程的3种方式

一、ji
  • guoying252166655
  • guoying252166655
  • 2014年09月21日 20:42
  • 286

java创建线程的四种方式

java创建线程的三种方式 1. 继承Thread类创建线程类 2. 通过Runable接口创建线程类 3. 通过Callable和FutureTask创建线程     a. 创建Callab...
  • u012973218
  • u012973218
  • 2016年04月29日 10:51
  • 2943

创建线程的三种方式优缺点

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。 一、继承Thread类创建线程类 1.重写run方法。该run()方法的方法体就代表了线程需要完成的任务 ...
  • sinat_27933301
  • sinat_27933301
  • 2017年04月10日 11:08
  • 940

创建线程的3种方式

Thread:继承Thread类,可以直接使用this关键字,不能继承其他类; runnable:可以继承其他类,多个线程共享一个target,必须使用Thread.currentThread()....
  • xiaocie3456
  • xiaocie3456
  • 2013年08月30日 19:10
  • 530

创建线程的三种方式和区别

第一种创建方式: 继承Thread,并且重写run方法。package com.easytopit.thread; public class ExtendsThread extends Thread...
  • HelianthS
  • HelianthS
  • 2016年07月09日 15:37
  • 411

创建线程的两种方法,及之间的区别

创建线程的第一种:继承Thread类. 步骤: 1,定义类继承Thread. 2,复写Thread类中的run方法. 目的:将自定义代码存储在run方法.让线程运行. 3,创建线程的子类对象...
  • u011445690
  • u011445690
  • 2013年08月04日 23:33
  • 943

java实现多线程的3种方法

java多线程
  • high2011
  • high2011
  • 2015年10月27日 10:57
  • 727

创建线程的两种方式区别

Java提供了线程类Thread来创建多线程的程序。其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象。每个Thread对象描述了一个单独的线程。要产生一个线...
  • Krito_blog
  • Krito_blog
  • 2017年02月01日 12:49
  • 1602

java并发——四种创建线程方式

并发用来提高运行在单处理器上的程序的性能。 这听起来有些违背直觉。如果有多个CPU处理器,那么我们让不同CPU并发处理程序一定会让速度变快。但是我们只有一个处理器,并发看起来只会增加上下文切换的开销时...
  • QuinnNorris
  • QuinnNorris
  • 2017年04月08日 21:33
  • 707
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:多线程基础知识第一篇:创建线程3种方式
举报原因:
原因补充:

(最多只允许输入30个字)