------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------
多线程总结
多线程就是指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,它们会交替执行,彼此间可以进行通信。
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
一、线程
(1)定义
进程:正在进行中的程序(直译)。
线程:进程中一个负责程序执行的控制单元(执行路径)。
注:
1、一个进程中可以有多个执行路径,称之为多线程。
2、一个进程中至少要有一个线程。
3、开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
(2)创建一个线程的两种方式(掌握)
1、定义一个类继承Thread类
public class A extends Thread{
}
new A().start();
2、定义一个类实现Runnable接口,并且重写run()方法
public class A implements Runnable{
@Override
public void run(){
}
}
new Thread(new A()).start();
(3)线程的随机性原理
多个程序实际是通过CPU在做高效切换实现的
(4)线程的声明周期(掌握)
新建 --> 就绪 --> 运行 -->阻塞 --> 死亡
这里要注意,线程阻塞后就无法执行,回到就绪状态
(5)创建线程方式一:继承Thread类
1.定义一个类继承Thread类。
2.覆盖Thread类中的run方法。
3.直接创建Thread的子类对象创建线程。
4.调用start方法开启线程并调用线程的任务run方法执行。
创建线程方式二:实现Runnable接口
1.定义类实现Runnable接口。
2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
4.调用线程对象的start方法开启线程。
二、卖票案例
(1)问题代码
public class TicketRunnable implements Runnable{
private int tickets = 100;
@Override
public void run() {
while(true){
if(tickets > 0){
try {
Thread.sleep(1000);//必须加这个,否则不一定出现负数-----语句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;
}
}
}
}
}
(5)问题补充:
为什么Object obj要加静态修饰?
原因
不加static修饰完全可以,但是在使用的时候需要注意创建多个线程时,传入的必须是同一个Runnable对象的实例,因为每个TicketThread对象中的obj的值是不一样的。这样锁就不一样,所以代码只能这么写:
TicketThread runnable = new TicketThread();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
要是加static的话,即使创建多个TicketThread对象,每个对象共享obj这个变量,锁就是一样的,代码还可以这么写
TicketThread runnable1 = new TicketThread();
TicketThread runnable2 = new TicketThread();
Thread t1 = new Thread(runnable1);
Thread t2 = new Thread(runnable2);
t1.start();
t2.start();
三、结构
(1)同步代码块
synchronized(锁对象){
需要被锁的代码//线程只有拿到了锁对象,才能执行这里的代码!!!换言之,这里的代码如果执行了,说明该线程拿到了锁对象,其他线程不能拿到该锁对象
}
注意
多个线程必须使用同一个锁对象,要不然锁无效
(2)同步方法(掌握)
public synchronized void show(){} //普通方法的锁是this
public static synchronized void show(){} //静态方法的锁是当前类的字节码文件对象 类名.class
(3)注意问题(掌握)
多个线程必须使用同一个锁对象,要不然锁无效
同步代码块锁可以是任意对象
同步方法的锁是this
静态方法的锁是当前类的字节码文件对象 类名.class
(4)什么时候用同步代码块,什么时候用同步方法
尽可能用同步代码块
如果一个方法内的所有代码都被同步代码块包住了,那就用同步方法就可以了
(5)死锁(掌握)
死锁原因总结
线程1自身拿着一个锁:A锁,线程2自身拿着一个锁:B锁
当线程1要用B锁,线程B要用A锁的时候就会发生死锁
线程1
package sisuo;
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
package sisuo;
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锁");
}
}
}
}
锁对象
package sisuo;
public class Lock {
public static final Object LOCK_A = new Object();
public static final Object LOCK_B = new Object();
}
测试代码
package sisuo;
public class Test {
public static void main(String[] args) {
Thread1 t1= new Thread1();
Thread2 t2= new Thread2();
t1.start();
t2.start();
}
}
四、多个线程操作同一数据的问题(线程键通讯问题)
线程键通讯:(掌握)
其实就是多个线程同时操作同一个对象
卖票案例就是线程间通讯问题
问题代码(因为没有对操作共享数据的代码加入同步代码块)
共享数据(学生类)
package tongxin;
public class Student {
public String name;
public int age;
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
线程1:负责修改共享数据
package tongxin;
public class SetThread extends Thread{
private Student stu;
public SetThread(Student stu){
this.stu = stu;
}
@Override
public void run() {
int i=0;
while(true){
if(i%2 ==0){//执行%2操作,是为了写入不同的数据,测试在写入过程中,是否影响另一个线程的读取操作
stu.name = "张三";
stu.age = 13;
}else{
stu.name = "李四";
stu.age = 14;
}
i++;
}
}
}
线程2:负责获取共享数据信息
package tongxin;
public class GetThread extends Thread {
private Student stu;
public GetThread(Student stu){
this.stu = stu;
}
@Override
public void run() {
while(true){
System.out.println(stu);
}
}
}
测试
package tongxin;
public class Test {
public static void main(String[] args) {
//创建共享数据
Student stu = new Student();
//创建两个线程,并且让这两个线程同时操作这个共享数据
GetThread get = new GetThread(stu);
SetThread set = new SetThread(stu);
get.start();
set.start();
}
}
多线程总结
多线程就是指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,它们会交替执行,彼此间可以进行通信。
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
一、线程
(1)定义
进程:正在进行中的程序(直译)。
线程:进程中一个负责程序执行的控制单元(执行路径)。
注:
1、一个进程中可以有多个执行路径,称之为多线程。
2、一个进程中至少要有一个线程。
3、开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
(2)创建一个线程的两种方式(掌握)
1、定义一个类继承Thread类
public class A extends Thread{
}
new A().start();
2、定义一个类实现Runnable接口,并且重写run()方法
public class A implements Runnable{
@Override
public void run(){
}
}
new Thread(new A()).start();
(3)线程的随机性原理
多个程序实际是通过CPU在做高效切换实现的
(4)线程的声明周期(掌握)
新建 --> 就绪 --> 运行 -->阻塞 --> 死亡
这里要注意,线程阻塞后就无法执行,回到就绪状态
(5)创建线程方式一:继承Thread类
1.定义一个类继承Thread类。
2.覆盖Thread类中的run方法。
3.直接创建Thread的子类对象创建线程。
4.调用start方法开启线程并调用线程的任务run方法执行。
创建线程方式二:实现Runnable接口
1.定义类实现Runnable接口。
2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
4.调用线程对象的start方法开启线程。
二、卖票案例
(1)问题代码
public class TicketRunnable implements Runnable{
private int tickets = 100;
@Override
public void run() {
while(true){
if(tickets > 0){
try {
Thread.sleep(1000);//必须加这个,否则不一定出现负数-----语句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;
}
}
}
}
}
(5)问题补充:
为什么Object obj要加静态修饰?
原因
不加static修饰完全可以,但是在使用的时候需要注意创建多个线程时,传入的必须是同一个Runnable对象的实例,因为每个TicketThread对象中的obj的值是不一样的。这样锁就不一样,所以代码只能这么写:
TicketThread runnable = new TicketThread();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
要是加static的话,即使创建多个TicketThread对象,每个对象共享obj这个变量,锁就是一样的,代码还可以这么写
TicketThread runnable1 = new TicketThread();
TicketThread runnable2 = new TicketThread();
Thread t1 = new Thread(runnable1);
Thread t2 = new Thread(runnable2);
t1.start();
t2.start();
三、结构
(1)同步代码块
synchronized(锁对象){
需要被锁的代码//线程只有拿到了锁对象,才能执行这里的代码!!!换言之,这里的代码如果执行了,说明该线程拿到了锁对象,其他线程不能拿到该锁对象
}
注意
多个线程必须使用同一个锁对象,要不然锁无效
(2)同步方法(掌握)
public synchronized void show(){} //普通方法的锁是this
public static synchronized void show(){} //静态方法的锁是当前类的字节码文件对象 类名.class
(3)注意问题(掌握)
多个线程必须使用同一个锁对象,要不然锁无效
同步代码块锁可以是任意对象
同步方法的锁是this
静态方法的锁是当前类的字节码文件对象 类名.class
(4)什么时候用同步代码块,什么时候用同步方法
尽可能用同步代码块
如果一个方法内的所有代码都被同步代码块包住了,那就用同步方法就可以了
(5)死锁(掌握)
死锁原因总结
线程1自身拿着一个锁:A锁,线程2自身拿着一个锁:B锁
当线程1要用B锁,线程B要用A锁的时候就会发生死锁
线程1
package sisuo;
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
package sisuo;
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锁");
}
}
}
}
锁对象
package sisuo;
public class Lock {
public static final Object LOCK_A = new Object();
public static final Object LOCK_B = new Object();
}
测试代码
package sisuo;
public class Test {
public static void main(String[] args) {
Thread1 t1= new Thread1();
Thread2 t2= new Thread2();
t1.start();
t2.start();
}
}
四、多个线程操作同一数据的问题(线程键通讯问题)
线程键通讯:(掌握)
其实就是多个线程同时操作同一个对象
卖票案例就是线程间通讯问题
问题代码(因为没有对操作共享数据的代码加入同步代码块)
共享数据(学生类)
package tongxin;
public class Student {
public String name;
public int age;
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
线程1:负责修改共享数据
package tongxin;
public class SetThread extends Thread{
private Student stu;
public SetThread(Student stu){
this.stu = stu;
}
@Override
public void run() {
int i=0;
while(true){
if(i%2 ==0){//执行%2操作,是为了写入不同的数据,测试在写入过程中,是否影响另一个线程的读取操作
stu.name = "张三";
stu.age = 13;
}else{
stu.name = "李四";
stu.age = 14;
}
i++;
}
}
}
线程2:负责获取共享数据信息
package tongxin;
public class GetThread extends Thread {
private Student stu;
public GetThread(Student stu){
this.stu = stu;
}
@Override
public void run() {
while(true){
System.out.println(stu);
}
}
}
测试
package tongxin;
public class Test {
public static void main(String[] args) {
//创建共享数据
Student stu = new Student();
//创建两个线程,并且让这两个线程同时操作这个共享数据
GetThread get = new GetThread(stu);
SetThread set = new SetThread(stu);
get.start();
set.start();
}
}