스레드와 프로세스의 차이점은 무엇인가요? 답변: 프로세스는 프로그램이나 응용 프로그램으로 간주될 수 있는 독립적인 실행 환경입니다. 스레드는 프로세스에서 실행되는 작업입니다. 스레드는 프로세스의 하위 집합으로, 프로세스에는 여러 스레드가 있을 수 있으며 각 스레드는 서로 다른 작업을 병렬로 수행합니다. 서로 다른 프로세스는 서로 다른 메모리 공간을 사용하며 모든 스레드는 동일한 메모리 공간을 공유합니다.
1) 스레드: 프로세스에서 프로그램 실행을 담당하는 실행 단위
스레드 자체는 프로그램에 의존하여 실행됩니다.
스레드는 프로그램의 순차적 제어 흐름이며 다음을 수행할 수 있습니다. 프로그램과 환경에 할당된 리소스만 사용
2) 프로세스: 실행 프로그램
프로세스에는 적어도 하나의 스레드가 포함됩니다.
3) 단일 스레드: 프로그램에는 스레드가 하나만 있으며 실제로 주요 방법은 다음과 같습니다. 메인 스레드
4) 멀티 스레드: 프로그램에서 여러 작업을 실행하는 목적은 CPU 리소스를 더 잘 활용하는 것입니다. 2. 스레드 구현
class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动创建的第"+num+"个线程"); } }
public class Test { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动创建的第"+num+"个线程"); } }
public class Test { public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyThread thread1 = new MyThread("thread1"); thread1.start(); MyThread thread2 = new MyThread("thread2"); thread2.run(); } }class MyThread extends Thread{ private String name; public MyThread(String name){ this.name = name; } @Override public void run() { System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId()); } }
1) thread1과 thread2의 스레드 ID가 다릅니다. thread2는 메인 스레드와 동일한 ID를 갖고 있습니다. 이는 run 메소드를 호출해도 새 스레드가 생성되지 않고 메인 스레드에서 직접 run 메소드가 실행된다는 것을 의미하며 이는 일반 메소드와 다르지 않습니다.
2. Runnable 인터페이스 구현
Java에서 스레드를 생성할 때 Thread 클래스를 상속하는 것 외에도 Runnable 인터페이스를 구현하여 유사한 기능을 구현할 수도 있습니다. Runnable 인터페이스를 구현하려면 해당 run 메서드를 재정의해야 합니다. 다음은 예입니다.
public class Test { public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); } } class MyRunnable implements Runnable{ public MyRunnable() { } @Override public void run() { System.out.println("子线程ID:"+Thread.currentThread().getId()); } }
3. ExecutorService, Callable 및 Future를 사용하여 결과를 반환하는 멀티스레딩 구현
멀티스레딩에 대해서는 나중에 배우겠지만 지금은 이 방법만 알아두세요.
ExecutorService, Callable 및 Future 개체는 실제로 Executor 프레임워크의 기능 클래스에 속합니다. Executor 프레임워크에 대해 자세히 알아보려면 http://www.javaeye.com/topic/366591을 방문하세요. 여기에서 프레임워크에 대해 자세히 설명됩니다. 결과를 반환하는 스레드는 JDK1.5에서 도입된 새로운 기능인데, 이 기능을 사용하면 더 이상 반환 값을 얻기 위해 많은 어려움을 겪을 필요가 없으며, 구현하더라도 허점이 가득할 수도 있습니다. 값을 반환할 수 있는 작업은 Callable 인터페이스를 구현해야 합니다. 마찬가지로 반환 값이 없는 작업은 Runnable 인터페이스를 구현해야 합니다. Callable 작업을 실행한 후 개체에 대해 get을 호출하여 Callable 작업에서 반환된 개체를 얻을 수 있습니다. 스레드 풀 인터페이스 ExecutorService와 결합하면 결과를 반환하는 전설적인 멀티스레딩을 구현할 수 있습니다. 반환된 결과가 포함된 완전한 다중 스레드 테스트 예제가 아래에 제공됩니다. 이는 JDK1.5에서 검증되었으며 문제 없이 직접 사용할 수 있습니다. 코드는 다음과 같습니다./** * 有返回值的线程 */ @SuppressWarnings("unchecked") public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序开始运行----"); Date date1 = new Date(); int taskSize = 5; // 创建一个线程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 创建多个有返回值的任务 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 执行任务并获取Future对象 Future f = pool.submit(c); // System.out.println(">>>" + f.get().toString()); list.add(f); } // 关闭线程池 pool.shutdown(); // 获取所有并发任务的运行结果 for (Future f : list) { // 从Future对象上获取任务的返回值,并输出到控制台 System.out.println(">>>" + f.get().toString()); } Date date2 = new Date(); System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } } class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } public Object call() throws Exception { System.out.println(">>>" + taskNum + "任务启动"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任务终止"); return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】"; } }
위 코드의 Executors 클래스는 스레드 풀을 생성하기 위한 일련의 팩토리 메서드를 제공하며 반환된 스레드 풀은 모두 ExecutorService 인터페이스를 구현합니다.
public static ExecutorService newFixedThreadPool(int nThreads)고정된 개수의 스레드로 스레드 풀을 생성합니다.
public static ExecutorService newCachedThreadPool()
캐시 가능한 스레드 풀을 생성하고, 실행을 호출하면 이전에 생성된 스레드가 재사용됩니다(스레드가 사용 가능한 경우). 기존 스레드를 사용할 수 없으면 새 스레드가 생성되어 풀에 추가됩니다. 60초 동안 사용되지 않은 스레드를 종료하고 캐시에서 제거합니다.
public static ExecutorService newSingleThreadExecutor()
단일 스레드 실행자를 생성합니다.
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
대부분의 경우 Timer 클래스를 대체하는 데 사용할 수 있는 예약 및 주기적인 작업 실행을 지원하는 스레드 풀을 만듭니다.
ExecutoreService는 Callable 또는 Runnable을 전달하고 Future를 반환하는 submit() 메서드를 제공합니다. Executor 백그라운드 스레드 풀이 Callable 계산을 완료하지 않은 경우 이 호출은 계산이 완료될 때까지 차단되는 Future 개체의 get() 메서드를 반환합니다.
Thread 클래스의 특정 메소드를 정식으로 배우기 전에 먼저 스레드의 상태를 이해해 두면 나중에 Thread 클래스의 메소드를 이해하는 데 도움이 됩니다.
1) 생성(새) 상태: 멀티 스레드 객체가 준비되었습니다.
2) 준비(runnable) 상태: start() 메서드가 호출되어 CPU 스케줄링을 기다리고 있습니다.
3) 실행(running) 상태: Execute run( )방법
4) 차단된 상태: 실행을 일시적으로 중지하고 리소스를 다른 스레드에 넘겨서 사용할 수 있습니다.
5) 종료(죽은) 상태: 스레드가 파괴됩니다.
하위 작업을 수행하기 위해 새 스레드를 시작해야 하는 경우입니다. 스레드를 만들었습니다. 그러나 스레드가 생성된 후에는 즉시 준비 상태로 들어가지 않습니다. 스레드를 실행하려면 몇 가지 조건(예: 메모리 리소스)이 필요하기 때문입니다. JVM 메모리 영역 분할에 대한 이전 블로그 게시물에서 우리는 프로그램 카운터, Java 스택 및 로컬 메소드 스택은 모두 스레드에 전용이므로 스레드를 위해 일정량의 메모리 공간을 할당해야 합니다.) 스레드를 실행하는 데 필요한 모든 조건이 충족된 경우에만 준비 상태로 들어갑니다. 상태.
스레드가 준비 상태에 들어간다고 해서 CPU 실행 시간을 즉시 얻을 수 있다는 의미는 아닙니다. 어쩌면 이때 CPU가 다른 작업을 실행 중이므로 기다려야 할 수도 있습니다. CPU 실행 시간을 얻으면 스레드는 실제로 실행 상태로 들어갑니다.
스레드가 실행되는 동안 현재 스레드가 계속 실행되지 않는 데에는 여러 가지 이유가 있을 수 있습니다. 예를 들어 사용자가 스레드를 적극적으로 절전 모드로 설정한 다음 일정 시간 동안 절전 모드로 전환한 후 다시 실행하는 경우입니다. 사용자가 스레드를 적극적으로 기다리게 하거나 동기화 블록에 의해 제공됩니다. 차단은 현재 여러 상태에 해당합니다: 시간 대기(잠자기 또는 특정 이벤트를 기다리는 중), 대기 중(깨어나기를 기다리는 중), 차단됨(차단됨).
갑작스런 중단이나 하위 작업 실행이 완료되면 스레드가 종료됩니다.
다음 그림은 생성부터 종료까지 스레드의 상태를 설명합니다.
일부 튜토리얼에서는 차단, 대기, 시간 대기를 총칭하여 차단 상태라고 합니다. 스레드의 상태는 Java의 메소드 호출과 연결되어 있으므로 대기 상태와 시간 대기 상태가 분리됩니다.
참고: sleep과 wait의 차이점:
sleep은 Thread 클래스의 메서드이고 wait는 Object 클래스에 정의된 메서드입니다.
Thread.sleep은 현재 스레드가 소유하는 경우 잠금 동작을 변경하지 않습니다. 그러면 Thread.sleep은 스레드가 잠금을 해제하도록 허용하지 않습니다.
Thread.sleep 및 Object.wait는 현재 스레드를 일시 중지합니다. 차이점은 wait를 호출한 후 다른 스레드가 실행 시간을 할당한다는 것입니다. inform/notifyAll을 실행해야 CPU 실행 시간을 다시 얻을 수 있습니다.
컨텍스트 전환
싱글 코어 CPU(멀티 코어 CPU의 경우 이는 하나의 코어로 이해됨)의 경우 CPU는 한 번에 하나의 스레드만 실행할 수 있습니다. 스레드를 실행할 때 다른 스레드를 실행하기 위해 전환하는 것을 스레드 컨텍스트 전환이라고 합니다(프로세스에서도 마찬가지).
현재 스레드의 작업이 완료되지 않을 수 있으므로 전환 시 스레드의 실행 상태를 저장해야 다음에 다시 전환할 때 이전 상태로 계속 실행될 수 있습니다. 간단한 예를 들면 다음과 같습니다. 예를 들어 스레드 A가 파일의 내용을 읽고 있는데 파일의 절반을 읽고 있습니다. 이때 스레드 A는 일시 중지된 후 스레드 B를 실행하도록 전송되어야 합니다. 스레드를 실행하기 위해 다시 전환하는 경우 A는 다시 하지 않습니다. 스레드 A가 파일의 처음부터 다시 읽기를 바랍니다.
그래서 스레드 A의 실행 상태를 기록해야 하는데 어떤 데이터가 기록되나요? 다음 복구 이전에 현재 스레드가 어떤 명령을 실행했는지 알아야 하기 때문에 프로그램 카운터의 값을 기록해야 합니다. 또한 예를 들어 스레드가 특정 계산을 수행하는 동안 일시 중단된 경우 실행이 중단됩니다. 다음에 계속됩니다. 때로는 일시 중단되었을 때 변수의 값을 알아야 하므로 CPU 레지스터의 상태를 기록해야 합니다. 따라서 일반적으로 스레드 컨텍스트 전환 프로세스 중에 프로그램 카운터 및 CPU 레지스터 상태와 같은 데이터가 기록됩니다.
간단히 말하면, 스레드에 대한 컨텍스트 전환은 실제로 CPU 상태를 저장하고 복원하는 프로세스로, 스레드 실행이 중단 지점에서 실행을 재개할 수 있게 해줍니다.
虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。
1.静态方法
currentThread()方法
currentThread()方法可以返回代码段正在被哪个线程调用的信息。
public class Run1{ public static void main(String[] args){ System.out.println(Thread.currentThread().getName()); } }
2.sleep()方法
方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。
sleep方法有两个重载版本:
sleep(long millis) //参数为毫秒
sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。看下面这个例子就清楚了:
public class Test { private int i = 10; private Object object = new Object(); public static void main(String[] args) throws IOException { Test test = new Test(); MyThread thread1 = test.new MyThread(); MyThread thread2 = test.new MyThread(); thread1.start(); thread2.start(); } class MyThread extends Thread{ @Override public void run() { synchronized (object) { i++; System.out.println("i:"+i); try { System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态"); Thread.currentThread().sleep(10000); } catch (InterruptedException e) { // TODO: handle exception } System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束"); i++; System.out.println("i:"+i); } } } }
从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。
注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。
3.yield()方法
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
public class MyThread extends Thread{ @Override public void run() { long beginTime=System.currentTimeMillis(); int count=0; for (int i=0;i<50000000;i++){ count=count+(i+1); //Thread.yield(); } long endTime=System.currentTimeMillis(); System.out.println("用时:"+(endTime-beginTime)+" 毫秒!"); } }public class Run { public static void main(String[] args) { MyThread t= new MyThread(); t.start(); } }
执行结果:
用时:3 毫秒!
如果将 //Thread.yield();的注释去掉,执行结果如下:
用时:16080 毫秒!
对象方法
4.start()方法
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
5.run()方法
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
5.getId()
getId()的作用是取得线程的唯一标识
代码:
public class Test { public static void main(String[] args) { Thread t= Thread.currentThread(); System.out.println(t.getName()+" "+t.getId()); } }
输出:
main 1
6.isAlive()方法
方法isAlive()的功能是判断当前线程是否处于活动状态
代码:
public class MyThread extends Thread{ @Override public void run() { System.out.println("run="+this.isAlive()); } }public class RunTest { public static void main(String[] args) throws InterruptedException { MyThread myThread=new MyThread(); System.out.println("begin =="+myThread.isAlive()); myThread.start(); System.out.println("end =="+myThread.isAlive()); } }
程序运行结果:
begin ==falserun=trueend ==false
方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
有个需要注意的地方
System.out.println(“end ==”+myThread.isAlive());
虽然上面的实例中打印的值是true,但此值是不确定的。打印true值是因为myThread线程还未执行完毕,所以输出true。如果代码改成下面这样,加了个sleep休眠:
public static void main(String[] args) throws InterruptedException { MyThread myThread=new MyThread(); System.out.println("begin =="+myThread.isAlive()); myThread.start(); Thread.sleep(1000); System.out.println("end =="+myThread.isAlive()); }
则上述代码运行的结果输出为false,因为mythread对象已经在1秒之内执行完毕。
7.join()方法
在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
public class Thread4 extends Thread{ public Thread4(String name) { super(name); } public void run() { for (int i = 0; i < 5; i++) { System.out.println(getName() + " " + i); } } public static void main(String[] args) throws InterruptedException { // 启动子进程 new Thread4("new thread").start(); for (int i = 0; i < 10; i++) { if (i == 5) { Thread4 th = new Thread4("joined thread"); th.start(); th.join(); } System.out.println(Thread.currentThread().getName() + " " + i); } } }
执行结果:
main 0main 1main 2main 3main 4new thread 0new thread 1new thread 2new thread 3new thread 4joined thread 0joined thread 1joined thread 2joined thread 3joined thread 4main 5main 6main 7main 8main 9
由上可以看出main主线程等待joined thread线程先执行完了才结束的。如果把th.join()这行注释掉,运行结果如下:
main 0main 1main 2main 3main 4main 5main 6main 7main 8main 9new thread 0new thread 1new thread 2new thread 3new thread 4joined thread 0joined thread 1joined thread 2joined thread 3joined thread 4
8.其他方法
getName和setName
用来得到或者设置线程名称。
getPriority和setPriority
用来获取和设置线程优先级。
setDaemon和isDaemon
用来设置线程是否成为守护线程和判断线程是否是守护线程。
守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
在上面已经说到了Thread类中的大部分方法,那么Thread类中的方法调用到底会引起线程状态发生怎样的变化呢?下面一幅图就是在上面的图上进行改进而来的:
停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。
停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有以下3种方法可以终止正在运行的线程:
1.使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
2.使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
3.暂停线程
interrupt()方法
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
设置线程优先级有助于帮“线程规划器”确定在下一次选择哪一个线程来优先执行。
设置线程的优先级使用setPriority()方法,此方法在JDK的源码如下:
public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } }
在Java中,线程的优先级分为1~10这10个等级,如果小于1或大于10,则JDK抛出异常throw new IllegalArgumentException()。
JDK中使用3个常量来预置定义优先级的值,代码如下:
public final static int MIN_PRIORITY = 1;public final static int NORM_PRIORITY = 5;public final static int MAX_PRIORITY = 10;
1.线程优先级特性:
1)继承性
比如A线程启动B线程,则B线程的优先级与A是一样的。
2)规则性
高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
3)随机性
优先级较高的线程不一定每一次都先执行完。
在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。
守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)
在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)
不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。
1.同步代码块
在代码块上加上”synchronized”关键字,则此代码块就称为同步代码块
同步代码块格式
동기화(동기화된 개체){
동기화가 필요한 코드 블록
}
2. 동기화 메서드
코드 블록 외에도 메서드도 동기화할 수 있습니다.
메서드 동기화 형식
1
synchronized void 메서드 이름() {}
관련 기사:
관련 동영상:
Java 멀티스레딩 및 동시성 라이브러리 고급 애플리케이션 동영상 튜토리얼
위 내용은 Java 스레드 구현 및 일반적으로 사용되는 스레드 메소드 모음에 대한 제로 기반 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!