ホームページ >Java >&#&チュートリアル >Java スレッドの実装についてのゼロベースの理解と、スレッドの一般的に使用されるメソッドのコレクション
スレッドとプロセスの違いは何ですか?回答: プロセスは独立した実行環境であり、プログラムまたはアプリケーションとみなすことができます。スレッドはプロセス内で実行されるタスクです。スレッドはプロセスのサブセットであり、プロセスには多数のスレッドを含めることができ、各スレッドは異なるタスクを並行して実行します。異なるプロセスは異なるメモリ空間を使用し、すべてのスレッドは同じメモリ空間を共有します。
1) スレッド: プロセス内でプログラムの実行を担当する実行ユニット
スレッド自体は実行するプログラムに依存します
スレッドはプログラム内のシーケンシャルな制御フローであり、次のことができますプログラムと環境に割り当てられたリソースのみを使用します
2) プロセス: 実行中のプログラム
プロセスには少なくとも 1 つのスレッドが含まれます
3) シングルスレッド: プログラムにはスレッドが 1 つだけあり、実際、メイン メソッドはメインスレッド
4) マルチスレッド: プログラム内で複数のタスクを実行する目的は、CPU リソースを有効に活用することです。 2. スレッド実装
class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动创建的第"+num+"个线程"); } }
独自のスレッド クラスを作成した後、スレッド オブジェクトを作成し、start() メソッドを通じてスレッドを開始できます。 run() メソッドは、スレッドを開始するために呼び出されるわけではないことに注意してください。 run メソッドが呼び出される場合、それはメインスレッドで run メソッドを実行することと同じになります。現時点では、通常のメソッド呼び出しと何の違いもありません。定義されたタスクを実行するために新しいスレッドが作成されます。 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+"个线程");
}
}
上記のコードでは、start()メソッドを呼び出すことで新しいスレッドが作成されます。 start() メソッド呼び出しと run() メソッド呼び出しの違いを区別するには、次の例を参照してください:
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) thread1 の start メソッドの呼び出しは thread2 の run メソッドの前に呼び出されますが、thread2 の run メソッド呼び出しに関連する情報が最初に出力され、新しいスレッドの作成プロセスがブロックされないことを示します。その後のメインスレッドの実行。
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());
}
}
Runnable の中国語の意味はその名前が示すように、Runnable インターフェイスを実装することでサブタスクを定義し、そのサブタスクを Thread に渡して実行します。このメソッドは、Thread クラスのパラメータとして Runnable を使用し、次に Thread の start メソッドを使用して、サブタスクを実行するための新しいスレッドを作成する必要があることに注意してください。 Runnable の run メソッドを呼び出した場合、この通常のメソッド呼び出しでは新しいスレッドは作成されません。
実際、Threadクラスの実装ソースコードを見ると、ThreadクラスがRunnableインターフェースを実装していることがわかります。
Java では、これら 2 つのメソッドを使用してサブタスクを実行するスレッドを作成できます。どちらのメソッドを選択するかは、ユーザー自身のニーズによって異なります。 Thread クラスを直接継承することは、Runnable インターフェイスを実装するより簡単に見えるかもしれませんが、Java では単一の継承しか許可されないため、カスタム クラスが他のクラスを継承する必要がある場合、Runnable インターフェイスの実装を選択することしかできません。
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()
キャッシュ可能なスレッド プールを作成します。execute を呼び出すと、以前に構築されたスレッドが再利用されます (スレッドが使用可能な場合)。使用可能な既存のスレッドがない場合は、新しいスレッドが作成され、プールに追加されます。 60 秒間使用されなかったスレッドを終了し、キャッシュから削除します。
public static ExecutorService newSingleThreadExecutor()
シングルスレッド Executor を作成します。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
スケジュールされた定期的なタスクの実行をサポートするスレッド プールを作成します。これは、ほとんどの場合、Timer クラスの置き換えに使用できます。
ExecutoreService は、Callable または Runnable を渡して Future を返す submit() メソッドを提供します。 Executor バックグラウンド スレッド プールが Callable の計算を完了していない場合、この呼び出しは Future オブジェクトの get() メソッドを返し、計算が完了するまでブロックされます。
Thread クラスの特定のメソッドを正式に学習する前に、まずスレッドのステータスを理解しましょう。これは、後で Thread クラスのメソッドを理解するのに役立ちます。
1) 作成 (新しい) 状態: マルチスレッド オブジェクトの準備ができています
2) 準備完了 (実行可能) 状態: start() メソッドが呼び出され、CPU がスケジュールするのを待ちます
3) 実行中 (実行中) 状態: 実行run( )メソッド
4) ブロック状態: 実行を一時的に停止し、リソースを他のスレッドに渡して使用する場合があります
5) 終了 (デッド) 状態: スレッドが破棄されます
サブタスクを実行するために新しいスレッドを開始する必要がある場合、スレッドが作成されます。ただし、スレッドの実行にはいくつかの条件 (メモリ リソースなど) が必要であるため、スレッドの作成後、すぐに準備完了状態にはなりません。JVM メモリ領域分割に関する前回のブログ投稿で、プログラム カウンター、 Java スタックとローカル メソッド スタックはすべてスレッドに対してプライベートであるため、スレッドの実行に必要なすべての条件が満たされた場合にのみ、スレッドに一定量のメモリ領域を割り当てる必要があります。州。
スレッドが準備完了状態になっても、CPU 実行時間がすぐに取得できるわけではありません。おそらく、この時点で CPU は他の処理を実行しているため、待機する必要があります。 CPU 実行時間が取得されると、スレッドは実際に実行状態になります。
スレッドの実行中に、現在のスレッドが実行を継続しない理由は複数考えられます。たとえば、ユーザーがスレッドを積極的にスリープ状態にした (一定時間スリープした後に再実行した)。ユーザーが積極的にスレッドを待機させるか、同期ブロックによって指定されます。ブロッキングは、現時点では、待機中 (スリープまたは特定のイベントを待っている)、待機中 (目覚めるのを待っている)、ブロック済み (ブロックされている) の複数の状態に対応します。
突然の中断やサブタスクの実行が完了した場合、スレッドは強制終了されます。
次の図は、スレッドの作成から消滅までの状態を示しています:
一部のチュートリアルでは、ブロック、待機、時間待機を総称してブロック状態と呼びます。これも可能ですが、ここではブロック状態を参照したいと思います。スレッドの状態は Java のメソッド呼び出しにリンクされているため、待機と時間待機の 2 つの状態は分離されます。
注: sleep と wait の違い:
sleep は Thread クラスのメソッドであり、wait は Object クラスで定義されたメソッドです。現在のスレッドが所有している場合、ロックの動作は変更されません。ロックの場合、Thread.sleep はスレッドにロックを解放させません。
Thread.sleep と Object.wait はどちらも現在のスレッドを一時停止します。 違いは、wait を呼び出した後、他のスレッドに実行時間を割り当てることです。スレッドは、notify/notifyAll を実行する必要があります。その場合のみ、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”关键字,则此代码块就称为同步代码块
同步代码块格式
synchronized(同期されたオブジェクト){
同期する必要があるコード ブロック
}
2. 同期方法
コード ブロックに加えてメソッドも同期できます
メソッドの同期形式
1
synchronized void メソッド名 () {}
関連記事:
関連ビデオ:
Java マルチスレッドおよび同時実行ライブラリの高度なアプリケーションのビデオ チュートリアル
以上がJava スレッドの実装についてのゼロベースの理解と、スレッドの一般的に使用されるメソッドのコレクションの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。