1.什么是线程,和进程有哪些区别
进程:一个程序的一次运行,在执行过程中拥有独立的内存单元,而多个线程共享一块内存
线程:线程是指进程内的一个执行单元。
联系:线程是进程的基本组成单位
区别:(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
2.如何创建线程
1.继承Thread类,重写run方法
public class MyThread_01 extends Thread {
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
MyThread_01 thread_01 = new MyThread_01();
MyThread_01 thread_02 = new MyThread_01();
thread_01.start();
thread_02.start();
}
}
}
2.实现Runnable接口,重写run方法
public class MyThread_02 implements Runnable {
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(i);
}
}
public static void main(String[] args) {
MyThread_02 myThread_02= new MyThread_02();
Thread thread1=new Thread(myThread_02);
Thread thread2=new Thread(myThread_02);
thread1.start();
thread2.start();
}
}
3.实现Callable接口创建线程
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyThread_03 implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i <3 ; i++) {
System.out.println(i);
}
return "我是Callable,已完成任务";
}
public static void main(String[] args) throws Exception {
//利用MyThread类实例化Callable接口的对象
Callable callable= new MyThread_03();
//利用FutureTask类的构造方法public FutureTask(Claaable<V> callable)
//将Callable接口的对象传给FutureTask类
FutureTask task=new FutureTask(callable);
//将FutureTask类的对象隐式地向上转型
//从而作为Thread类的public Thread(Runnable runnable)构造方法的参数
Thread thread=new Thread(task);
//调用Thread类的start()方法
thread.start();
//FutureTask的get()方法用于获取FutureTask的call()方法的返回值,为了取得线程的执行结果
System.out.println(task.get());
}
}
运行结果:
对比:
1、继承Thread类有一个缺点就是单继承,而实现Runnable接口则弥补了它的缺点,可以实现多继承
2、继承Thread类必须如果产生Runnable实例对象,就必须产生多个Runnable实例对象,然后再用Thread产生多个线程;而实现Runnable接口,只需要建立一个实现这个类的实例,然后用这一个实例对象产生多个线程。即实现了资源的共享性
3.Runnable接口的run()方法没有返回值,而Callable接口中的call()方法有返回值,若某些线程执行完成后需要一些返回值的时候,就需要用Callable接口创建线程
3.线程的状态转换
从Thread的State中发现线程有六种状态
4.线程的基本操作
4.1.新建线程
新建线程很简单,只要使用new 关键字创建一个线程对象,并将它start启动起来即可。
public class MyThread_04 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread() { // 匿名类创建子线程
@Override
public void run() {
for(int i=0;,i<10;i++)
System.out.println(i);
}
};
thread.start(); // 启动线程
}
}
start()方法启动之后调用的是run()方法,但并不能直接调用run方法,因为这样调用的方法会和当前线程方法串行,而不是启动新的线程了。
4.2.终止线程
一般来说,线程在执行完毕后会自动结束,无须手工关闭。但有时有些线程执行本身就是个无穷循环,我们需要手动的对它进行关闭。Thread提供了一个stop()方法,但你会发现方法前加上了@Deprecated,表明这个方法或类不再建议使用。
为什么stop不再建议使用了呢?因为它太暴力了,把执行到一般的线程直接终止,不管线程逻辑是否完整,这是非常危险的.可能会引起一些数据不一样的问题。
public class MyThread_04 {
public static void main(String[] args) throws Exception {
Thread thread = new Thread() { // 匿名类创建子线程
@Override
public void run() {
try {
Thread.sleep(1000); // 该线程休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("此处代码不会执行");
}
};
thread.start(); // 启动线程
Thread.sleep(100); // 主线程休眠0.1秒
thread.stop(); // 子线程停止
}
}
那我们应该如何正确关闭这个线程呢
public class MyTread_05 extends Thread {
public volatile boolean exit = false; //定义一个变量来决定线程停止
@Override
public void run() {
while (!exit) {
System.out.println(1);
}
}
public static void main(String[] args) throws Exception
{
MyTread_05 thread = new MyTread_05();
thread.start();
sleep(1000); // 主线程延迟5秒
thread.exit = true; // 终止线程thread
System.out.println("线程退出!");
}
}
4.3线程休眠(sleep)与中断(interrupt)
Thread.sleep()方法会让当前线程休眠若干时间,它会抛出一个InterruptedException异常。InterruptedException异常不是中断异常,所以必须try-catch处理。当sleep休眠时,如果被中断,这个异常就会产生。
public static native void sleep(long millis) throws InterruptedException:每次休眠多少毫秒
public static void sleep(long millis, int nanos)throws InterruptedException:每次休眠多少毫秒,休眠几次
public class MyThread_8 extends Thread {
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
try {
Thread.sleep(1000);//每次休眠一秒
System.out.println(i);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread_8 myThread_8=new MyThread_8();
myThread_8.start();
}
}
interrupt()不能中断在运行中的线程,它只能改变中断状态而已。相关方法是:
public void interrupt() :中断
public static boolean interrupted():判断是否被中断
public boolean isInterrupted():判断是否被中断,并清除当前中断状态
interrupt()方法经常用来吵醒”休眠的线程“.当一些线程调用sleep方法处于休眠状态时,一个占有CPU资源的线程可以调用interrupt方法吵醒自己,即导致休眠的线程发生InterruptedException异常,从而结束休眠
public class MyThread_07{
public static void main(String[] args) {
ClaaRoom claaRoom= new ClaaRoom();
claaRoom.student.start();
claaRoom.teacher.start();
}
}
class ClaaRoom implements Runnable {
Thread student,teacher; //定义两个线程
public ClaaRoom() {
student=new Thread(this);
teacher=new Thread(this);
teacher.setName("王老师");
student.setName("小明");
}
@Override
public void run() {
if (Thread.currentThread() == student) {
try {
System.out.println(student.getName()+"正在睡觉,不听课");
Thread.sleep(1000 * 60 * 60);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被老师吵醒了");
}
System.out.println(Thread.currentThread().getName() + "开始听课");
} else if (Thread.currentThread() == teacher) {
for (int i = 0; i <= 3; i++) {
System.out.println("上课");
}
try {
Thread.sleep(500);
} catch (InterruptedException e) { }
student.interrupt(); //叫醒小明
}
}
}
4.4等待(wait)和通知(notify)
wait和notify方法并不是Thread里的方法,而是Object类的方法,主要用于多线程间的协同处理,即控制线程之间的等待、通知、切换及唤醒。
在某个线程方法中对wait()和notify()的调用必须指定一个Object对象,而且该线程必须拥有该Object对象的监视器(monitor)。而获取对象监视器(monitor)最简单的办法就是,在对象上使用synchronized关键字。当调用wait()方法以后,该线程会释放掉这个监视器,并进入等待队列中等待。而在其它线程调用这个Object对象的notify()方法时,任选这个对象其中一个等待线程将被唤醒,而notifyAll()则是将其所有等待线程唤醒。
public class MyThread_09 {
final static Object object= new Object();
public static class Thread_01 extends Thread{
@Override
public void run() {
synchronized (object){
System.out.println(System.currentTimeMillis()+"Thread_01 start");
try {
System.out.println(System.currentTimeMillis()+"Thread_01 wait for object");
object.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()+"Thread_01 end");
}
}
}
public static class Thread_02 extends Thread{
@Override
public void run() {
synchronized (object){
System.out.println(System.currentTimeMillis()+"Thread_02 start! notify one Thread");
object.notify();
System.out.println(System.currentTimeMillis()+"Thread_02 end");
try {
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread t1=new Thread_01();
Thread t2=new Thread_02();
t1.start();
t2.start();
}
}
从运行结果来看,线程t2在nitify()方法调用前也必须获得该object对象的监视器,而t1在唤醒后首先不是继续执行下面的代码,而是重新获得object的监视器,如果暂时无法获得及必须等待这个监视器,直到获得监视器才继续往下执行。
注:wait()和sleep()方法都可以让线程等待若干时间,但两者还是有区别的
1) 原理不同。sleep()方法是Thread类的静态方法,是线程用来控制自身流程的,他会使此线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,此线程会自动苏醒。例如,当线程执行报时功能时,每一秒钟打印出一个时间,那么此时就需要在打印方法前面加一个sleep()方法,以便让自己每隔一秒执行一次,该过程如同闹钟一样。而wait()方法是object类的方法,用于线程间通信,这个方法会使当前拥有该对象锁的进程等待,直到其他线程调用notify()方法或者notifyAll()时才醒来,不过开发人员也可以给他指定一个时间,自动醒来。
2) 对锁的 处理机制不同。由于sleep()方法的主要作用是让线程暂停执行一段时间,时间一到则自动恢复,不涉及线程间的通信,因此,调用sleep()方法并不会释放锁。而wait()方法则不同,当调用wait()方法后,线程会释放掉他所占用的锁,从而使线程所在对象中的其他synchronized数据可以被其他线程使用。
3) 使用区域不同。wait()方法必须放在同步控制方法和同步代码块中使用,sleep()方法则可以放在任何地方使用。sleep()方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。在sleep的过程中,有可能被其他对象调用他的interrupt(),产生InterruptedException。由于sleep不会释放锁标志,容易导致死锁问题的发生,因此一般情况下,推荐使用wait()方法。
4.5,线程挂起(suspend)和继续执行(resume)线程
线程挂起(suspend)和继续执行(resume)是一对相反的操作,被挂起的线程必须要等到resume()后,才能继续执行。看上出这两个方法应该很好用,但其实他们已经被官方弃用了,不建议使用。原因就是suspend挂起暂停线程的同时,并不会释放任何锁资源,此时,其他任何线程想要访问它占用的锁时都会被牵连,导致无法继续运行。直到对应的线程进行了resume操作,被挂起的线程才可以继续执行。但是resume()操作意外在suspend()前执行了,那么被挂起的线程可能很难有机会继续被执行,被占用的的锁资源不会被释放,导致整个系统工作不正常。而且从它的线程状态来看,还是Runnable,严重影响我们对系统的判断。
public class MyThread_10 {
public static Object u=new Object();
static ChangeObjectThread t1=new ChangeObjectThread("t1");
static ChangeObjectThread t2=new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread{
public ChangeObjectThread(String name) {
super.setName(name);
}
@Override
public void run() {
synchronized (u){
System.out.println("in "+ getName());
Thread.currentThread().suspend();
}
}
}
public static void main(String[] args) throws InterruptedException{
t1.start();
Thread.sleep(100);
t2.start();
t1.resume();
t2.resume();
}
}
表明这两个线程先后进入了临界区,但程序不会退出,而是会挂起。
4.6 等待线程结束(join)和谦让(yield)
在很多情况下,一个线程的输入可能非常依赖于另外一个或者多个线程的输出,此时,这个依赖的线程就需要等待目标线程执行完毕,才能继续执行.JDK提供了join()方法来实现这个功能
public final void join() throws InterruptedException;//表示无限期等待,它会一直阻塞当前线程,直到目标线程执行完毕
public final synchronized void join(long millis) throws InterruptedException;
//给出了等待时间,如果超过给定时间目标线程还没有执行完毕,当前线程也会因为等待时间过长,开始继续执行
public class MyThread_11 {
public volatile static int i = 0;
//目标线程
public static class AddThread implements Runnable{
@Override
public void run() {
for (i = 0; i < 10;i++){
if (i == 9999){
System.out.println("自增结束,i = " + i);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new AddThread());
thread.start();
thread.join();
System.out.println(i);
}
}
从结果来看,主线程是等AddTread线程结束后才执行的,不然打印出来的结果会是下面这样
yield():
public static native void yield();
这是一个静态的本地方法,一旦执行,它会使当前线程让出CPU的资源。但是要注意,让出CPU资源并不代表当前线程不执行了。当前线程让出CPU资源后,还会进行CPU资源的争夺,但是能否再次被分配到资源就不一定了。 使用场景: 如果你觉得一个线程不那么重要,或者优先级非常低,并且害怕它占用太多的CPU资源,那么可以在适当的时候调用该方法Thread,yield(),给予其他重要线程更多的执行机会。
5.线程优先级
线程是有优先级的,也就是线程的执行顺序。 线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。
获取并设置Java线程的优先级:
public final int getPriority():返回给定线程的优先级。
public final void setPriority(int newPriority):将线程的优先级更改为值newPriority。如果参数
newPriority的值超出最小(1)和最大(10)限制,则此方法抛出IllegalArgumentException。
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
public class MyThread_12 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread(), "t1");
Thread t2 = new Thread(new MyThread(), "t2");
Thread t3 = new Thread(new MyThread(), "t3");
t1.setPriority(1);
t3.setPriority(10);
t1.start();
t2.start();
t3.start();
}
}
从结果可知:具有最高优先级的线程将在其他线程之前获得执行机会,优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程
6.守护线程Daemon
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。
守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程(GC)就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了
public class MyThread_13 {
static class Daemon extends Thread{
@Override
public void run() {
while (true) {
System.out.println(" daemon is alive");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread=new Daemon();
thread.setDaemon(true);
thread.start();
Thread.sleep(3000);
}
}
从结果可知,在main线程停止后,Daemon线程也停止了,说明Daemon已经设置成了守护线程。需要注意的是:
1.thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程;2.在Daemon线程中产生的新线程也是Daemon的;3 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。