-
什么是进程?
- 正在运行的程序,是系统进行资源分配的基本单位。
- 目前的操作系统都是支持多进程的,可以同时执行多个进程,通过进程ID(PID)区分。
-
什么是线程?
- 线程又称轻量级进程。
- 线程是进程中的一条执行路径,也是CPU的基本调度单位。
- 一个进程由一个或多个线程组成,彼此间完成不同的工作。
- 同时执行,又称为多线程。
- Java虚拟机是一个进程,当中默认包含主线程(main),可以通过代码创建多个独立线程,与main并发执行。
-
进程和线程的区别
- 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
- 一个程序运行后至少有一个进程。
- 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的。
- 进程间不能共享数据段地址,但同进程间的线程之间可以。
-
线程的组成
- 任何一个线程都具有基本的组成部分:
- CPU时间片:操作系统(OS)操作系统会为每个线程分配执行时间。
- 运行数据:
- 堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象。
- 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈。
- 线程的逻辑代码。
- 线程的特点
- 线程抢占式执行
- 效率高
- 可防止单一线程长时间独占CPU
- 在单核CPU中,宏观上同时执行,微观上顺序执行
- 线程抢占式执行
-
main方法是主线程,用来执行main方法;main方法里面创建的线程叫做子线程
-
package com.stu.szh.duoxiancheng; public class MyThread01 { public static void main(String[] args) { // 1.创建线程对象 SonThread sonThread = new SonThread(); // 2.启子动线程 // 注意:不能用run() sonThread.start(); // main方法,主线程 for (int i=0; i<=50; i++){ System.out.println("这是主线程:-----------------"+i); } } static class SonThread extends Thread { @Override public void run() { super.run(); for (int i=0; i<=50; i++){ System.out.println("这是子线程:.........."+i); } } } } -
获取线程ID和线程名称
- 在 Thread的子类中调用this.getId()或this.getName(),得到线程id和名称。
- 缺点:只能在继承Thread类的子类中使用,因为他们是Thread类中的方法。
- 使用Thread.currentThread().get()和Thread.currentThread().getName。
- 在 Thread的子类中调用this.getId()或this.getName(),得到线程id和名称。
-
修改线程名称
-
调用线程对象的setName方法。
-
sonThread.setName("子线程");
-
-
使用线程子类的构造方法赋值。
-
public SonThread(String name){ super(name); } SonThread sonThread = new SonThread("子线程");
-
-
只能修改线程的名称,不能修改线程的ID(ID由系统生成)。
-
修改线程名称必须在,启动线程之前修改(sonThread.start()之前修改)。
-
- 相比继承Thread,实现Runnable有一下优势
- 适合多个相同的程序代码的线程去共享同一个资源,如果一个类继承Thread,则不适合资源共享;但如果实现了Runnable接口的话,则很容易的实现资源的共享。
- 可以避免java中单继承的局限性,java不支持多继承,但允许调用多个接口。
- 增强程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立,而继承方式中线程和任务是耦合的。
- 线程池只能放入实现Runnable接口或Callable接口的类线程,不能直接放入Thread类。
-
休眠:
-
public static void sleep(long millis) // 当前线程主动休眠 millis 毫秒,1000 millis = 1 s。
-
Thread.sleep(1000); // 休眠1秒。注意要try-catch异常,不能抛出异常。因为子类不能抛出比父类有更宽的异常
-
-
public static void yield () // 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片(有可能抢到也有可能抢不到)。
-
Thread.yield(); // 主动放弃CPU
-
pubilc final void join(); // 允许其他线程加入到当前线程中,会把当前线程阻塞,知道加入的线程执行完毕,当前线程才会继续 执行
-
join()要在启动(start())之后加入。
j1.join(); // (入过join是在main块中写的话)加入到当前线程(main()),并阻塞当前线程,直到加入线程执行完毕。 // jion()不是静态方法(static),不能直接用类名调用。
-
优先级:
- 线程对象setPriority();
- 线程优先级为1-10,默认为5,优先级越高(值越大,优先级越高,10的优先级最高),表示获取CPU机会越高。
-
p1.setPriority(1);
-
守护线程:
- 线程对象setDaemon(true); 设置为守护线程
- 线程有两类:用户线程(前台线程),守护线程(后台线程)
- 如果线程中所有前台线程都执行完毕了,后台线程会自动结束
- 垃圾回收器线程属于守护线程
-
d1.stDaemon(true); // 将d1设置为守护线程,注意要在start()之前设置。
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
- 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可以保证其正确性。
- 原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可以打乱或缺省。
synchronized(临界资源对象){ // 对临界资源对象加锁
// 代码(原子操作)
}
-
当一个线程执行该代码块时,其他线程不能执行该代码块(加锁了)。
-
每个对象都有一个互斥锁标记,用来分配给线程。
-
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
-
package com.stu.szh.duoxiancheng; public class TestBankCard02 { public static void main(String[] args) { // 1.创建银行卡 BankCard02 card = new BankCard02(); // 使用匿名内部类的方法来实现存钱 Runnable add = new Runnable() { @Override public void run() { // 加锁 synchronized (card) { for (int i = 0; i < 10; i++) { card.setMoney(card.getMoney() + 1000); System.out.println(Thread.currentThread().getName() + "存了1000,余额是:" + card.getMoney()); } } } }; // 使用匿名内部类的方式来取钱 Runnable sub = new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { synchronized (card) { if (card.getMoney() >= 1000) { card.setMoney(card.getMoney() - 1000); System.out.println(Thread.currentThread().getName() + "取了1000,余额是:" + card.getMoney()); } else { System.out.println("余额不足,请及时充钱!"); i--; } } } } }; // 创建两个线程对象 Thread xiaoli = new Thread(add,"小李"); Thread xiaoming = new Thread(sub,"小明"); // 启动线程 xiaoli.start(); xiaoming.start(); } } package com.stu.szh.duoxiancheng; public class BankCard02 { private double money; public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }
-
synchronized 返回值类型 方法名称 (形参列表){ // 对当前对象(this)加锁 // 代码(原子操作) } // 同步方法 public synchronized boolean sale(){} -
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
-
线程退出同步方法时,会释放相应的互斥锁标记。
- 注意:
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的标记锁。
- 如果调用不包含同步代码块的方法,或普通方法时,则不需要所标记,可直接调用。
- 已知JDK中线程安全的类:
- StringBuffer
- Vector
- Hashtable
- 以上类中的公开方法,均为synchronize修饰的同步方法。
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象所标记时,就产生了死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已拥有的锁标记,由此可能会造成死锁。
-
等待:
-
public final void wait()
-
public final void wait(long timeout)
-
必须在obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁进入等待队列。
-
-
通知: 即唤醒等待中的线程。(等待线程进入等待队列后,如果没有别的线程来通知它,就不会醒,所以需要以下方法)
-
public final void notify()
-
public final void notifyAll()
-
- 问题:
- 线程是宝贵的内存资源,单个线程约占1MB空间,过多分配,易造成内存溢出。
- 频繁的创建及销毁线程会增加虚拟机的回收频率,资源开销,造成程序性能下降。
- 线程池:
- 线程容量,可设定线程分配的数量上限。
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
- 避免频繁的创建和销毁线程。
- 将任务提交给线程池,由线程池分配线程,运行任务,并在当前任务结束后复用线程
-
常用的线程池接口和类(所在包java.util.concurrent):
-
Executor:线程池的顶级接口,所有的线程池都要实现该接口。
-
public interface Executor { void executor(Runnable command); } -
ExecutorService: 线程池接口,包含管理线程池的一些方法(submit,shutdown),可通过submit(Runnable task)提交任务代码。
- ThreadPoolExecutor:是ExecutorService的实现类,线程池执行器。
- SchedulePoolExecutor:是ThreadPoolExecutor的子类,
-
Executors工厂类:通过此类可以获得一个线程池。
-
创建固定大小的线程池
-
创建缓存线程池,由任务的多少来决定
-
创建单线程池(里面只有一个线程)
Executors.newSingleThreadExeutor();
-
创建调度线程池(调度:周期执行,或定时执行)
Executors.newScheduledThreadPool(线程个数);
-
-
shutdown : 等待所有任务执行完毕,然后关闭线程池
-
shutdownNow:直接停止所有任务,关闭线程池
-
public class Demo { public static void main(String[] args){ // 1.1创建固定大小的线程池(例:4个线程) ExecutorService ex = Executors.newFixedThreadPool(4); // 1.2创建缓存线程池,由任务的多少来决定 ExecutorService ex = Executors.newCachedThreadPool(); // 2.创建线程任务 Runnable runnable = new Runnable(){ // 采用匿名内部类 @override public void run() {...}; }; // 3.提交任务 for (int i=0; i<4; i++) { es.submit(runnable); } // 4.关闭线程池 es.shutdown; } }
-
-
public interface Callable
{ public V call() throws Exception; } -
JDK5加入,与Runable接口类似,实现之后代表一个线程任务
-
Callable具有泛型返回值,可以声明异常(Runnable接口无返回值不具有这两个特点,这也是Callable接口与Runnable的区别)
-
异步编程用Callable接口
-
package com.stu.szh.duoxiancheng; import com.sun.corba.se.impl.orbutil.closure.Future; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Callable03 { public static void main(String[] args) throws ExecutionException, InterruptedException { // 1.创建Callable对象 Callablecallable = new Callable () { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName()+"开始计算"); int sum = 0; for (int i=1; i<=100; i++){ sum += i; Thread.sleep(100); } return sum; } }; // 2.将Callable对象转换成可执行对象 FutureTask task = new FutureTask<>(callable); // 3.将任务交给线程对象 Thread thread = new Thread(task); // 4.启动线程 thread.start(); // 5.因为有返回值,所以可以获取结果 // get方法要等到call方法执行完毕,才会返回值 Integer sum = task.get(); System.out.println("结果是: "+sum); } } -
在线程池中使用Callable
-
package com.stu.szh.duoxiancheng; import java.util.concurrent.*; public class CallableChi03 { public static void main(String[] args) throws ExecutionException, InterruptedException { // 1.创建线程池 ExecutorService ex = Executors.newFixedThreadPool(1); // 2.提交任务,Future:表示将要执行完任务的结果 Futurefuture = ex.submit(new Callable () { @Override public Integer call() throws Exception{ System.out.println(Thread.currentThread().getName()+"开始计算"); int sum = 0; for (int i=0; i<=100; i++){ sum+=i; } return sum; } }); // 3.获取任务结果,等待任务执行完毕才返回结果 System.out.println(future.get()); // 4.关闭线程 ex.shutdown(); } }
- future表示将要完成任务的结果
-
同步:
- 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。
- 注解:单条执行路径。
-
异步:
- 形容一次方法调用,异步一旦开始,像是一次消息传递,调用告知者后立刻返回,二者竞争时间片,并发执行。
- 注解:多条执行路径
-
JDK5加入,与synchronized(synchronized效率较低)相比,显示定义,结构更加灵活。
-
提供更多实用性方法,功能更强大,性能更优越。
-
常用方法:
-
void lock() // 获取锁,如果锁被占用,则等待。
-
booleab tryLock() // 尝试获取锁(成功则返回true,失败则返回false,不阻塞)
-
void unlock() // 释放锁
-
-
ReentrantLock: Lock接口的实现类,与synchronized一样具有互斥锁的功能。
-
class X { private final ReentrantLock lock = new ReentrantLock(); // ... public void m() { lock.lock(); // block until condition holds try { // ... method body } finally { lock.unlock() } } }
-
ReentrantReadWriteLock:
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁。
- 支持多次分配读锁,使多个读操作可以并发执行。
-
互斥规则:
- 写-写:互斥,阻塞。
- 读-写: 互斥,阻塞。
- 读-读: 不互斥,不阻塞。
- 在读操作远远高于写操作的环境中,可在保证线程安全的情况下,提高运行效率。
-
class CachedData { Object data; volatile boolean cachevalid; ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); if (!cachevalid) { // Must release read lock before acquiring write lock rwl.readLock().unlock(); rwl.writeLock().lock(); // Recheck state because another thread might have acquired // write lock and changed state before we did. if (!cachevalid) { data = ... cachevalid = true; } // Downgrade by acquiring read lock before releasing write lock rwl.readLock().lock(); rwl.writeLock().unlock(); // Unlock write, still hold read } use(data); rwl.readLock().unlock(); } }



