线程与进程
- 进程是程序的一次动态执行,是系统进行资源分配和调度的基本单位,是操作系统运行的基础,通常每一个进程都拥有自己独立的内存空间和系统资源
- 线程是程序运行的执行单元,依托于进程存在。一个进程中可以包含多个线程,多线程可以共享一块内存空间和一组系统资源
多进程
特点:内存隔离,单个进程的异常不会导致整个应用的崩溃,方便调试;但是进程间调用、通信和切换的开销大
常使用在目标子功能间交互少的场景,弱相关性的、可扩展到多机分布(Nginx 负载均衡)的场景
多线程
特定:提高了系统的并行性,开销小;但是没有内存隔离,单个线程崩溃会导致整个应用的退出,定位不方便
常使用在存在大量 I/O,网络等耗时操作,或者需要与用户进行交互,频繁创建和销毁的 Web 服务、大量计算的、强相关性的、多核分布(多核 CPU)的场景
线程的状态
新建状态(New)
当用 new 操作符创建一个线程时, 例如 new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新建状态时,程序还没有开始运行线程中的代码。就绪状态(Runnable)
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的 start() 方法。当线程对象调用 start() 方法即启动了线程,start() 方法创建线程运行的系统资源,并调度线程运行 run() 方法。当 start() 方法返回后,线程就处于就绪状态。
处于就绪状态的线程并不一定立即运行 run() 方法,线程还必须同其他线程竞争 CPU 时间,只有获得 CPU 时间才可以运行线程。因为在单 CPU 的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。运行状态(Running)
当线程获得 CPU 时间后,它才进入运行状态,真正开始执行 run() 方法-
阻塞状态(Blocke)
阻塞状态是正在运行的线程没有运行结束,暂时让出 CPU,这时其他处于就绪状态的线程就可以获得 CPU 时间,进入运行状态。线程运行过程中,可能因为各种原因进入阻塞状态:- 线程通过调用 sleep 方法进入睡眠状态
- 线程调用一个在 I/O 上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者
- 线程试图得到一个锁,而该锁正被其他线程持有
- 线程在等待某个触发条件
- ......
-
死亡状态(Dead)
有两个原因会导致线程死亡:- run方法正常退出而死亡
- 某个未捕获的异常终止了 run 方法而使线程猝死
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用 isAlive 方法。如果是可运行或被阻塞,这个方法返回 true; 如果线程仍旧是 new 状态且不是可运行的, 或者线程死亡了,则返回 false
线程的创建方式
继承 Thread 类
/**
* 如果调用run()方法,程序会先执行run()方法,然后再去执行主路径main方法
* 如果调用start()方法,主线程main方法和run()方法会并行交替执行
*
*线程开启不一定立即执行,有CPU调度执行
*/
//创建线程的方式一:继承Thread类,重写run()方法,调用start方法开启线程
public class TestThread1 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("Thread学习---");
}
}
//main也是一个线程
public static void main(String[] args) {
//创建一个线程对象
TestThread1 testThread1 = new TestThread1();
//调用start()方法开启线程
testThread1.start();
//testThrea1.run();
for (int i = 0; i < 1000; i++) {
System.out.println("main方法---" + i);
}
}
}
输出部分截图:
可以发现,当调用start方法时,主线程main和run()会交替执行
如果调用run()方法,会先执行run(),执行完毕后再执行主线程main
实现 Runnable 接口
//创建线程方式2:实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口的实现类。调用start()
public class TestThread2 implements Runnable{
private String name;
public TestThread2(String name){
this.name = name;
}
@Override
public void run() {
//run方法线程体
System.out.println("线程名---" + name);
}
public static void main(String[] args) {
TestThread2 threadA = new TestThread2("线程A");
TestThread2 threadB = new TestThread2("线程B");
TestThread2 threadC = new TestThread2("线程C");
new Thread(threadA).start();
new Thread(threadB).start();
new Thread(threadC).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main方法---" + i);
}
}
}
输出部分截图:
与继承Thread不同
实现Runnable接口在调用start()方法时
//需要先创建Runnable接口的实现类
TestThread2 testThread2 = new TestThread2();
//然后再创建线程对象,调用start()方法
Thread thread2 = new Thread(testThread2);
thread2.start();
//或者可以简写
TestThread2 testThread2 = new TestThread2();
new Thread(testThread2).start();
第一种方法直接继承 Thread 类,其实 Thread 也实现了 Runnable 接口
实现 Callable 接口
- 实现 Callable 接口
- 重写 call 方法,需要返回值类型
- main方法需要抛出异常
import java.util.concurrent.*;
//线程创建方式3:实现Callable接口
public class TestThread3 implements Callable {
@Override
public Boolean call() {
//重写call方法,需要返回值
for (int i = 0; i < 20; i++) {
System.out.println("Thread学习---");
}
return true;
}
//main方法需要抛出异常
public static void main(String[] args) throws Exception {
TestThread3 t1 = new TestThread3();
FutureTask<Boolean> result1 = new FutureTask(t1);
new Thread(result1).start();
//返回值
System.out.println(result1.get());
}
}
输出部分截图:
多线程——龟兔赛跑
public class Race implements Runnable {
//胜利者
private static String winner;
//重写run()方法
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子休息,每跑10布休息一会
if (Thread.currentThread().getName().equals("兔子") && (i%10 == 0) && (i != 0) ){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
//如果比赛结束,则停止程序
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName() + "-->跑了" + i);
}
}
//判断是否完成比赛
private boolean gameOver(int steps) {
//判断是否有胜利者
if (winner != null) {
return true;
} else if (steps >= 100) {
winner = Thread.currentThread().getName();
System.out.println("winner is" + winner);
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
模拟多人抢票
//多个线程同时操作同一个对象
//买火车票的例子
//多个线程操作同一个资源的情况下,线程不安全,数据紊乱
public class TestThread4 implements Runnable{
//票数
private int ticketNUm = 10;
boolean flag = true;
@Override
public void run() {
//买票
while (flag){
buy();
}
}
private void buy(){
//判断是否有票
if(ticketNUm == 0){
flag = false;
return;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName() + "买到了第" + ticketNUm-- + "张票");
}
public static void main(String[] args) {
TestThread4 testThread4 = new TestThread4();
new Thread(testThread4,"学生").start();
new Thread(testThread4,"老师").start();
new Thread(testThread4,"黄牛").start();
}
}
解决办法:
//在写操作票数量的代码buy()方法时,加锁sychronized
private synchronized void buy(){...}
利用多线程向数据库插入10万条数据
首先创建数据库表tb_demo数据库连接
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Vector;
public class JdbcUtils {
static String driver = "com.mysql.cj.jdbc.Driver";
static String url="jdbc:mysql://localhost:3306/thread?serverTimezone=UTC";
static String user="root";
static String pwd = "666666";
static Vector<Connection> pools = new Vector<Connection>();
public static Connection getDBConnection(){
try {
//1.加载驱动
Class.forName(driver);
//2.取得数据库连接
Connection conn = DriverManager.getConnection(url, user, pwd);
return conn;
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
static {
int i = 0;
while(i<10){
pools.add(getDBConnection());
i++;
}
}
public static synchronized Connection getPool(){
if(pools != null && pools.size() > 0){
int last_ind = pools.size() -1;
return pools.remove(last_ind);
}else{
return getDBConnection();
}
}
public static int insert(String sql,Object[] params){
Connection conn = getPool();
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
for(int i=0;i<params.length;i++){
pstmt.setObject(i+1, params[i]);
}
return pstmt.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(pstmt != null){
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
pools.add(conn);
}
}
return 0;
}
}
10个线程向数据库插入10万条数据
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class doInsert {
public static void main(String[] args) {
long startTimes = System.currentTimeMillis();
String[] names = new String[]{"A","B","C","D","E","F","G","H","I","J"};
int threadCount = 10;
int total = 100000;
int every = total/threadCount;
final CountDownLatch latch = new CountDownLatch(threadCount);
for(int i=0;i<threadCount;i++){
new Thread(new Worker(latch,names[i],i*every,(i+1)*every)).start();
}
try {
latch.await();
long endTimes = System.currentTimeMillis();
System.out.println("所有线程执行完毕:" + (endTimes - startTimes));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Worker implements Runnable{
static String sql = "INSERT INTO `tb_demo` (`name`,cre_date) VALUES (?, ?);";
int start = 0;
int end = 0;
String name = "";
CountDownLatch latch;
public Worker(CountDownLatch latch,String name, int start,int end){
this.start = start;
this.end = end;
this.name = name;
this.latch = latch;
}
@Override
public void run() {
for (int i = start; i < end; i++) {
System.out.println("线程" + Thread.currentThread().getName()+ "正在执行。。");
Object[] params = new Object[] { name + i, new Date() };
JdbcUtils.insert(sql, params);
}
latch.countDown();
}
}
执行结果
而如果用一个线程来插入这10万条数据,将线程数 threadCount 设为1.
则需要396秒大约6分半钟!!