初识Java多线程

编程

  • 进程与线程

1、进程
进程是系统进行资源分配和调度的一个独立单位;     
动态产生、动态消亡;
具有并发性; 
    并行指的是同一时刻,多个指令在多台处理器上同时运行。
    并发指的是同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,看起来就好像多个指令同时执行一样。

进程由程序、数据和进程控制块三部分组成。

2、线程
是进程中的一个实体,是被系统调度的基本单位;
不拥有系统资源,与同个进程的其他线程共享资源;
同一进程中的多个线程之间可以并发执行;
多线程:在单个程序中同时运行多个线程完成不同的工作;
与jvm共同消亡;

  • 线程的生命周期

关于Java中线程的生命周期,首先看一下下面这张较为经典的图:

1、新建状态:当线程对象创建后,即进入新建状态  Thread t = new MyThread()
2、就绪状态:当线程对象调用start()方法,即进入就绪状态。这意味着CPU随时可以对该线程进行调度执行
3、运行状态:当CPU开始调度处于就绪状态的线程,该线程开始执行,即进入运行状态。要进入执行状态之前必须是就绪状态
4、阻塞状态:处于运行状态当线程由于某种原因放弃对cpu的使用权,停止执行,即进入阻塞状态。
    阻塞状态分以下三种:
        a、等待阻塞 -- 线程执行wait()方法,释放锁
        b、同步阻塞 -- 线程获取同步锁失败
        c、其他阻塞 -- 
调用线程的sleep()或join()或发出了I/O请求时
5、死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期

  • 创建多线程的方式
    常用的实现多线程的两种方式
    Callable、Future和FutureTask

 1、继承Thread类,重写run()方法

package com.demo.test;

public class MyThread extends Thread {

private String name;

public MyThread(String name){

this.name = name;

}

@Override

public void run() { //线程执行体

for (int i = 0; i < 10; i++) {

System.out.println(name + "运行 :" + i);

}

}

}

package com.demo.test;

public class ThreadTest {

public static void main(String[] args) {

Thread myThread1 = new MyThread("A"); // 创建一个新的线程 myThread1 此线程进入新建状态

Thread myThread2 = new MyThread("B"); // 创建一个新的线程 myThread2 此线程进入新建状态

myThread1.start(); // 调用start()方法使得线程进入就绪状态

myThread2.start(); // 调用start()方法使得线程进入就绪状态

}

}

运行结果:

A运行  :0

B运行 :0

B运行 :1

B运行 :2

B运行 :3

B运行 :4

B运行 :5

B运行 :6

B运行 :7

B运行 :8

A运行 :1

A运行 :2

A运行 :3

B运行 :9

A运行 :4

A运行 :5

A运行 :6

A运行 :7

A运行 :8

A运行 :9

2、实现Runnable接口,重写run()方法
要注意的是:创建Runnable实现类的实例,作为Thread类的构造参数来创建Thread对象,这个Thread对象才是真正的线程对象

package com.demo.test;

public class MyRunnable implements Runnable{

private String name;

public MyRunnable(String name){

this.name = name;

}

@Override

public void run() {

for (int i = 0; i < 10; i++) {

System.out.println(name + "运行 :" + i);

}

}

}

package com.demo.test;

public class ThreadTest {

public static void main(String[] args) {

Runnable myRunnable = new MyRunnable("A"); // 创建一个Runnable实现类的对象

Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程

Runnable myRunnable1 = new MyRunnable("B");

Thread thread2 = new Thread(myRunnable1);

thread1.start(); // 调用start()方法使得线程进入就绪状态

thread2.start();

}

}

运行结果:

A运行  :0

B运行 :0

B运行 :1

B运行 :2

A运行 :1

A运行 :2

B运行 :3

A运行 :3

A运行 :4

B运行 :4

A运行 :5

A运行 :6

A运行 :7

B运行 :5

A运行 :8

A运行 :9

B运行 :6

B运行 :7

B运行 :8

B运行 :9

