#8-11
程序是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。 (程序是静态的)
是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。 (进程是动态的)是一个动的过程 ,进程的生命周期 : 有它自身的产生、存在和消亡的过程
进程可进一步细化为线程, 是一个程序内部的一条执行路径。 若一个进程同一时间并行执行多个线程,就是支持多线程的。
多个CPU同时执行多个任务
一个CPU同时执行多个任务
继承Thread类的类需要重写run方法
在需要开启新线程的时候,调用对象的start()
方法
/**
* 线程子类
* 拥有抢夺资源的能力 : 继承Thread 重写其中run方法
*/
class NewThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("子线程,启动:"+i);
}
}
}
class main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("主线程,启动:"+i);
}
//创建一个线程类
new NewThread().start(); //如果调用run, 相当于调用普通方法 不是开启一个新的线程
for (int i = 0; i < 10; i++) {
System.out.println("主线程二,启动:"+i);
}
}
}
线程默认的名字是 Thread-[数字]
主方法的线程名是 main
通过调用线程类的setName()
方法可以自定义线程名,也可以通过构造函数来自定义线程名
实现Runnable接口,必须实现其中的run()
方法
/**
* 创建线程2: 实现Runnable接口
*/
class Runable implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"正在运行: "+i);
}
}
}
class Test {
public static void main(String[] args) {
Runable runable = new Runable();
new Thread(runable, "子线程").start();
//注意实现Runnable接口的线程启动方式
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"正在运行:"+i);
}
}
}
Thread和Runnable是实现java多线程的2种方式,
Runable
是接口,Thread
是类,建议使用Runable
实现 java多线程,不管如何,最终都需要通过thread.start()
来使线程处于可运行状态。
对比第一种和第二种创建线程的方式发现,无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要有一个run方法,
但是这个run方法有不足:
(1)没有返回值
(2)不能抛出异常
基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:实现Callable接口。
实现Callable接口好处:
(1)有返回值
(2)能抛出异常
缺点:
线程创建比较麻烦
class CallableTest implements Callable {
@Override
public Object call() throws Exception {
int i = 0;
for ( i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"线程运行中:"+i);
}
return i; //自动装箱
}
}
class Test{
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new CallableTest());
Thread thread = new Thread(futureTask,"仔仔");
thread.start(); //开启callable线程
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"线程运行中:"+i);
}
Object o = futureTask.get();
System.out.println(o);
}
}
继承Thread类:
- 创建线程最简单的方式之一,只需要继承Thread类并重写run方法。
- 优点:代码比较简单,适合简单的线程任务。
- 缺点:由于Java不支持多重继承,如果已经继承了其他类,就无法使用这种方式创建线程。
实现Runnable接口:
- 创建线程的推荐方式,可以避免单继承限制,适合多线程共享同一个资源的情况。
- 需要实现run方法,并将实现了Runnable接口的对象作为参数传递给Thread类的构造函数。
使用Callable和Future接口:
- 这种方式允许线程返回结果,相对于前两种方式,更加灵活。
- Callable是一个带有返回值的任务,而Runnable只是一个执行的任务。
- 使用Executor框架调度Callable任务,并通过Future对象来获取线程的执行结果。
Thread类中有一个内部类Thread.State,其中定义了一些枚举常量,他们代表了线程生命周期中的几个状态
- NEW
新建状态,线程刚刚被创建,尚未调用
start()
方法。 - RUNNABLE 就绪状态,线程已经被创建且可以被调度执行,但尚未获得CPU资源。
- BLOCKED 阻塞状态,线程被阻塞,正在等待获取一个监视器锁
- WAITING 等待状态,线程进入等待状态,等待另一个线程通知或中断。
- TIMED_WAITING 限时等待状态,线程等待一个具有超时时间的操作。
- TERMINATED 终止状态,线程已经执行完毕。
start()
启动当前线程run()
线程执行的内容/单独调用时视为普通方法currentThread()
静态方法:获取当前正在执行的线程setName()
设置线程名getName()
获取线程名currentThread().setPriority()
设置当前线程优先级currentThread().getPriority()
获取当前线程优先级join()
当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程。sleep(long millis)
人为的制造阻塞事件setDaemon()
设置伴随线程
synchronized ( 🔒 ){
...
}
🔒 :多个线程必须共用一把锁 (同步监视器)。锁必须是引用数据类型。
同步代码块中不能改变锁对象的引用
尽量不要使用String和Integer做锁
使用final修饰锁
public class BuyTicketThread extends Thread {
public BuyTicketThread(String name){
super(name);
}
//一共10张票:
static int ticketNum = 10;//多个对象共享10张票
//每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
@Override
public void run() {
//每个窗口后面有100个人在抢票:
for (int i = 1; i <= 100 ; i++) {
buyTicket();
}
}
public static synchronized void buyTicket(){// 锁 BuyTicketThread.class
if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
System.out.println("我在"+Thread.currentThread().getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
public class BuyTicketThread implements Runnable {
int ticketNum = 10;
//拿来一把锁: (可住入锁)
Lock lock = new ReentrantLock();//可重入锁 多态 可以使用不同的实现类
@Override
public void run() {
//此处有1000行代码
for (int i = 1; i <= 100 ; i++) {
//上锁
lock.lock();
try{
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}catch (Exception ex){
ex.printStackTrace();
}finally {
//开锁:--->即使有异常,这个锁也可以得到释放
lock.unlock();
}
}
//此处有1000行代码
}
}
优点:安全 缺点:效率低 容易造成死锁。解决方法:减少同步资源的定义,避免同步代码的嵌套。