Java多线程入门
多线程是编程的一项核心技能,特别在需要处理大量并发任务下可以显著提高程序的性能和响应速度。Java作为一门成熟的编程语言,提供了丰富的多线程支持。本文会介绍从线程概念到线程安全问题的知识
一.什么是线程和多线程
线程是程序执行流的最小单元,是 CPU 调度和分派的基本单位。一个进程可以包含多个线程。往底层一点说,线程是CPU和内存之间提取代码的一个通道
多线程的优势
提高性能:在多核处理器上,多个线程可以并行执行,充分利用 CPU 资源,从而提高程序的执行效率。
增强响应性:在 GUI 程序中,使用多线程可以避免主线程被长时间的任务阻塞,从而保持界面的响应性。
二.Java多线程的实现
概念简单介绍一下即可,重点还是放在具体的实现上。
Java中实现多线程一般有四种方式:1.继承Thread类 2.实现Runnable接口 3.实现Callable接口 4.线程池。不过后三种方式事实上也是有Thread对象执行的,下面分别简单演示一下多线程的创建
1.继承Thread类
通过
extends Thread
创建子类并重写run
方法,直接调用start()
启动线程。示例
package a_thread; public class mythread extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } } } package a_thread; public class test01 { public static void main(String[] args) throws InterruptedException { mythread t1 = new mythread(); Runnable ruab = new ruab(); t1.start(); for (int i = 0; i < 10; i++) { Thread.yield(); System.out.println(Thread.currentThread().getName() + " " + i); } } }
这段代码中一共有两条线程,分别是t1
,t2
以及主线程(即main(CPU会特地为main方法的线程开一个主线程)
2.实现Runnable接口
创建Runnable的实现类,将
Runnable
对象传入Thread
的构造方法,由Thread
负责启动。
示例
package a_thread; public class ruab implements Runnable { @Override public void run() { for(int i = 0; i < 10; i++) { try { Thread.sleep(90); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + " " + i); } } } package a_thread; public class test01 { public static void main(String[] args) throws InterruptedException { Runnable ruab = new ruab(); Thread t1 = new Thread(ruab); t1.start(); for (int i = 0; i < 10; i++) { Thread.yield(); System.out.println(Thread.currentThread().getName() + " " + i); } } }
3.实现Callable接口
实现callable
接口创建线程对象需要重写call()
方法,call()
方法不同于run
,call()
方法是有返回值的,接收该返回值用到FutureTask<>
类,创建一个FutureTask<>
对象然后用该对象创建线程,具体代码如下
package callable; import java.util.concurrent.Callable; public class myCallable implements Callable<String> { @Override public String call() throws Exception { return "Hello World"; } } import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class pratice { public static void main(String[] args) throws ExecutionException, InterruptedException { myCallable task = new myCallable(); FutureTask<String> future = new FutureTask<>(task); Thread t = new Thread(future); t.start(); System.out.println(future.get()); } }
4.线程池
线程池可以循环利用线程对象,使用的时候直接拿线程对象,用完还回去即可,不必每次执行任务重新创建一条线程浪费内存
创建线程池对象需要用到Executors
类中的静态方法-static ExecutorService newFixedThreadPool(int nThreads)
参数是创建几条线程,返回值ExecutorService
是线程池,用于管理线程对象
执行线程任务需要用到ExecutorService
中的Future<?> submit(Runnable task)
或Future<T> submit(Callable<T> task)
方法,前者是Runnable
接口的实现对象,后者是Callable
接口实现对象,Runnable
中的执行方法run没有返回值,因此不需要参数接收。Callable
的call
则需要用Future
接口接收
线程池用完后需要用shutdown
关闭,不会接受任何新任务
示例代码
public class MyRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"执行了"); } } public class MyRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"执行了"); } } public class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { return 1; } } public class Test02 { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService es = Executors.newFixedThreadPool(2); Future<Integer> future = es.submit(new MyCallable()); System.out.println(future.get()); } }
三.多线程安全问题
当多个线程访问同一个资源时,就可能导致线程不安全。例如小白,小红,小黑三个人同时买车票,如果不进行线程安全处理,就可能一张票被多个人买,问题代码如下,大家可以自己跑一下这个代码
package ticket; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class myticket implements Runnable { static int ticket = 100; @Override public void run() { method(); } public void run{ while (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票"); ticket--; } } }
package ticket; public class buyTicket { public static void main(String[] args) { myticket t = new myticket(); Thread t1 = new Thread(t, "小红"); Thread t2 = new Thread(t, "小白"); Thread t3 = new Thread(t, "小黑"); t1.start(); t2.start(); t3.start(); } }
这是因为一个线程尚未结束时另一个线程有进行访问,从而出现问题,这种情况下就需要用到synchronized()
方法来进行线程同步,方法参数可以是任意类型的对象。可以理解为,当在synchronized()
代码块中,该线程一直拿着这把锁,别的线程进不来,只有出了synchronized()
代码块所有线程才会一起继续抢夺这个钥匙,这样就只会出现一个人买多张票,但一张票不会被多个人买,代码如下
package ticket; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class myticket implements Runnable { static int ticket = 100; Object lock = new Object(); @Override public void run() { method(); } public void method() { while (ticket > 0) { synchronized (lock) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票"); ticket--; } } } }
类似地,还有lock
也可以达到对应效果。一个线程进入后上锁,完成代码后用unlock
解锁
package ticket; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class myticket implements Runnable { static int ticket = 100; @Override public void run() { method(); } Lock lock = new ReentrantLock(); public void method() { while (ticket > 0) { lock.lock(); try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票"); ticket--; lock.unlock(); } } }
总结
本文介绍了 Java 多线程的基础知识,包括线程和多线程的概念、创建线程的方式以及线程同步等内容。通过学习这些基础知识,你可以开始编写简单的多线程程序。在实际开发中,多线程编程还涉及到更多的高级知识,如线程池、并发集合等,需要进一步深入学习