目录
一、程序,进程,线程
1.程序是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
2.进程是运行中的程序,被加载到内存中,是操作系统进行资源分配的最小单位。
3.线程可进一步细化为线程,是一个进程内部的最小执行单元,是操作系统进行任务调度的最小单元,隶属于进程。
二、线程和进程的关系
一个线程只属于一个进程,线程不能脱离进程。
一个进程至少一个线程(主线程),比如java中的main方法就是启动主线程的。
三、创建线程
创建线程有两种方式:继承Thread类的方式和实现Runnable接口的方式
1、继承Thread类的方式
扩展Thread类,重写其中的run方法
Thread类是java提供的对线程进行管理的类
①.定义:
public class MyThread extends Thread {
public void run() {
在线程中要执行的任务都写在run方法中
}
}
②.调用:
MyThread thread = new MyThread();
//myThread.run(); 调用run(),不能启动线程,就是普通的方法调用
thread.start();//启动线程
2、实现Runnable接口的方式
(线程任务类 implements Runnable)
实现Runnable接口是以后创建线程常用的方式:
避免直接继承Thread类,导致我们自己的类,不能再继承其他类。
①.定义:
public class MyThread implements Runnable{
@Override
public void run() {
//要执行的任务写在run方法中
}
}
}
②.调用:
线程执行任务
MyThread r = new MyThread();
创建一个线程作为外壳,将r包起来,
Thread thread = new Thread(r);
thread.start();
3、继承方式和实现方式的联系与区别
【区别】:
继承Thread: 线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
【实现Runnable的好处】
1)避免了继承Thread后不能继承其他类的局限性。
2)多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
四、Thread类中的方法
run();线程任务执行方法
start();启动线程
Thread(target);创建线程,并指定任务
currenThread();返回对当前正在执行的线程对象的引用
getName(); 获得线程名称
setName();设置线程名称
setPriority(int newPriority);设置线程的优先级(默认5,最大10,最小1)
getPriority();返回线程的优先级
join();等待线程终止
yield();线程让步
sleep(long millis);让线程休眠(毫秒)
[注意] 重写的方法不能抛出异常,只能使用try{}catch(){}
五、线程优先级
计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务。
优先级较高的线程有更多获得CPU的机会,反之亦然
Thread类有如下3个静态常量来表示优先级
● MAX_PRIORITY:取值为10,表示最高优先级。
● MIN_PRIORITY:取值为1,表示最底优先级。
● NORM_PRIORITY:取值为5,表示默认的优先级。
六、线程状态
● 新建:新创建了一个线程对象。
象处于新建状态
● 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时
间片,此时它已具备了运行的条件,只是没分配到CPU资源
● 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run
()方法定义了线程的操作和功能
● 阻塞:阻塞状态是指线程因为某种原因放弃了cpu 使用权,并临时中止自己的执行进入阻塞状态,等待线程进入可运行状态
● 死亡:线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
七、守护线程
1、Java中的线程分为两类:用户线程和守护线程。
2、只有当JVM实例中最后一个非守护线程结束时,守护线程才会随着JVM一同结束工作。
3、守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用是GC(垃圾回收器)。
4、设置守护线程: setDaemon(boolean on)
[注意] 设置线程为守护线程必须在启动线程之前,不然会有异常。
八、多线程的概念
1、多线程:一个应用程序内部,可以同时执行多个任务。
2、优缺点:
①.多线程的优点:
提高程序的处理能力,响应速度。
提高cpu利用率,压榨硬件的价值
②.提升程序结构
多线程的缺点:
对内存和cpu的要求提高了,提升硬件性能改善。
多个线程对同一个共享资源进行访问会相互影响。
九、线程同步
1、并发与并行:
并发:在同一个时间节点上,多个事情同时进行
并发:在一个时间段内,多个事情依次执行
解决办法:
排队+锁
①.几个线程之间要排队
②.在访问时加入锁机制
2、synchronized同步锁
①.修饰一段代码块(同步代码块)
synchronized (同步锁){ // 需要被同步的代码; }
②.在方法声明中,表示整个方法,为同步方法:
synchronized(同步对象/锁对象){
}
同步对象/锁对象:
用来记录有没有线程进入到同步代码块, 要求是唯一的对象,多个线程对应的是同一个对象(充当锁标记)。
public synchronized void show (String name){ // 需要被同步的代码; }
synchronized修饰非静态的方法时,锁对象是this synchronized修饰静态方法时,锁对象是当前类的Class对象。
eg:模拟卖票
public class TicketThread implements Runnable {
static int num = 10;
static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + num);
num--;
} else {
break;
}
}
}
}
}
public class Test {
public static void main(String[] args) {
TicketThread ticketThread = new TicketThread();
Thread thread1 = new Thread(ticketThread, "窗口1");
thread1.start();
Thread thread2 = new Thread(ticketThread, "窗口2");
thread2.start();
}
}
3、synchronized关键字:
(1).修饰一个普通方法:被修饰的方法称为同步方法,其作用域是整个方法,作用的对象是调用这个方法的对象。
(2).修饰静态方法:作用域是整个静态方法,作用的对象是这个类,与类的对象无关。
(3).修饰代码块:被修饰的代码块称为同步代码块,其作用域是大括号{}括起来的代码块,作用的对象是括号中的对象(类和对象)。
十、LOCK锁
1、ReentrantLock类实现了Lock,它与synchronized关键字作用差别不大,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
2、ReentrantLock 和 synchronized 区别:
①.synchronized是关键字,依靠底层编译后的指令来实现。 synchronized可以修饰代码块和方法 synchronized是隐式锁,自动添加锁,同步代码块执行完毕或者出现异常,锁会自动释放
②.ReentrantLock是java.util.concurrent.locks包下的一个类,是依靠java代码实现控制。
ReentrantLock只能修饰代码块 ReentrantLock是手动添加,手动释放。
public class TicketThread implements Runnable{
int num = 10;
Lock lock = new ReentrantLock();//ReentrantLock只能对代码块实现 不能修饰方法
@Override
public void run() {
while (true) {
try {
lock.lock();//获取锁
if (num > 0) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num--;
} else {
break;
}
}finally {
lock.unlock();//释放锁
}
}
}
}
public class Test {
public static void main(String[] args) {
PrintTicket printTicket = new PrintTicket();
Thread t1 = new Thread(printTicket,"窗口1");
t1.start();
Thread t2 = new Thread(printTicket,"窗口2");
t2.start();
}
}
十一、线程死锁
1、多个线程分别占用对方需要的资源,等待对方释放资源(一般至少一个锁的嵌套)。
2、设计程序时需要考虑清楚锁的顺序,尽量减少锁的嵌套使用。
十二、线程通讯
1、线程通讯指的是线程间的相互作用。
2、方法:
.wait(); 线程进入阻塞状态,释放同步监听器,必须使用锁对象来调用wait。
.notify(); 唤醒被wait的线程(或优先级最高的线程)
.notifyAll(); 唤醒所有wait的线程
[注意] .wait(); .notify(); .notifyAll();三个方法必须使用在同步代码块或同步方法中。
3、wait();和sleep();的区别:
①.wait是Object中的方法,而sleep是线程中的方法
②.wait的锁会释放并加入等待队列,而sleep的锁不会释放
③.wait依赖于关键字sychronized,而sleep不依赖于同步器synchronized
④.sleep不需要被唤醒,但wait需要被唤醒
4、典型案例:
生产者/消费者问题
public class Counter{
int num=1;
public synchronized void add(){
if(num==0){
this.num=1;
System.out.println("生产者生产了一件商品");
this.notify();
}
else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void sub(){
if(num>0){
this.num--;
System.out.println("消费者拿走了一件商品");
this.notify();
}else {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Customer extends Thread{
Counter counter=new Counter();
public Customer(Counter counter){
this.counter=counter;
}
@Override
public void run() {
while (true){
counter.add();
}
}
}
public class Producer extends Thread {
Counter counter = new Counter();
public Producer(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
while (true) {
counter.sub();
}
}
}
public class Test {
public static void main(String[] args) {
Counter counter = new Counter();
new Producer(counter).start();
new Customer(counter).start();
}
}
十三、新增创建线程方式——Callable接口
1、实现Callable接口与使用Runnable相比,Callable功能更强大些.
• 相比run()方法,可以有返回值
• 方法可以抛出异常
• 支持泛型的返回值
但需要借助FutureTask类,获取返回结果
2、接收任务
FutureTask<Integer> futureTask = new FutureTask(任务);
3、创建线程
Thread t = new Thread(futureTask);
t.start();
Integer val = futureTask.get();获得线程call方法的返回值