1、线程
1、概述
宏观来讲
进程:就是正在运行的程序
线程:就是进程的执行路径,执行单元
2、创建并启动线程的两种方式(掌握)
1、定义一个类继承Thread类
package cn.itcast.createThread;
class MyThread extends Thread{
@Override
public void run() {
System.out.println("线程在运行");
}
}
public class Demo1 {
public static void main(String[] args) {
MyThread mt = new MyThread();
//启动线程
mt.start();
}
}
2、定义一个类实现Runnable接口,并且重写run()方法
package cn.itcast.createThread;
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("启动了");
}
}
public class Demo {
public static void main(String[] args) {
//第一步:创建实现了Runnable接口的子类对象
MyRunnable mr = new MyRunnable();
//第二步:通过刚创建的mr对象创建Thread对象
Thread thread = new Thread(mr);
//第三步:启动线程
thread.start();
}
}
3、给线程设置名字(掌握)
1、通过Thread类的有参构造
2、通过Thread类的setName方法
3、通过Thread.currentThread().setName()方法
public class MyThread extends Thread{
@Override
public void run() {
//设置线程名
Thread.currentThread().setName("线程A");
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
}
package cn.itcast.create;
public class MyRunnable implements Runnable{
@Override
public void run() {
//设置线程名
Thread.currentThread().setName("线程A");
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
4、获取线程的名字(掌握)
1、通过Thread类的getName方法
2、通过Thread.currentThread().getName()方法
5、线程的随机性原理
多个程序实际是通过CPU在做高效切换实现的
6、线程的声明周期(了解)
新建 --> 就绪 --> 运行 -->阻塞 --> 死亡
这里要注意,线程阻塞后就无法执行,回到就绪状态
2、卖票案例(掌握)
1、有问题的代码(出现错误票)
/**
卖票程序
*/
public class TicketRunnable implements Runnable{
private int tickets = 100;
@Override
public void run() {
while(true){
if(tickets > 0){
try {
Thread.sleep(1000);//必须加这个,由于CPU太快否则不一定出现负数-----语句1
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+tickets--+"张的票");---语句2
}else{
break;//必须加这个,否则无法跳出循环,造成死机
}
}
}
}
/**
测试类
*/
public class TicketDemo {
public static void main(String[] args) {
TicketRunnable runnable = new TicketRunnable();
Thread t1 = new Thread(runnable, "窗口1");
Thread t2 = new Thread(runnable, "窗口2");
Thread t3 = new Thread(runnable, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
2、产生问题的原因
比如现在只剩一张票了tickets=1,现在有两个线程,线程1和线程2
线程1先执行,判断tickets >0,执行线程1的语句1,然后被休眠1s
在这个时候线程2抢到了执行权,首先判断tickets>0,继续往下走,执行线程2的语句1,然后被休眠1秒
在线程2休眠的时候,线程1醒了,执行语句1,然后线程1停止,这时候tickets=0
线程2醒了,执行语句2,这时候tickets=-1
3、如何查找问题(掌握)
1、看有没有共享数据
2、看操作共享数据的语句是不是多条语句
3、看是不是在多线程的环境中
最后,把操作共享数据的多条语句用锁 锁起来,注意我们的这把锁必须被多个线程所共享
4、修改后的代码(掌握)
修改后的代码/卖票案例完整代码
public class TicketRunnable implements Runnable {
private int tickets = 50;
private Object lock = new Object();
@Override
public void run() {
while (true) {
synchronized (lock) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "正在卖第" + tickets-- + "张的票");
} else {
break;
}
}
}
}
}
3、锁,同步代码块,同步方法(这块记住结论就好,根据自身情况自行测试)
1、同步代码块
synchronized(锁对象){
需要被锁的代码//线程只有拿到了锁对象,才能执行这里的代码!!!换言之,这里的代码如果执行了,说明该线程拿到了锁对象,其他线程不能拿到该锁对象
}
注意
多个线程必须使用同一个锁对象,要不然锁无效
2、同步方法(掌握)
public synchronized void show(){} //普通方法的锁是this
public static synchronized void show(){} //静态方法的锁是当前类的字节码文件对象 类名.class
3、注意问题(掌握)
多个线程必须使用同一个锁对象,要不然锁无效
同步代码块锁可以是任意对象
同步方法的锁是this
静态方法的锁是当前类的字节码文件对象 类名.class
4、什么时候用同步代码块,什么时候用同步方法
尽可能用同步代码块
如果一个方法内的所有代码都被同步代码块包住了,那就用同步方法就可以了
4、死锁(掌握)
死锁原因总结
线程1自身拿着一个锁:A锁,线程2自身拿着一个锁:B锁
当线程1要用B锁,线程B要用A锁的时候就会发生死锁
/**锁对象*/
public class Lock {
public static final Object LOCK_A = new Object();
public static final Object LOCK_B = new Object();
}
/**线程1*/
public class Thread1 extends Thread {
@Override
public void run() {
synchronized (Lock.LOCK_A) {
System.out.println("我是线程1,已经拿到A锁,将要去哪B锁");
synchronized (Lock.LOCK_B) {
System.out.println("我是线程1,成功拿到B锁");
}
}
}
}
/**线程2*/
public class Thread2 extends Thread {
@Override
public void run() {
synchronized (Lock.LOCK_B) {
System.out.println("我是线程2,已经拿到B锁,将要去哪A锁");
synchronized (Lock.LOCK_A) {
System.out.println("我是线程2,成功拿到A锁");
}
}
}
}
/**测试代码*/
public class Test {
public static void main(String[] args) {
Thread1 t1= new Thread1();
Thread2 t2= new Thread2();
t1.start();
t2.start();
}
}
- //注意:以上代码可能不会死锁,如果必须产生死锁效果将run()方法中的所有内容用while(true)包裹起来
5、休眠线程(掌握)
Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000
package cn.itcast.createThread;
public class MyThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6、守护线程
setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
package cn.itcast.createThread;
public class MyThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("xx");
}
}
public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
mt.setDaemon(true);
mt.start();
//主线程在1S后结束,结束后mt线程也随之停止
Thread.sleep(1000);
}
}
7、加入线程
package cn.itcast.createThread;
public class MyThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("xx");
}
}
public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
mt.start();
mt.join();
//因为mt线程被设置为加入线程,所以只有当mt线程执行完后你好才会执行
System.out.println("你好");
}
}
8、设置线程优先级
1、线程优先级级别
线程默认优先级是5。范围是1-10
Thread.MAX_PRIORITY //10
Thread.MIN_PRIORITY //1
Thread.NORM_PRIORITY //5
2、方法
public final int getPriority():获取线程优先级
public final void setPriority(int newPriority):更改线程的优先级
3、注意
优先级可以在一定的程度上,让线程获较多的执行机会
4、举例
MyThread t = new MyThread();
System.out.println(t.getPriority());
t.setPriority(Thread.MAX_PRIORITY);
sleep(...) 线程休眠多久
join(..) 执行多久后就不插队了
wait(..) 多久后开始等待
13、今天必须掌握的内容,面试题,笔试题。(掌握这个就可以放心学习后面的知识了)
1、说说线程和进程
2、说说创建线程的两种方式,分别在什么情况下使用
3、如何启动线程
4、如何给线程设置名字,如何获取线程的名字
5、请描述下线程的生命周期
6、多个线程访问同一数据如果发生数据异常,我们应该如何查找问题,找到问题后应该如何处理
7、同步代码块的锁是什么,同步方法的锁是什么,静态同步方法的锁是什么
8、说说死锁原理
9、如何让线程进入休眠状态
10、sleep和wait的区别(都释放CPU执行权,sleep不释放锁,wait释放锁)
11、一个线程的优先级越高,那么这个线程就一定先执行完毕其他线程才能执行,错还是对,为什么
12、代码题:卖票案例,增加需求:统计每个窗口各卖了多少张票。(分别用线程的两种方式实现)
13、代码题:死锁案例