Day24
-
多线程
(1) 什么时线程
线程时程序执行的一条路径,一个进程中可以包含多条线程
多线程并发执行可以提高程序的效率,可以同时完成多项工作。
(2) 多线程的应用场景
(1) 红蜘蛛同时共享屏幕给多个电脑
(2) 迅雷开启多线程一起下载
(3) QQ同时和多个人一起视频
(4) 服务器同时处理多个客户端的请求 -
多线程(多线程并行和并发的区别)
(1) 并行就是两个任务同时运行,就是甲任务进行的时候,乙任务也在进行。(需要多核CPU)
(2) 并发是指两个任务都请求运行,而处理器只能接受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
(3) 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,右手操作一个电脑跟乙聊,就叫并行。
(4) 如果用一台电脑我先给甲发个消息,然后立刻给乙发个消息,然后再跟甲聊,再跟乙聊。这就叫并发。 -
多线程(Java程序运行原理和JVM的启动是多线程的吗)
(1) Java程序运行原理
Java命令会启动Java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程”,然后主线程取调用某个类的main方法。
(2) JVM的启动是多线程的吗?
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程。
public class Demo1_Thread {
/*
证明jvm是多先程的
*/
public static void main(String[] args) {
for (int i = 0 ; i < 1000000 ; i ++){ //垃圾线程
new Demo();
}
for(int i = 0; i < 100000 ; i++){
System.out.println("我是主线程的执行代码");
}
}
}
class Demo {
public void finalize(){
System.out.println("垃圾被清扫了");
}
}
- 多线程(多线程程序实现的方式1)
(1) 继承Thread
定义类继承Thread
重写run方法
把新线程要做的事情写再run方法中。
创建线程对象
开启新线程,内部会自动执行run方法。
public class Demo2_Thread {
public static void main(String[] args) {
MyThread mt = new MyThread(); //4:创建Thread类的子类对象
mt.start(); //5:开启线程
for(int i = 0; i < 1000; i++){
System.out.println("bbb");
}
}
}
class MyThread extends Thread { //1:继承Thread
public void run() { //2:重写run方法
for (int i = 0;i < 1000; i++){ //3:将要执行的代码放在run方法中
System.out.println("aaaaaaaaaa");
}
}
}
- 多线程(多线程程序实现的方式2)
(1) 实现Runnable
定义类实现Runnable接口
实现run方法
把新线程要做的事写在run方法中
创建自定义的Runnable的子类对象
创建Thread对象,传入Runnable
调用star()开启新线程,内部会自动调用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 < 10000; i++){
System.out.println("bbbv");
}
}
}
class MyRunnable implements Runnable { //1:定义一个类实现Runnable
@Override
public void run() { //2:重写run方法
for(int i = 0; i < 10000; i++){ //3:将要执行的代码写在run方法中
System.out.println("aaaaaaaa");
}
}
}
- 多线程(实现Runnable的原理)
查看源码:
(1) 看Thread类的构造函数,传递了Runnable接口的引用
(2) 通过init()方法找到传递的target给成员变量的target赋值
(3) 查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法。 - 多线程(两种方式的区别)
(1)查看源码的区别:
继承Thread: 由于子类重写了Thread类的run(),当调用start()时,直接找子类的run()方法。
实现Runnable:构造函数中传入了Runnable的引用,成员变量记住了它,start()调用run()方法时内部判断成员变量Runnable的引用是否为空,不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法。
(2) 继承Thread
好处是:可以直接使用Thread类中的方法,代码简单
弊端是:如果已经有了父类,就不能用这种方法
(3) 实现Runnable接口
好处是:即使自己的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的。
弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂。 - 多线程(匿名内部类实现线程的两种方式)
继承Thread类
实现Runnable接口
public class Demo4_Thread {
public static void main(String[] args) {
new Thread(){ //1:继承Thread类
public void run(){ //2:重写run方法
for(int i = 0; i < 1000; i++) { //3:将要执行的代码写在run方法中
System.out.println("aaaaaaaa");
}
}
}.start(); //4:开启线程
new Thread( new Runnable(){ //1:将Runabble的子类对象传递给Thread的构造方法
public void run(){ //2:重写run方法
for(int i = 0;i < 10000; i++){ //3:将要执行的代码写在run方法中
System.out.println("bb");
}
}
}).start(); //4:开启线程
}
}
- 多线程(获取名字和设置名字)
(1) 获取名字
通过getName()方法获取线程对象的名字
(2) 设置名字
通过构造函数可以传入String类型的名字
通过setName(String)方法可以设置线程对象的名字
public class Demo1_Name {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
System.out.println(this.getName() + "...aaaaaa");
}
};
Thread t2 = new Thread(){
public void run(){
System.out.println(this.getName() + "...bbb");
}
};
t1.setName("1233");
t2.setName("1122");
t1.start();
t2.start();
}
private static void demo2() {
new Thread(){
public void run(){
this.setName("张三"); //通过setName设置名字
System.out.println(this.getName() + "...aaaaaa");
}
}.start();
new Thread(){
public void run(){
this.setName("李四");
System.out.println(this.getName() + "...bbb");
}
}.start();
}
private static void demo1() {
new Thread("111"){ //通过构造方法命名
public void run(){
System.out.println(this.getName() + "...aaaaaa");
}
}.start();
new Thread("222"){
public void run(){
System.out.println(this.getName() + "..bbb");
}
}.start();
}
}
- 多线程(获取当前线程的对象)
Thread.currentThread(),主线程也可以获取
public class Demo2_CurrentThread {
public static void main(String[] args) {
new Thread(){
public void run(){
System.out.println(getName() + "....aaaa");
}
}.start();
new Thread(new Runnable() {
public void run() {
//Thread.currentThread()获取当前正在执行的线程
System.out.println(Thread.currentThread().getName() + "...b");
}
}).start();
Thread.currentThread().setName("我是主线程"); //更改主线程的名字
System.out.println(Thread.currentThread().getName()) ;//获取主线程的名称
}
}
11. 多线程(休眠线程)
Thread.sleep(毫秒,纳秒),控制当前线程休眠若干毫秒1秒=1000毫秒,
1秒=1000*10000*1000纳秒 1000000000
public class Demo3_Sleep {
/*
Thread.sleep(毫秒,纳秒),控制当前线程休眠若干毫秒1秒=1000毫秒,
1秒=1000*10000*1000纳秒 1000000000
*/
public static void main(String[] args) throws InterruptedException {
new Thread(){
public void run(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0; i < 100; i++){
System.out.println(getName() + "..aaa");
}
}
}.start();
new Thread(){
public void run(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0; i < 100; i++){
System.out.println(getName() + "..bbb");
}
}
}.start();
}
private static void demo1() throws InterruptedException {
for(int i = 20; i >= 0 ; i --){
Thread.sleep(1000);
System.out.println("倒计时第" + i + "秒");
}
}
}
- setDaemon(),设置一个线程为守护线程,该线程不会单独执行,当其他非守护线程都执行结束后,自动退出
public class Demo4_SetDaemon {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
for(int i = 0; i < 2; i ++){
System.out.println(getName() + "....aaaaa");
}
}
};
Thread t2 = new Thread(){
public void run(){
for(int i = 0; i < 50; i ++){
System.out.println(getName() + "....bb");
}
}
};
t2.setDaemon(true); //当传入true就是意味着设置成守护线程
t1.start();
t2.start();
}
}
- 多线程(加入线程)
join(),当前线程暂停,等待指定的线程执行结束后,当前线程再继续
join(int),可以等待指定的毫秒之后继续
public class Demo5_Join {
final public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
for (int i = 0; i < 10; i++){
System.out.println(getName() + "...aaaaa");
}
}
};
Thread t2 = new Thread(){
public void run(){
for (int i = 0; i < 10; i++){
if(i == 2){
try {
// t1.join();
t1.join(1);//设置插队时间,过了指定时间后,两条线程交替执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(getName() + "...bbb");
}
}
};
t1.start();
t2.start();
}
}
- 多线程(礼让线程)
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){
Thread.yield(); //让出cpu
}
System.out.println(getName() + "..." + i);
}
}
}
- 多线程(设置线程的优先级)
setPriority()设置线程的优先级
public class Demo7_SetPriority {
public static void main(String[] args) {
Thread t1 = new Thread(){
public void run(){
for(int i = 0; i < 1000; i++){
System.out.println(getName() + "...aaaaaa");
}
}
};
Thread t2 = new Thread(){
public void run(){
for(int i = 0; i < 1000; i++){
System.out.println(getName() + "...dd");
}
}
};
// t1.setPriority(10); //设置最大优先级
// t2.setPriority(5);
t1.setPriority(Thread.MAX_PRIORITY); //设置最大优先级
t2.setPriority(Thread.MIN_PRIORITY);//设置最小优先级
t1.start();
t2.start();
}
}
- 多线程(同步代码块)
(1) 什么情况下需要同步
当多线程并发,有多段代码同时执行时,我们希望某一段代码执行的过程中CPU不要切换到其他线程工作,这时需要同步。
如果两段代码是同步的,那么同时只能执行一段,再一段代码执行没有结束之前,不会执行另外一段代码。
(2) 同步代码块
使用synchronized关键字加上一个锁对象来定义一段代码,这就交同步代码块。
多个同步代码块如果使用相同的锁对象,那么他们就是同步的。
public class Demo1_Synchronized {
/*
同步代码块
*/
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 (d) { //同步代码块锁机制,锁对象可以是任意的,但是不可以是匿名的
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
}
}
public void print2() {
synchronized (d) {
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
}
}
}
class Demo {
}
- 多线程(同步方法)
使用synchronized关键字修饰一个方法,该方法中所有的代码都是同步的
1:非静态的同步方法的锁对象是什么?
答:非静态的同步方法锁对象是this
2:静态的同步方法的锁对象是什么?
答:静态的同步方法锁对象是该类的字节码对象
public class Demo2_Syn {
public static void main(String[] args) {
final com.heima.syn.Printer p = new com.heima.syn.Printer();
new Thread() {
public void run (){
while (true){
p.print1();
}
}
}.start();
new Thread() {
public void run (){
while (true){
p.print2();
}
}
}.start();
}
}
class Printer2 {
Demo d = new Demo();
//非静态的同步方法的锁对象是什么?
//非静态的同步方法锁对象是this
//静态的同步方法的锁对象是什么?
//静态的同步方法锁对象是该类的字节码对象
public static synchronized void print1() { //同步方法,只需要在方法上加 synchronized 关键字即可
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
}
public static void print2() {
synchronized (Printer2.class) {
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
}
}
}
- 多线程(线程安全问题)
多线程并发操作同一数据时,就有可能出现线程安全问题
使用同步技术可以解决这种问题,把操作数据的代码进行同步,不要多个线程一起操作
练习:铁路售票,一共100张,通过4个窗口售完
public class Demo3_Ticket {
/*
练习:铁路售票,一共100张,通过4个窗口售完
*/
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; //定义成静态的,可以让四个窗口共享
// private static Object obj = new Object(); //如果是成员变量当作锁对象,必须是静态的
public void run(){
while (true) {
//synchronized (obj) { 成员变量当作锁对象
synchronized (Ticket.class) {
if (ticket == 0) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "这是第" + ticket-- + "号票");
}
}
}
}
- 多线程(火车站卖票额例子用实现Runnable接口)
public class Demo4_Ticket {
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();
}
}
class MyTicket implements Runnable {
private int tickets = 100; //此处不需要静态,因为这种方式只需要创建1次对象
@Override
public void run() {
while (true) {
//synchronized (obj) { 成员变量当作锁对象
synchronized (Ticket.class) {
if (tickets == 0) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "这是第" + tickets-- + "号票");
}
}
}
}
- 多线程(死锁)
多线程同步的时候,如果同步代码嵌套,使用相同锁,就有可能出现死锁
尽量不要嵌套使用
public class Demo5_Lock {
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
new Thread(){
public void run(){
while (true){
synchronized (s1){
System.out.println(getName()+"获取" + s1 + "等待" + s2);
synchronized (s2){
System.out.println(getName() +"拿到" + s2 + "开吃");
}
}
}
}
}.start();
new Thread(){
public void run(){
while (true){
synchronized (s2){
System.out.println(getName()+"获取" + s2+ "等待" + s1);
synchronized (s1){
System.out.println(getName() +"拿到" + s1 + "开吃");
}
}
}
}
}.start();
}
}
- 多线程(以前的线程安全的类回顾)
(1) 回顾以前的线程安全问题
看源码:Vector ,StringBuffer,Hashtable,Collections,synchronized(xxx)
Vector是线程安全的,ArrayList是线程不安全的;
StringBuffer是线程安全的,StringBuilder是线程不安全的;
Hashtable是线程安全的,HashMap是线程不安全的;
Collections.synchronized(xxx)可以将线程不安全的变为安全的;