多线程的概述
多线程具有随机性,谁抢到谁执行。
- 多线程并行和并发的区别
-并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
-并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
-比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。
-如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。 - Java程序运行原理和JVM的启动是多线程的吗?
-A:Java程序运行原理
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
-B:JVM的启动是多线程的吗
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
创建线程的两种方法
创建线程的第一种方法
/*
如何在自定义的代码中,自定义一个线程呢?
通过对API的查找,java已经提供了对线程这类事物的描述。就是thread类。
创建线程的第一种方式:继承Thread类。
步骤:
1,定义类继承Thread
2,复写Thread类中的run方法
3,调用线程的start方法,该方法有两个作用
(1)启动线程
(2)调用run方法
*/
clsaa Demo extends Thread{
public void run() { //重写run方法
System.out.println("demo run");
}
}
class ThreadDemo{
public static void main(String[] args) {
Demo d = new Demo(); //创建好一个线程
d.start(); //d.run(); 仅仅是对象调用方法,而线程创建了却没有运行
}
}
创建线程的第二种方法: 实现runnable接口
步骤:
1,定义类实现runnable接口
2,覆盖runnable接口中的run方法,将线程要运行的代码存放在该run方法中
3,通过Thread类建立线程对象
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
为什么要将Runnable接口的子类对象传递给Thread的构造函数
因为,自定义的run方法所属的对象是Runnable接口的子类对象
所以要让线程去执行指定对象的run方法,就必须明确该run方法所属的对象
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
public class Demo3_Thread {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable(); //4,创建Runnable的子类对
Thread t = new Thread(mr); //5,将其当作参数传递给Thread的构造函数
t.start(); //6,开启线程
for(int i = 0; i < 1000; i++) {
System.out.println("bb");
}
}
}
class MyRunnable implements Runnable { //1,定义一个类实现Runnable
@Override
public void run() { //2,重写run方法
for(int i = 0; i < 1000; i++) { //3,将要执行的代码写在run方法中
System.out.println("aaaaaaaaaaaa");
}
}
}
实现Runnable和继承方式有什么区别呢?
查看源码的区别:
* a.继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
* b.实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
实现Runnable好处:
(1)避免了单继承的局限性。在定义线程时,建议使用实现方式。 Runnable方式最为常用
(2)多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况。
继承Thread好处
(1)好处是:可以直接使用Thread类中的方法,代码简单
(2)弊端是:如果已经有了父类,就不能用这种方法
两种方式的区别:
继承Thread:线程代码存放Thread子类run方法中
实现Runnable:线程代码存放在接口子类的run方法中
匿名内部类实现线程的两种方式
- 继承Thread类
new Thread() { //1,new 类(){}继承这个类
public void run() { //2,重写run方法
for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中
System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
}
}.start();
- 实现Runnable接口
new Thread(new Runnable(){ //1,new 接口(){}实现这个接口
public void run() { //2,重写run方法
for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中
System.out.println("bb");
}
}
}).start();
获取线程的名字和设置线程的名字
原来线程都有自己默认的名称,Thread-编号 该编号从0开始。
clsaa Test extends Thread{
Test(String name){
super(name); //super(name) 调用父类实参为String name的构造器且必须放在第一行。
}
public void run(){
this.setName("线程一") //通过setName设置线程名称
for(int x=0;x<60;x++){
System.out.println(Thread.currentThread().getName()+"run...."+x) //也可以用this.getName()
}
}
}
/*
getName() ; 是获取线程名称
Thread.currentThread() ;获取当前进程对象
设置线程名称:setName 或者构造函数
*/
class ThreadDemo{
public static void main(String[] args) {
Test t1 = new Test("线程二"); //通过构造函数设置线程名称
Test t2 = new Test("线程三");
t1.start();
t2.start();
}
}
线程运行状态
Java线程在某个时刻只能处于以下七个状态中的一个。
1.New(新创建),一个线程刚刚被创建出来,还没有调用start()方法;
2.Runnable(可运行状态),当线程对象调用了start()方法的时候,线程启动进入可运行的状态。
3.Running(运行状态),获取了时间片,运行run()方法中的代码
4.Blocked(阻塞状态),表示线程阻塞于锁
5.Waiting(等待状态),表示线程进入等待状态,需要其他线程做出一些特定动作
6.Timed_Waiting(超时等待)不同于Waiting,他是在指定时间内自行返回
7.Terminated(被终止),因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
休眠线程 sleep
Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒
public class Demo3_Sleep {
public static void main(String[] args) throws InterruptedException {
new Thread() {
public void run() {
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "...aaaaaaaaaa");
}
}
}.start();
new Thread() {
public void run() {
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "...bb");
}
}
}.start();
}
}
输出结果:thread-1 和thread-0已每隔1秒的时间交替运行输出
守护线程
setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
非守护线程就相当于象棋中的帅,守护线程就相当于车马象士。非守护线程一旦结束,守护线程也就结束。
比如QQ就是非守护线程,QQ窗口就是守护线程。当QQ一关闭,QQ窗口也会自动关闭
public class Demo3_Sleep {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
public void run() {
for(int i = 0; i < 3; i++) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "...aaaaaaaaaa");
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 100; i++) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "...bb");
}
}
};
t2.setDaemon(true);
t1.start();
t2.start();
}
}
输出结果:将t2设置为守护线程,可以看到t1结束,t2没执行完也结束了。
加入线程
join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
join(int), 可以等待指定的毫秒之后继续
final Thread t1 = new Thread() { //匿名内部类在使用它所在方法中的局部变量时,必须用final修饰
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(getName() + "...aaaa");
}
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 50; i++) {
if(i == 2) {
try {
t1.join(30); //加入,有固定的时间,过了固定时间,继续交替执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName() + "...bb");
}
}
};
t1.start();
t2.start();
执行两次bb后,给aaaa执行30毫秒,再给bb执行
礼让线程
yield让出cpu,让其他线程执行
public class Demo6_Yield {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
class MyThread extends Thread {
public void run() {
for(int i = 1; i <= 1000; i++) {
if(i % 10 == 0) { //每当i是10的倍数就让出CPU
Thread.yield(); //让出CPU
}
System.out.println(getName() + "..." + i);
}
}
}
设置线程的优先级
setPriority()设置线程的优先级
1代表最低优先级
5代表默认优先级
10代表最高优先级
public class Demo7_Priority {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println(getName() + "...aaaaaaaaa" );
}
}
};
Thread t2 = new Thread(){
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println(getName() + "...bb" );
}
}
};
//t1.setPriority(10); 设置最大优先级
//t2.setPriority(1);
t1.setPriority(Thread.MIN_PRIORITY); //设置最小的线程优先级
t2.setPriority(Thread.MAX_PRIORITY); //设置最大的线程优先级
t1.start();
t2.start();
}
}
多线程的安全问题 - 线程同步
再看这个售票程序
public class Demo1_Synchronized {
/**
* @param args
* 同步代码块
*/
public static void main(String[] args) {
final Printer p = new Printer();
new Thread() {
public void run() {
while(true) {
p.print1();
}
}
}.start();
new Thread() {
public void run() {
while(true) {
p.print2();
}
}
}.start();
}
}
class Printer {
public void print1() {
System.out.print("华");
System.out.print("中");
System.out.print("科");
System.out.print("技");
System.out.print("大");
System.out.print("学");
System.out.print("\r\n");
}
public void print2() {
System.out.print("软");
System.out.print("件");
System.out.print("工");
System.out.print("程");
System.out.print("\r\n");
}
}
输出结果:
通过分析我们发现,打印出了很多错误。
多线程的运行出现了安全问题。
问题的原因在于:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。 这样就能阻止两个线程对同一个资源进行并发访问。
JAVA对于多线程的安全问题提供了专业的解决方式。 就是 同步代码块
1.什么情况下需要同步
-当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
- 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
2.同步代码块
-使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
-多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
synchronized(对象){
需要被同步的代码
}
就好比你在上厕所的时候,把门锁上,只有你执行完了之后别人才能进来使用。
public class Demo1_Synchronized {
/**
* @param args
* 同步代码块
*/
public static void main(String[] args) {
final Printer p = new Printer();
new Thread() {
public void run() {
while(true) {
p.print1();
}
}
}.start();
new Thread() {
public void run() {
while(true) {
p.print2();
}
}
}.start();
}
}
class Printer {
Demo d = new Demo();
public void print1() {
//synchronized(new Demo()) {
synchronized(d) { //同步锁,一个线程进来后就锁上,执行完才打开,另外的进程才能执,锁对象可以是任意的
System.out.print("华");
System.out.print("中");
System.out.print("科");
System.out.print("技");
System.out.print("大");
System.out.print("学");
System.out.print("\r\n");
}
}
public void print2() {
//synchronized(new Demo()) { //锁对象不能用匿名对象,因为匿名对象不是同一个对象
synchronized(d) { //上下两个锁必须一致才行
System.out.print("软");
System.out.print("件");
System.out.print("工");
System.out.print("程");
System.out.print("\r\n");
}
}
}
class Demo{}
同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁,这样才能保证安全。
必须保证同步中只能有一个线程在运行,
好处:解决了多线程的安全问题
坏处:多个线程都需要判断锁,比较消耗资源
同步方法
使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
非静态同步方法
非静态同步函数的锁是:this
还是上面的程序
class Printer {
Demo d = new Demo();
public synchronized void print1() {
System.out.print("华");
System.out.print("中");
System.out.print("科");
System.out.print("技");
System.out.print("大");
System.out.print("学");
System.out.print("\r\n");
}
}
public void print2() {
synchronized(this){
System.out.print("软");
System.out.print("件");
System.out.print("工");
System.out.print("程");
System.out.print("\r\n");
}
}
}
静态同步方法
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不是this。因为静态方法中也不可以定义this
静态进内存是,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象的类型是class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象
类名.class
class Printer {
Demo d = new Demo();
public static synchronized void print1() {
System.out.print("华");
System.out.print("中");
System.out.print("科");
System.out.print("技");
System.out.print("大");
System.out.print("学");
System.out.print("\r\n");
}
}
public void print2() {
synchronized(Printer2.class){
System.out.print("软");
System.out.print("件");
System.out.print("工");
System.out.print("程");
System.out.print("\r\n");
}
}
}
多线程安全问题
这里模拟一个售票的例子来说明多线程里的一些安全问题
public class Demo3_Ticket {
/**
* 需求:铁路售票,一共100张,通过四个窗口卖完.
*/
public static void main(String[] args) {
new Ticket().start();
new Ticket().start();
new Ticket().start();
new Ticket().start();
}
}
class Ticket extends Thread {
private static int ticket = 100; //必须是静态的才是4个线程共享100张票
//private static Object obj = new Object(); //如果用引用数据类型成员变量当作锁对象,必须是静态的,才是4个对象共享相同的成员变量
public void run() {
while(true) {
synchronized(Ticket.class) {
if(ticket <= 0) {
break;
}
try {
Thread.sleep(10); //线程1睡,线程2睡,线程3睡,线程4睡
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "...这是第" + ticket-- + "号票");
}
}
}
}
火车售票例子用runnable接口实现
public class Demo4_Ticket {
/**
* @param args
* 火车站卖票的例子用实现Runnable接口
*/
public static void main(String[] args) {
MyTicket mt = new MyTicket();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
/*Thread t1 = new Thread(mt); //多次启动一个线程是非法的
t1.start();
t1.start();
t1.start();
t1.start();*/
}
}
class MyTicket implements Runnable {
private int tickets = 100; //这里不用定义成static,因为只之创建一个对象mt
@Override
public void run() {
while(true) {
synchronized(Ticket.class) { //锁对象也可以用this,应为只创建一次对象,this就代表mt
if(tickets <= 0) {
break;
}
try {
Thread.sleep(10); //线程1睡,线程2睡,线程3睡,线程4睡
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "...这是第" + tickets-- + "号票");
}
}
}
}