/**
* 线程
* 锁
*/
1.线程
例如:迅雷同时下载多个任务,迅雷是一个进程,每个任务就是一个线程
线程池:存放的是等待CPU调用的线程
当调用线程的start()方法,该线程会加入CPU的线程,等待CPU调用
CPU会随机的从线程池选择一个线程运行,并且分配给它一个时间
当CPU分配的时间段到了,如果任务还没有结束,CPU也会让当前线程暂停运行,等待下一次调用
.......
当某一个线程的任务执行完成,它的任务就结束了,CPU就会把该线程从线程池中销毁
如果是一个CPU(中央处理器),并且是单核CPU,不可能在同一时刻执行多个任务
线程是否执行,是由CPU来决定
多个线程,CPU如何调用:CPU会把系统执行任务的时间分割成多个时间片段,会在不同的时间段调用不同的线程去运行,由于这个时间间隔非常短,我们感觉不到,所以会以为它是同时运行的
1.1 线程的第一种创建方法
1.编写类,继承于 Thread类
2.重写父类的 run() 方法 这个方法称为"线程体",里面包含线程要具体执行的代码
3.创建编写类的实例,调用实例的start()方法启动线程
// 线程
public class Thread1 extends Thread {
private String name;
public Thread1(String name) {
super();
this.name = name;
}
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("线程 " + name);
}
}
}
// 启动线程
Thread1 t1 = new Thread1("1---------");
Thread1 t2 = new Thread1("2---------------------");
// 运行不分前后,谁先抢到 start() 里面的 run() 谁先运行
t1.start();
t2.start();
1.2 线程的第二种创建方法
1.编写类,实现与 Runnable接口
2.重写接口中的 run() 方法
3.Thread th = new Thread(接口实现类的实例)
4.th.start()
// 线程
public class Thread2 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 1000; i++) {
try {
Thread.sleep(2000); // 休眠 2000 毫秒,之后接着运行后面的代码
} catch (Exception e) {
}
Thread.yield(); // 礼让线程,把控制权交给其他线程来运行(重新抢夺 run())
System.out.println(Thread.currentThread().getName());
}
}
}
// 启动线程
Thread t1 = new Thread(new Thread2(), "线程1--------"); // 后面的字符串是线程的名字
Thread t2 = new Thread(new Thread2(), "线程2------------------");
t1.start();
t2.start();
t2.setPriority(10); // 设置线程的优先级,范围是1-10(只是概率大一点,不是绝对)
t1.setName("1231"); // 设置线程名字
2.锁
线程安全:一个线程正在操作一个对象或者一个方法,其他线程必须等待该线程访问结束以后才允许操作该对象或者方法
线程不安全:一个对象或者一个方法可以被多个线程同时访问
我们可以通过锁而达到线程安全
2.1 synchronized 同步锁
1.这个关键字如果修饰方法表示该方法只允许被一个线程访问,如果方法中已经存在线程,其他线程必须等待该线程退出方法后才可以访问该方法
2.使用同步块,表示方法中的其他代码依然可以被多个线程访问,但是同步块中的代码只允许一个线程访问
如果多个线程操作了共享资源,操作共享资源的代码都应该放在同步锁中
使用锁的前提是这个对象是多个线程都会操作的同一个对象
// 三个窗口同时买卖票,票只有 100 张,一张票只能卖出一次
//售票类
public class Window implements Runnable {
private static int ticket = 0;
@Override
public void run() {
while (true) {
synchronized (this) {
if (ticket < 1000) {
ticket++;
System.out.println(Thread.currentThread().getName() + "卖了第 " + ticket + " 张票");
} else {
break;
}
}
}
}
}
//三个窗口卖票
Window w = new Window();
Thread t1 = new Thread(w, "武汉售票口");
t1.start();
Thread t2 = new Thread(w, "武昌售票口");
t2.start();
Thread t3 = new Thread(w, "汉口售票口");
t3.start();
2.2 Lock锁(接口,通过其子类来实例化Lock接口)
//需要上锁的类
public class LockUp implements Runnable {
private Lock lock = new ReentrantLock(); //创建锁
@Override
public void run() {
for(int i = 0; i < 100; i++) {
lock.lock(); //获得锁(上锁)
System.out.println(Thread.currentThread().getName() + i);
lock.unlock(); //释放锁
}
}
}
// 启动线程
LockUp lu = new LockUp();
Thread t1 = new Thread(lu, "线程1----------");
t1.start();
Thread t2 = new Thread(lu, "线程2----------------------");
t2.start();
代码加锁的好处与弊端:
好处:线程安全,数据不会出错
弊端:1.代码如果加了锁,它会消耗更多的系统资源,会降低程序的性能
2.如果频繁加锁,容易产生死锁
2.3 死锁
在争夺资源时,都在等待对方释放锁,程序进入一种无限等待的状态
// 演示死锁的类
class Obj{
static Object objA = new Object();
static Object objB = new Object();
}
// Obj 和 DieLock 在一个类里面
public class DieLock implements Runnable {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (Obj.objA) {
System.out.println("线程1锁住了objA");
synchronized (Obj.objB) {
System.out.println("线程1锁住了objB----------------线程1的任务结束了");
}
}
}else {
synchronized (Obj.objB) {
System.out.println("线程2锁住了objB");
synchronized (Obj.objA) {
System.out.println("线程2锁住了objA----------------线程2的任务结束了");
}
}
}
}
}
// 启动线程
Thread td1 = new Thread(new DieLock(true));
td1.start();
Thread td2 = new Thread(new DieLock(false));
td2.start();
产生死锁的四个必要条件
1.互斥条件:一个资源只能被一个进程占用
2.不可剥夺条件:某个进程占用了资源,只能它自己去释放
3.请求和保持条件:某个进程之前申请了资源,还想再申请资源,之前的资源还在占用着
4.循环等待条件:一定会有两个线程互相等待