3、使用Callable和Future接口创建线程【有返回值】
创建Callable接口的实现类,实现call()方法,使用FutureTask类来包装Callable对象,以FutureTask对象来作为Thread类的构造参数来创建Thread对象

package com.demo.test;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer>{

// 与run()方法不同的是,call()方法具有返回值

@Override

public Integer call() throws Exception{

System.out.println("子线程在进行计算");

Thread.sleep(3000);

int sum = 0;

for (int i = 0; i < 100; i++) {

//System.out.println(Thread.currentThread().getName() + " " + i);

sum += i;

}

return sum;

}

}

package com.demo.test;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

public class ThreadTest {

public static void main(String[] args) {

Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象

FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象

//FutureTask = Runnable【作为thread的初始化参数】 + Future【获取返回值】

Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程

thread.start(); //线程进入到就绪状态

try {

Thread.sleep(1000);

} catch (InterruptedException e1) {

e1.printStackTrace();

}

System.out.println("主线程在执行任务");

try {

int sum = ft.get(); //取得新创建的线程中的call()方法返回的结果

//call()未执行完 get方法一直阻塞

System.out.println("task运行结果,sum = " + sum);

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

System.out.println("所有任务执行完毕");

}

}

运行结果:

子线程在进行计算

主线程在执行任务

task运行结果,sum = 4950

所有任务执行完毕

  • 线程调度方法

1、join()-- 在当前线程中调用另一个线程的join()方法,当前线程进入阻塞状态,直到另一个线程执行结束,当前线程从阻塞转就绪状态;
    使用场景:当前线程需要用到另一个线程的计算结果
2、 sleep()-- 运行状态进入到阻塞状态,指定睡眠时间,超时后该线程进入就绪状态。注意,sleep方法不会释放锁
3、yield()-- 为了让相同优先级线程轮流执行,线程让步可以让该线程从运行状态进入就绪状态,相同优先级线程再获取执行,不保证下一个运行线程不是当前线程。注意,yield方法不会释放锁
4、interrupt()-- 通过判断线程的中断标记来控制线程

  • 线程安全与线程同步

1、为什么需要保证线程安全?
    多线程环境下对共享资源的访问可能会引起此共享资源的不一致性。因此,为避免线程安全问题,应该避免多线程环境下对此共享资源的并发访问。
2、为什么需要线程同步?
     java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
3、同步
    a、 同步方法 -- 方法定义中加上synchronized关键字修饰

public synchronized void run() {

// ....

}

    b、同步代码块 

synchronized (obj) {

//...

}

关于synchronized关键字的说明:
 ① 原理
    在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。当前线程调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj),当前线程就获取了“obj这个对象”的同步锁。
    不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。
② 基本规则
第一条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条:当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
③ 实例锁和全局锁
实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。实例锁对应的就是synchronized关键字。
全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
就是说,一个非静态方法上的synchronized关键字,代表该方法依赖其所属对象。一个静态方法上synchronized关键字,代表该方法依赖这个类本身。

  • 线程通信

1、wait():导致当前线程等待并使其进入到等待阻塞状态。直到其他线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程。
2、notify():唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操作,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
3、notifyAll():唤醒在此同步锁对象上等待的所有线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。

  • 线程优先级和守护线程

1、 java中的线程优先级的范围是1~10,默认的优先级是5。
    每个线程默认的优先级都与创建它的父线程具有相同的优先级。
    默认情况下,mian线程具有普通优先级。“高优先级线程”会优先于“低优先级线程”执行。
    Thread提供了setPriority(int newPriority)和getPriority()方法来设置和返回线程优先级。
    Thread类有3个静态常量:

MAX_PRIORITY = 10

MIN_PRIORITY = 1

NORM_PRIORITY = 5

 2、 java 中有两种线程:用户线程和守护线程。
    可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
    用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。
    需要注意的是:Java虚拟机在“用户线程”都结束后会后退出。

将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意一下几点:

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

(2) 在Daemon线程中产生的新线程也是Daemon的。

(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

 

以上是 初识Java多线程 的全部内容, 来源链接: utcz.com/z/511085.html

回到顶部