1. 进程与线程
进程:资源分配的基本单位
线程:资源调度的基本单位
1.1 有了进程为什么还需要线程呢?
为了进程能进行并发操作
1.2 线程的生命周期
2. 创建进程
创建进程有两种方法(一般推荐第二种,因为单继承问题)
先来看看线程的构造函数
2.1 继承Thread类,重写run()
public class Threadtest extends Thread {
//设置名字
public Threadtest(String name) {
super(name);
}
//重写方法
public void run(){
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName() + "-----" + i);
}
}
public static void main(String[] args) {
Threadtest t1 = new Threadtest("线程1");
Threadtest t2 = new Threadtest("线程2");
t1.start();
t2.start();
}
}
2.2 实现Runnable接口,重写run()
public class Runnabletest implements Runnable {
@Override
public void run() {
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName() + ":::" + i);
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnabletest(),"线程1");
Thread t2 = new Thread(new Runnabletest(),"线程2");
t1.start();
t2.start();
}
}
3.线程的方法
3.1 守护线程(setDaemon)
守护进程是为其他线程服务的线程,存在于后台,一旦有线程就存在,线程全部消失而结束
需要在进程启动前调用Thread.setDaemon(true)
3.2 优先级(setPriority)
- 设置获取CPU时间片的几率,分0—10等级,默认为5
public static void main(String[] args) {
Threadtest t1 = new Threadtest("线程1");
Threadtest t2 = new Threadtest("线程2");
//设置优先级
t1.setPriority(10);
t1.start();
t2.start();
}
线程1:::97
线程1:::98
线程1:::99
线程2:::3
线程2:::4
线程2:::5
在前面实例中调用该函数,发现t1线程cpu执行时间片多于t2线程,t1完成了t2还在开头
3.3 sleep
让该线程休眠
try {
Thread.sleep(500); //休眠500ms
} catch (InterruptedException e) {
e.printStackTrace();
}
3.4 join
使当前线程停下来等待,直到调用join()的线程结束,才恢复执行
pulic class extends Thread{
Thread B;
run(){
B.join(); //在线程A中执行线程B
……
}
}
3.5 wait和notify
wait使线程挂起,notify使线程唤醒
thread.wait();
thread.notify();
4. 线程同步
并发出现的问题,线程可以解决,这里引入锁机制
- 可见性:一个线程对共享变量的修改,另一个线程能立刻看到
- 原子性:执行多个操作,其中一个操作没执行的话,全部操作也不执行;否则全部执行
- 有序性:代码的执行顺序按照代码的先后顺序执行,不用考虑重排序
4.1 synchronized锁
它是java的关键字,可以修饰方法,代码块,类
synchronized锁一次只能允许一个线程进入被锁住的代码块,java每个对象都有内置锁,synchronized就是使用对象的内置锁来锁定的
4.1.1 方法锁
public class Synchronizedtest implements Runnable {
//使用的是该类的锁
@Override
public synchronized void run() {
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName() + "------" + i);
}
}
public static void main(String[] args) {
Synchronizedtest st = new Synchronizedtest();
Thread t1 = new Thread(st,"线程1");
Thread t2 = new Thread(st,"线程2");
t1.start();
t2.start();
}
}
线程1------96
线程1------97
线程1------98
线程1------99 //获得锁,执行完才释放,t2线程不能执行该方法
线程2------0
线程2------1
线程2------2
线程2------3
线程2------4
4.1.2 代码块锁
public void run() {
//使用的也是该类的锁,打印结果是一致的
synchronized(this){
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName() + "------" + i);
}
}
}
4.2 volatile
该关键字只修饰类变量和实例变量,禁止了指令重排序,当一个线程修改volatile修饰的变量,另一个线程会立即看到最新的变化,解决了可见性问题
public volatile int num;
5. 线程死锁
- 互斥条件:线程使用的资源不共享
- 请求与保持条件:一个线程有一个资源且等待获取一个被其他线程拥有的资源
- 非剥夺条件:分配的资源不能从相应的线程中被强制剥夺
- 循环等待条件:一个线程等待其他线程,其他线程又等待该线程
6. 线程池
线程池和数据库的连接池同样意思,把多个线程放在一个集合里,有任务时从集合里分配线程,当该线程完成任务后不是销毁,放入线程池等待下次任务,减少了创建和销毁线程的次数,提高系统效率
这里只讨论ThreadPoolExecutor连接池,有三个常见的实现池
- newFixedThreadPool:返回corePoolSize和maximumPoolSize相等的线程池
- newCachedThreadPool:如果线程池里没有空闲线程,线程池也会创建一条新的线程去处理这个任务
- SingleThreadExecutor:使用单个worker线程的Executor
6.1 瞻要
- corePoolSize:核心线程的大小
- maximumPoolSize:线程池最大的线程数
- keepAliveTime:允许线程空闲的时间
- unit:时间单位
- workQueue:阻塞队列
- threadFactory:线程工厂
- handler:拒绝策略
- 生命周期:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED
- Callable和Future
主要方法
execute() //线程池提交任务的
submit() //线程池提交任务的,能够返回任务执行的结果
shutdown() //线程池状态变为SHUTDOWN
shutdownNow() //线程池状态立刻变为STOP
6.2 实现
public class ThreadPool {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
// 提交Callable事务,future拿结果
Future f1 = pool.submit(new Callabletest("线程1"));
Future f2 = pool.submit(new Callabletest("线程2"));
// future的get方法返回结果
Object oj1 = f1.get();
Object oj2 = f2.get();
System.out.println(oj1);
System.out.println(oj2);
// 关闭线程池
pool.shutdown();
}
}
public class Callabletest<V> implements Callable<V> {
private V num;
public Callabletest(V num) {
this.num = num;
}
@Override
public V call() throws Exception {
return num;
}
}
<!-- 打印 -->
线程1
线程2
7. ThreadLocal
ThreadLocal让线程有自己的局部变量,其中重要的方法有:
- set()
- get()
- remove() //可以避免内存泄漏
public class DBUtil {
//数据库连接池
private static BasicDataSource source;
//为不同的线程管理连接
private static ThreadLocal<Connection> local;
static {
try {
//省略配置操作
………………
//初始化线程本地
local = new ThreadLocal<>();
} catch (IOException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
//获取Connection对象
Connection connection = source.getConnection();
//把Connection放进ThreadLocal里面
local.set(connection);
//返回Connection对象
return connection;
}
//关闭数据库连接
public static void closeConnection() {
//从线程中拿到Connection对象
Connection connection = local.get();
try {
if (connection != null) {
//恢复连接为自动提交
connection.setAutoCommit(true);
//这里不是真的把连接关了,只是将该连接归还给连接池
connection.close();
//既然连接已经归还给连接池了,ThreadLocal保存的Connction对象也已经没用了
local.remove();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
参考Java3y