目录
一、概念
1.区分程序、进程、线程:
程序:指为完成某种任务,用某种语言编写的一组指令集合。
进程:程序的一次执行过程。当一个程序被运行,即开启一个进程。
线程:即细化后的进程,可以理解为程序完成任务的一个步骤,是程序内部执行的一条路径。同时线程也是CPU调度的最小单位。一个进程可以有多个线程,若该进程同时执行了多个线程,即为多线程。
2.区分并行与并发:
并行:多个CPU同时执行多个任务。
并发:一个CPU同时执行多个任务。
二、线程的基本操作:
1、线程的创建
public class Threadcreat {
public static void main(String[] args) {
MyThread1 myThread1=new MyThread1();
//myThread1.start(); //用于开启一个一个线程,并且start方法只能调用一次
myThread1.run();
/*
面试题:run方法和start方法的区别
*/
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName() + "打印---->" + i);
}
}
}
class MyThread1 extends Thread{
//run方法里面存放的是线程任务
@Override
public void run(){
for (int i=0;i<10;i++){
//Thread类提供了一个静态方法名字叫做getName()用于获取线程名称
System.out.println(Thread.currentThread().getName() + "打印----》" + i);
}
}
}
实现Runnable接口:
public class RunnableTest {
public static void main(String[] args) {
Mythread01 mythread01=new Mythread01();
Thread thread=new Thread(mythread01,"线程1");
Thread thread1=new Thread(mythread01,"线程2");
thread.start();
thread1.start();
}
}
class Mythread01 implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
/*
thread类的currentthread()方法可以用于返回一个当前线程类的对象
*/
System.out.println(Thread.currentThread().getName() + "----->" + i);
}
}
}
2、线程类的常用方法
3、线程调度与线程通信
线程调度:由于计算机通常只有一个 cpu,在任意时刻只能执行一条机器指令。因此,每个线程只有获得 cpu 的使用权才能执行指令。所谓线程并发运行,其实是各个线程轮流获取cpu的使用权,分别执行各自的任务。在线程池中会有多个线程处于就绪状态等待cpu。JAVA虚拟机的任务之一就是负责线程调度-------->按照特定机制为多个等待中的线程分配cpu。
线程通信:在需要让多个线程按照规则去协同执行任务时,需要用到线程通信。如定义两个线程交替打印数字。
线程通信通常可以利用 wait()、notify()、notifyAll()等方法来实现。其中wait()方法:让当前进程释放对象锁并进入阻塞状态;notify()方法:用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁, 进而得到 CPU 的执行;notifyAll()方法:用于唤醒所有正在等待相应对象锁的线程。
注:wait、notify、notifyall 这三个方法都不是 Thread 类中所声明的方法,而是 Object 类中声明的方法。因此在使用的时需要用对应同步块或者方法的锁对象去调用。
class T1 extends Thread{
private static int num = 1;
private static Object object = new Object();
T1(String name){
super(name);
}
@Override
public void run() {
while (true){
synchronized (object){
object.notify();
if (num <= 20){
System.out.println(getName()+"--->"+num);
num++;
try {
object.wait();
}catch (Exception e){
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
4、线程的生命周期
与对象相同,线程也有生命周期,一个线程的生命周期包含:新建、就绪、运行、阻塞、死亡五个状态。
新建:当创建声明一个Thread类或其子类后,新生的线程对象就处于新建状态;
就绪:当处于新建状态的线程对象调用 start 方法以后,它就进入线程队列等待cpu时间片,此时线程对象已经具备了运行条件,只是没有得到 cpu 资源;
5、线程安全与线程同步
线程安全:指当多个线程去操作共享数据的时候出现了共享数据的冲突,我们称此时线程是不安全的。如:创建三个线程售卖火车票问题。在线程一对共享数据进行操作时,线程二拿到的并不是线程一操作后的数据,因此该数据前后会发生冲突。这一问题通常通过线程同步或线程通信去解决。
线程同步:指当一个线程在操作数据的时候,其它线程不能参与进来,只能当前线程某个操作完成以后才可以让其它线程参与进来。java中提供了一些关键字用于实现线程同步。如Synchronized、lock等。
用Synchronized去同步代码块或方法:通过 Synchronized 关键字将需要待同步的代码给放到指定代码块里面,并使用同步锁去控制同步。同步代码块中的同步监视器是用于控制线程同步,同步监视器也就是锁,任何一个类的对象都是锁,需要注意的是多个线程要使用同一把锁否则将不能达到同步的效果。
此方法主要用到了线程的三大特性:原子性、有序性、可见性。
Synchronized(同步监视器){代码}
public class Model18 {
public static void main(String[] args) {
WindowSal sal = new WindowSal();
Thread thread1 = new Thread(sal,"窗口一");
Thread thread2 = new Thread(sal,"窗口二");
Thread thread3 = new Thread(sal,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
class WindowSal implements Runnable{
private int ticket = 1000;
@Override
public void run() {
while (true){
synchronized (this){
if (ticket > 0){
System.out.println(Thread.currentThread().getName()+"正在售卖第
"+(100-ticket+1)+"张票"+"还剩下:"+(ticket-1)+"张票。");
ticket--;
}else {
break;
}
}
}
}
}
6、锁
锁:上述关键字Synchronized中的参数,也就是Synchronized(同步监视器)中的“同步监视器”就是锁。它可以是一个类的对象,也可以是this指针。用于控制线程同步,即在一个对象执行完这部分代码之前,其它线程对线不能访问这部分代码,即形成了锁。
死锁:多个线程互相等待对方持有的锁,而在得到对方的锁之前都不会释放自己的锁,即为死锁。
产生条件:
(1)互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
(2)请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
(3)非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
class T1 extends Thread{
private static Object obj1 = new Object();
private static Object obj2 = new Object();
private int flag;
T1(String name,int flag){
super(name);
this.flag = flag;
}
@Override
public void run() {
if (flag == 1){
synchronized (obj1) {
System.out.println(getName()+"已持锁一");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"正在尝试获取锁二");
synchronized (obj2) {
System.out.println(getName()+"已持锁二");
}
}
}else {
synchronized (obj2) {
System.out.println(getName()+"已持锁二");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"正在尝试获取锁一");
synchronized (obj1) {
System.out.println(getName()+"已持锁一");
}
}
}
}
}
public class Model2 {
public static void main(String[] args) {
T1 t1 = new T1("线程一",1);
T1 t2 = new T1("线程二",2);
t2.start();
t1.start();
}
}
死锁解决:资源回收、改变加锁顺序、开放调用、定时锁等。
Lock锁:Java提供的一个接口,使用它的实现类可以达到与Synchronized相同的同步代码块的效果。
Lock接口中用于加锁、释放锁等操作的方法:
(1)void lock():获得锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁。
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long var1, TimeUnit var3) throws
InterruptedException;
void unlock();
Condition newCondition();
}
注:在操作 Lock 锁需要注意的是 Lock 锁需要手动释放,即使发生了异常,锁也不会被释放,需要我们手动释放,对此我们在使用 Lock 锁时就需要将代码放在放在 try-finally 异常处理机制中。
三、其它重点:
(1)
sleep 和 yield 的区别
(2)
sleep 和 wait 的区别
(3)
synchronized 和 Lock 有什么区别
- synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
- synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。