这个系列开始来讲解 Java 多线程的知识,这节就先讲解多线程的基本知识。
进程就是在运行过程中的程序,就好像手机运行中的微信,QQ,这些就叫做进程。
线程就是进程的执行单元,就好像一个音乐软件可以听音乐,下载音乐,这些任务都是由线程来完成的。
Java 中创建线程的方法有三种,以下来逐一详细讲解。
使用继承 Thread 类创建线程的步骤如下:
class MyThread extends Thread{
@Override
public void run() {
super.run();
Log.i(TAG,"线程运行了")
}
}
MyThread myThread = new MyThread();
myThread.start();
最后运行打印出来的结果是:
线程运行了
使用继承 Thread 类创建线程的步骤如下:
代码举例如下:
class MyRunnable implements Runnable{
@Override
public void run() {
Log.i(TAG,"线程运行了")
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
最后运行打印出来的结果是:
线程运行了
和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
》call()方法可以有返回值
》call()方法可以声明抛出异常
代码实现::
FutureTask task = new FutureTask<>(new Callable() {
@Override
public Integer call() throws Exception {
return 0;
}
});
new Thread(task,"有返回值的线程").start();
try{
System.out.println("子线程的返回值:"+task.get());//get()方法会阻塞,直到子线程执行结束才返回
}catch (Exception e){
}
最后运行打印出来的结果是:
子线程的返回值:0
实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:
注:一般推荐采用实现接口的方式来创建多线程
当一个线程开启之后,它会遵循一定的生命周期,它要经过新建,就绪,运行,阻塞和死亡这五种状态,理解线程的生命周期有助于理解后面的相关的线程知识。
首先通过一张图片来加强记忆 图片来源百度百科
生命周期的五种状态
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用notify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)
void run() 创建该类的子类时必须实现的方法
void start() 开启线程的方法
static void sleep(long t) 释放CPU的执行权,不释放锁
static void sleep(long millis,int nanos)
final void wait()释放CPU的执行权,释放锁
final void notify()唤醒线程
final void notifyAll()唤醒线程
static void yied()可以对当前线程进行临时暂停(让线程将资源释放出来)
需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep()方法不会释放“锁标志”,也就是说如果有synchronized同步块,其他线程仍然不能访问共享数据。
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyThread thread2 = new MyThread("t1");
thread2.start();// 启动线程
for (int i = 0; i <= 5; i++) {
System.out.println("I am main Thread");
}
}
class MyThread extends Thread {
MyThread(String s) {
super(s);
}
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(getName() + ":" + i);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
wait()方法需要和notify()及notifyAll()两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用,也就是说,调用wait(),notify()和notifyAll()的任务在调用这些方法前必须拥有对象的锁。注意,它们都是Object类的方法,而不是Thread类的方法。
wait()方法与sleep()方法的不同之处在于,wait()方法会释放对象的“锁标志”。当调用某一对象的wait()方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了notify()方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的notifyAll()方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。
除了使用notify()和notifyAll()方法,还可以使用带毫秒参数的wait(long timeout)方法,效果是在延迟timeout毫秒后,被暂停的线程将被恢复到锁标志等待池。
此外,wait(),notify()及notifyAll()只能在synchronized语句中使用,但是如果使用的是ReenTrantLock实现同步,该如何达到这三个方法的效果呢?解决方法是使用ReenTrantLock.newCondition()获取一个Condition类对象,然后Condition的await(),signal()以及signalAll()分别对应上面的三个方法。
暂停当前正在执行的线程对象。
yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
yield()只能使同优先级或更高优先级的线程有执行的机会。
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
MyThread3 t1 = new MyThread3("t1");
/* 同时开辟了两条子线程t1和t2,t1和t2执行的都是run()方法 */
/* 这个程序的执行过程中总共有3个线程在并行执行,分别为子线程t1和t2以及主线程 */
MyThread3 t2 = new MyThread3("t2");
t1.start();// 启动子线程t1
t2.start();// 启动子线程t2
for (int i = 0; i <= 5; i++) {
System.out.println("I am main Thread");
}
}
class MyThread3 extends Thread {
MyThread3(String s) {
super(s);
}
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(getName() + ":" + i);
if (i % 2 == 0) {
yield();// 当执行到i能被2整除时当前执行的线程就让出来让另一个在执行run()方法的线程来优先执行
/*
* 在程序的运行的过程中可以看到,
* 线程t1执行到(i%2==0)次时就会让出线程让t2线程来优先执行
* 而线程t2执行到(i%2==0)次时也会让出线程给t1线程优先执行
*/
}
}
}
}
}
等待该线程终止。
等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
package concurrent;
public class TestJoin {
public static void main(String[] args) {
Thread thread = new Thread(new JoinDemo());
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程第" + i + "次执行!");
if (i >= 2)
try {
// t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class JoinDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程1第" + i + "次执行!");
}
}
}
大家都有了以上相同的教程,但是人和人之间的区别在于:别人有颗更强大的内心,可怕的是比你聪明的人比你还要更努力!!
当你下定决心,准备前行的时候,剩下的只有坚持了。。。
如果大家觉得我写的还可以的话,请关注我的微信公众号: