Java 多线程
一、线程与进程
1、程序:是为了让计算机执行某些操作或解决某个问题而编写的一系列的有序指令集合。
2、进程:是程序的一次执行过程,是系统运行运行一个程序即是一个进程从创建、运行到消亡的过程。
3、线程(thread):比进程更小的运行单位,是程序中单个顺序的流控制。一个进程可以包含多个线程。
4、线程与进程的区别
- 地址空间: 同一进程的所有线程共享本进程的地址空间,而不同的进程之间的地址空间是独立的。
- 资源拥有: 同一进程的所有线程共享本进程的资源,如内存,CPU,IO等。进程之间的资源是独立的,无法共享。
- 执行过程:每一个进程可以说就是一个可执行的应用程序,每一个独立的进程都有一个程序执行的入口,顺序执行序列。但是线程不能够独立执行,必须依存在应用程序中,由程序的多线程控制机制进行控制。
- 健壮性: 因为同一进程的所以线程共享此线程的资源,因此当一个线程发生崩溃时,此进程也会发生崩溃。 但是各个进程之间的资源是独立的,因此当一个进程崩溃时,不会影响其他进程。因此进程比线程健壮。
- 线程执行开销小,但不利于资源的管理与保护。
- 进程的执行开销大,但可以进行资源的管理与保护。进程可以跨机器前移。
二、java线程的三种创建方式
1、继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run()方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
public class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程开始运行了:"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("子线程1:");
t1.start();
MyThread1 t2 = new MyThread1();
t2.setName("子线程2");
t2.start();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("这是一个main方法"+Thread.currentThread().getName());
}
}
}
2、通过Runnable接口创建线程类
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
Thread.currentThread()方法返回当前正在执行的线程对象。getName()方法返回调用该方法的线程的名字。
public class MyThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
System.out.println("子线程开始运行了:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//启动子线程
MyThread2 t = new MyThread2();
Thread t1 = new Thread(t);
t1.setName("子线程1:");
t1.start();
Thread t2 = new Thread(t);
t2.setName("子线程2:");
t2.start();
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
System.out.println("主线程开始运行了:"+ Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3、通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
public interface Callable
{
V call() throws Exception;
}
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。)
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
三、继承Thread类与实现Runnable接口的区别与优势
1、区别
- Runnable接口中只有一个未实现的run()方法,实现该接口的类必须重写该方法。
- Runnable接口必须实现run()方法,而Thread类中的run() 方法是一个空方法,可以不重写。
- Runnable接口的实现类并不是真正的线程类,只是线程运行的目标类。要想以线程的方式执行run()方法,必须依靠Thread类。
- Runnable接口适合线程共享。
2、实现 Runnable 接口比继承 Thread 类所具有的优势:
- 适合多个相同的程序代码的线程去处理同一个资源(共享)
- 可以避免 java 中的单继承的限制
- 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
- 线程池只能放入实现 Runable 或 callable 类线程,不能直接放入继承 Thread 的类。
四、线程中常用的方法与关键字
名称 | 说明 |
run() | 线程运行时执行的的代码,通常在子类中重写它 |
start() | 启动一个新的线程,然后虚拟机调用新线程的run方法 |
Thread.currentThread() | 方法返回当前正在执行的线程对象。 |
getName() | 方法返回调用该方法的线程的名字。 |
sleep() | 用于让当前线程暂时休眠一段时间,参数millis的单位是毫秒 |
synchronized(this) | 表示同步代码块,叫锁, this表示当前对象,确保 |
notify() | 唤醒线程 |
wait() | 让当前执行线程暂停 |
join() | 哪个线程执行这句代码,哪个线程就被阻塞。 |
yield() | 用了yield方法后,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行) |
五、解决并发问题
例子1:用多线程模拟火车票 2 个窗口同时卖票 100 张,解决并发问题(一票多卖),(线程1和线程2会相互抢资源),加同步锁synchronized关键字
public class ThreadTicket implements Runnable {
int num = 100; //预投放100张票
@Override
public void run() {
while(true) {
synchronized (this){ //synchronized表示同步代码块,叫锁, this表示当前对象
if (num>0){
try {
Thread.sleep(100);//注意:休眠时间太小,一个线程会抢完所有资源
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了一张票号为:sz-xz2021042900"+num+",剩下:"+(--num));
}else {
break;
}
}
}
}
public static void main(String[] args) {
ThreadTicket t = new ThreadTicket();
Thread t1 = new Thread(t);
t1.setName("A窗口:");
t1.start();
Thread t2 = new Thread(t);
t2.setName("B窗口:");
t2.start();
}
}
例子2:用多线程模拟火车票 2 个窗口同时卖票 100 张,要求每次每个窗口只能卖一张票,2 个窗口轮流卖。(线程1和线程2交替执行)
方法notify() wait()
public class ThreadTicket implements Runnable {
int num = 100; //假设有100张免费的去西藏的卧铺票;
@Override
public void run() {
while (true){
//只要有票就循环抢!
synchronized (this){
//同步代码块;
notify(); //通知对方,我进来了,你在外面等待;
if (num>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了一张票号为:sz-xz2021042900"+num+",剩下:"+(--num));
}else {
break; //票为0,所以退出循环;
}
try {
wait(); //我出去了,你可以进来了!!!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ThreadTicket t = new ThreadTicket();
Thread t1 = new Thread(t);
t1.setName("A窗口:");
t1.start();
Thread t2 = new Thread(t);
t2.setName("B窗口:");
t2.start();
}
例3:两个线程,打印1-100,10的倍数让权,关键字yield
public class Threadyield extends Thread {
Threadyield(String s) {
super(s);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+ ":" + i);
if (i % 10 == 0) {
yield(); //运行到10的倍数让权cpu
}
}
}
public static void main(String[] args) {
Threadyield th1 = new Threadyield("t1");
Threadyield th2 = new Threadyield("t2");
th1.start();
th2.start();
}
}
例4、分苹果多线程 关键字
public class AppleThread implements Runnable {
static int app_num = 5;
@Override
public void run() {
while(true) {
synchronized (this) {
notify();
if (app_num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"分了一个苹果,还剩下"+(--app_num)+"苹果");
} else {
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
AppleThread app = new AppleThread();
Thread t1 = new Thread(app);
t1.setName("小黑");
t1.start();
Thread t2 = new Thread(app);
t2.setName("小红");
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("小黑的线程进入"+(t2.isAlive()==true?"非死亡状态":"死亡状态"));
System.out.println("小红的线程进入"+(t1.isAlive()==true?"非死亡状态":"死亡状态"));
}
}
例5、多线程计算1-300的累加和,线程1计算1-100,线程2计算101-200,线程3计算201-300 关键字join
public class ThreadSum implements Runnable{
public int sum = 0;
private int start;
private int end;
public ThreadSum(int start, int end){
this.start = start;
this.end = end;
}
@Override
public void run() {
for (int i = start; i <= end; i++) {
sum+=i;
}
System.out.println("sum = " + sum);
}
public static void main(String[] args) {
ThreadSum threadSum1 = new ThreadSum(1,100);
ThreadSum threadSum2 = new ThreadSum(101,200);
ThreadSum threadSum3 = new ThreadSum(201,300);
Thread t1 = new Thread(threadSum1);
Thread t2 = new Thread(threadSum2);
Thread t3 = new Thread(threadSum3);
t1.start();
t2.start();
t3.start();
try{
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e){
e.printStackTrace();
}
int a = threadSum1.sum;
int b = threadSum2.sum;
int c = threadSum3.sum;
int all = a + b + c;
System.out.println(all);
}
}
例6、设计 4 个线程:其中 2 个线程每次对 m 加 1,另 外 2 个线程对 m 每次减 1。因为要优先调用前面两个线程,所以需要使用 join()
//MyData.java类
public class MyData {
private int m = 0;
//同步方法
public synchronized void add(){
for (int i = 0; i < 10; i++) {
System.out.print(++m+"\t");
}
System.out.println("");
}
//同步方法
public synchronized void minus(){
for (int i = 0; i < 10; i++) {
System.out.print(--m+"\t");
}
System.out.println("");
}
}
//Threadadd.java类
public class Threadadd extends Thread{
private MyData data;
public Threadadd(MyData data) {
this.data = data;
}
@Override
public void run() {
this.data.add();
}
}
//Threadminus.java类
public class Threadminus extends Thread{
private MyData data;
public Threadminus(MyData data) {
this.data = data;
}
@Override
public void run() {
this.data.minus();
}
}
//main函数
public static void main(String[] args) {
MyData data = new MyData();
Threadadd t1 = new Threadadd(data);
Threadadd t2 = new Threadadd(data);
Threadminus t3 = new Threadminus(data);
Threadminus t4 = new Threadminus(data);
t1.start();
t2.start();
try {
t1.join(); //当前线程先执行
t2.join();//当前线程先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t3.start();
t4.start();
}