多线程

  • 1>多线程基础
    • 1.1、什么是线程?什么是进程?
    • 1.2、Java程序中的进程与线程
    • 1.3、线程与进程的关系
    • 1.4、线程与线程的关系
    • 1.5、单核CPU可以实现多线程并发吗?
  • 2>Java中实现进程的两种方式
    • 2.1、继承Thread接口
    • 2.2、实现Runnable接口
  • 3>关于线程的几种情况
    • 3.1、使用匿名内部类的方式启动线程
    • 3.2、判断以下程序几个线程?
    • 3.3、方法run和start的区分
  • 4>线程的常用方法
    • 4.1、关于sleep的面试题
    • 4.2、三种方式终止一个线程的进行
      • 4.2.1、强制终止
      • 4.2.2、利用异常处理机制终止
      • 4.2.3、合理终止
  • 5>线程对象的生命周期
  • 6>线程的调度
    • 6.1、常用的线程调度模型
    • 6.2、Java中的调度有关的方法
    • 6.2.1、关于优先级
      • 6.2.2、关于yield()方法
      • 6.2.3、关于join()方法
  • 7>多线程并发环境下数据的安全问题
    • 7.1、为什么是重点?
    • 7.2、什么时候数据在多线程并发的环境下会存在安全问题?
    • 7.3、如何解决线程安全问题
    • 7.4、异步与同步
  • 8>三大变量的线程安全
  • 9>synchronized用法
    • 9.1、第一种
    • 9.2、第二种
    • 9.3、第三种
    • 9.4、演示银行取钱系统
      • 9.4.1、账户类
      • 9.4.2、账户线程类
      • 9.4.3、测试案例
      • 9.4.4、测试结果
    • 9.5、关于synchronized的面试题
      • 9.5.1、第一题
      • 9.5.2、第二题
      • 9.5.3、第三题
    • 9.6、死锁
  • 10>开发中解决线程安全问题
  • 11>第三种实现线程的方式
  • 12>守护线程
  • 13>定时器
  • 14>wait和notify方法

1>多线程基础

1.1、什么是线程?什么是进程?

 进程是一个应用程序(1个进程是一个软件)。线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。

1.2、Java程序中的进程与线程

 java HelloWorld 回车之后。会先启动JVM,而JVM就是一个进程。JVM再启动一个主线程调用main方法。同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。

1.3、线程与进程的关系

举个例子:阿里巴巴:进程马云:阿里巴巴的一个线程员工:阿里巴巴的一个线程京东:进程强东:京东的一个线程员工:京东的一个线程进程可以看做是现实生活当中的公司。线程可以看做是公司当中的某个员工。火车站,可以看做是一个进程。火车站中的每一个售票窗口可以看做是一个线程。我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。java中之所以有多线程机制,目的就是为了提高程序的处理效率。
注意:进程A和进程B的内存独立不共享。(阿里巴巴和京东资源不会共享的!)魔兽游戏是一个进程酷狗音乐是一个进程这两个进程是独立的,不共享资源。

1.4、线程与线程的关系

 线程A和线程B在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
  • 思考一个问题:使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。
  • main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。

1.5、单核CPU可以实现多线程并发吗?

 对于多核的CPU电脑来说,真正的多线程并发是没问题的。4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。什么是真正的多线程并发?t1线程执行t1的。t2线程执行t2的。t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。单核的CPU表示只有一个大脑:不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情同时在做!线程A:播放音乐线程B:运行魔兽游戏线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,给我们的感觉是同时并发的。电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像一根钢针扎到手上,到最终感觉到疼,这个过程是需要“很长的”时间的,在这个期间计算机可以进行亿万次的循环。所以计算机的执行速度很快。

2>Java中实现进程的两种方式

  • 注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。

2.1、继承Thread接口

  • java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。
  • 继承Thread,并重写run()方法。
  • 注意这里的run()方法不能上抛异常,因为重写的方法不能抛出比父类范围更大的异常
public class MyThread extends Thread{public void run(){}
}

2.2、实现Runnable接口

  • 实现Runnable接口,并重写run()方法
public class MyThread implements Runnable{public void run(){}
}

3>关于线程的几种情况

3.1、使用匿名内部类的方式启动线程

  • start()方法的作用是:启动一个分支线程,在JVMzho开辟一个新的空间,这段代码结束后,瞬间就完成了。
  • 启动成功的线程会自动调用run()方法,并且run方法位于分支栈底部(压栈)
  • run()方法在分支栈底部,main()方法也在主栈底部,二者平级
public static void main(String[] args){Thread thread = new Thread(new Runnable(){public void run(){}});thread.start();
}

3.2、判断以下程序几个线程?

  • 排除垃圾回收这个线程的情况下
public class ThreadTest01 {public static void main(String[] args) {System.out.println("main begin");m1();System.out.println("main over");}private static void m1() {System.out.println("m1 begin");m2();System.out.println("m1 over");}private static void m2() {System.out.println("m2 begin");m3();System.out.println("m2 over");}private static void m3() {System.out.println("m3 execute!");}
}

3.3、方法run和start的区分


public class ThreadTest {public static void main(String[] args) {MyThread t = new MyThread();//t.run();t.start();// 这里的代码还是运行在主线程中。for(int i = 0; i < 1000; i++){System.out.println("主线程--->" + i);}}
}class MyThread extends Thread {public void run() {for(int i = 0; i < 1000; i++){System.out.println("分支线程--->" + i);}}
}

4>线程的常用方法

1、怎么获取当前线程对象?
Thread t = Thread.currentThread();
返回值t就是当前线程。
注意这是个静态方法,所以与谁调用它没关系,它在哪个线程中就返回当前线程对象2、获取线程对象的名字
String name = 线程对象.getName();3、修改线程对象的名字
线程对象.setName("线程名字");4、当线程没有设置名字的时候,默认的名字有什么规律?(了解一下)
Thread-0
Thread-1
Thread-2
Thread-3
.....
主线程 名字 main
5、sleep方法:
static void sleep(long millis)1)静态方法:Thread.sleep(1000);2)参数是毫秒3)作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。这行代码出现在A线程中,A线程就会进入休眠。这行代码出现在B线程中,B线程就会进入休眠。4)Thread.sleep()方法,可以做到这种效果:间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
public class ThreadTest05 {public void doSome(){String name = Thread.currentThread().getName();System.out.println("------->" + name);}public static void main(String[] args) {ThreadTest05 tt = new ThreadTest05();tt.doSome();//currentThread就是当前线程对象// 这个代码出现在main方法当中,所以当前线程就是主线程。Thread currentThread = Thread.currentThread();System.out.println(currentThread.getName()); //main// 创建线程对象MyThread2 t = new MyThread2();// 设置线程的名字t.setName("t1");// 获取线程的名字String tName = t.getName();System.out.println(tName); //Thread-0MyThread2 t2 = new MyThread2();t2.setName("t2");System.out.println(t2.getName()); //Thread-1t2.start();// 启动线程t.start();}
}class MyThread2 extends Thread {public void run(){for(int i = 0; i < 100; i++){// currentThread就是当前线程对象。当前线程是谁呢?// 当t1线程执行run方法,那么这个当前线程就是t1// 当t2线程执行run方法,那么这个当前线程就是t2Thread currentThread = Thread.currentThread();System.out.println(currentThread.getName() + "-->" + i);//System.out.println(super.getName() + "-->" + i);//System.out.println(this.getName() + "-->" + i);}}
}

4.1、关于sleep的面试题

package com.bjpowernode.java.thread;
/*
关于Thread.sleep()方法的一个面试题:*/
public class ThreadTest07 {public static void main(String[] args) {// 创建线程对象Thread t = new MyThread3();t.setName("t");t.start();// 调用sleep方法try {// 问题:这行代码会让线程t进入休眠状态吗?t.sleep(1000 * 5);// 在执行的时候还是会转换成:Thread.sleep(1000 * 5);// 这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠。// 这样代码出现在main方法中,main线程睡眠。} catch (InterruptedException e) {e.printStackTrace();}// 5秒之后这里才会执行。System.out.println("hello World!");}
}class MyThread3 extends Thread {public void run(){for(int i = 0; i < 10000; i++){System.out.println(Thread.currentThread().getName() + "--->" + i);}}
}

4.2、三种方式终止一个线程的进行

4.2.1、强制终止

package com.bjpowernode.java.thread;
/*
在java中怎么强行终止一个线程的执行。这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。不建议使用。*/
public class ThreadTest09 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable3());t.setName("t");t.start();// 模拟5秒try {Thread.sleep(1000 * 5);} catch (InterruptedException e) {e.printStackTrace();}// 5秒之后强行终止t线程t.stop(); // 已过时(不建议使用。)}
}class MyRunnable3 implements Runnable {@Overridepublic void run() {for(int i = 0; i < 10; i++){System.out.println(Thread.currentThread().getName() + "--->" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

4.2.2、利用异常处理机制终止

package com.bjpowernode.java.thread;
/*
sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么叫醒一个正在睡眠的线程??注意:这个不是终断线程的执行,是终止线程的睡眠。*/
public class ThreadTest08 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable2());t.setName("t");t.start();// 希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了。)try {Thread.sleep(1000 * 5);} catch (InterruptedException e) {e.printStackTrace();}// 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)t.interrupt(); // 干扰,一盆冷水过去!}
}
class MyRunnable2 implements Runnable {// 重点:run()当中的异常不能throws,只能try catch// 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "---> begin");try {// 睡眠1年Thread.sleep(1000 * 60 * 60 * 24 * 365);} catch (InterruptedException e) {// 打印异常信息//e.printStackTrace();}//1年之后才会执行这里System.out.println(Thread.currentThread().getName() + "---> end");
}

4.2.3、合理终止

package com.bjpowernode.java.thread;
/*
怎么合理的终止一个线程的执行。这种方式是很常用的。*/
public class ThreadTest10 {public static void main(String[] args) {MyRunable4 r = new MyRunable4();Thread t = new Thread(r);t.setName("t");t.start();// 模拟5秒try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}// 终止线程// 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。r.run = false;}
}class MyRunable4 implements Runnable {// 打一个布尔标记boolean run = true;@Overridepublic void run() {for (int i = 0; i < 10; i++){if(run){System.out.println(Thread.currentThread().getName() + "--->" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}else{// return就结束了,你在结束之前还有什么没保存的。// 在这里可以保存呀。//save....//终止当前线程return;}}}
}

5>线程对象的生命周期

  • 新建状态
  • 就绪状态
  • 运行状态
  • 阻塞状态
  • 死亡状态

6>线程的调度

6.1、常用的线程调度模型

 抢占式调度模型:那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。java采用的就是抢占式调度模型。均分式调度模型:平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式。

6.2、Java中的调度有关的方法

实例方法:void setPriority(int newPriority) 设置线程的优先级int getPriority() 获取线程优先级最低优先级1默认优先级是5最高优先级10优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)静态方法:static void yield()  让位方法暂停当前正在执行的线程对象,并执行其他线程yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。注意:在回到就绪之后,有可能还会再次抢到。

6.2.1、关于优先级

package com.bjpowernode.java.thread;
public class ThreadTest11 {public static void main(String[] args) {// 设置主线程的优先级为1Thread.currentThread().setPriority(1);/*System.out.println("最高优先级" + Thread.MAX_PRIORITY);System.out.println("最低优先级" + Thread.MIN_PRIORITY);System.out.println("默认优先级" + Thread.NORM_PRIORITY);*/// 获取当前线程对象,获取当前线程的优先级Thread currentThread = Thread.currentThread();// main线程的默认优先级是:5//System.out.println(currentThread.getName() + "线程的默认优先级是:" + currentThread.getPriority());Thread t = new Thread(new MyRunnable5());t.setPriority(10);t.setName("t");t.start();// 优先级较高的,只是抢到的CPU时间片相对多一些。// 大概率方向更偏向于优先级比较高的。for(int i = 0; i < 10000; i++){System.out.println(Thread.currentThread().getName() + "-->" + i);}}
}class MyRunnable5 implements Runnable {public void run() {// 获取线程优先级//System.out.println(Thread.currentThread().getName() + "线程的默认优先级:" + Thread.currentThread().getPriority());for(int i = 0; i < 10000; i++){System.out.println(Thread.currentThread().getName() + "-->" + i);}}
}

6.2.2、关于yield()方法

  • 这里的让位与sleep()不同的地方在于,sleep()是需要进入阻塞状态,而yield()是直接进入就绪状态,立刻拥有抢断CPU时间片的权利
/*
让位,当前线程暂停,回到就绪状态,让给其它线程。
静态方法:Thread.yield();*/
public class ThreadTest12 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable6());t.setName("t");t.start();for(int i = 1; i <= 10000; i++) {System.out.println(Thread.currentThread().getName() + "--->" + i);}}
}class MyRunnable6 implements Runnable {public void run() {for(int i = 1; i <= 10000; i++) {//每100个让位一次。if(i % 100 == 0){Thread.yield(); // 当前线程暂停一下,让给主线程。}System.out.println(Thread.currentThread().getName() + "--->" + i);}}
}

6.2.3、关于join()方法

  • 将调用该方法的对象合并到当前线程中,并让该线程结束后,在继续原来所处的进程
/*
线程合并*/
public class Test {public static void main(String[] args) {System.out.println("main begin");Thread t = new Thread(new MyRunnable7());t.setName("t");t.start();//合并线程try {t.join(); // t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。} catch (InterruptedException e) {e.printStackTrace();}System.out.println("main over");}
}class MyRunnable7 implements Runnable {public void run() {for(int i = 0; i < 10000; i++){System.out.println(Thread.currentThread().getName() + "--->" + i);}}
}

7>多线程并发环境下数据的安全问题

7.1、为什么是重点?

     以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。

7.2、什么时候数据在多线程并发的环境下会存在安全问题?

三个条件:条件1:多线程并发。条件2:有共享数据。条件3:共享数据有修改的行为。满足以上3个条件之后,就会存在线程安全问题。

7.3、如何解决线程安全问题

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在
线程安全问题,怎么解决这个问题?线程排队执行(不能并发)用排队执行解决线程安全问题。这种机制被称为:线程同步机制。专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。怎么解决线程安全问题呀?使用“线程同步机制”。线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。

7.4、异步与同步

异步编程模型:线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)异步就是并发。
同步编程模型:线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。同步就是排队。

8>三大变量的线程安全

  • 实例变量:在堆中。

  • 静态变量:在方法区。

  • 局部变量:在栈中。

     以上三大变量中:局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)局部变量在栈中。所以局部变量永远都不会共享。实例变量在堆中,堆只有1个。静态变量在方法区中,方法区只有1个。堆和方法区都是多线程共享的,所以可能存在线程安全问题。局部变量+常量:不会有线程安全问题。成员变量:可能会有线程安全问题。如果使用局部变量的话:建议使用:StringBuilder。因为局部变量不存在线程安全问题。选择StringBuilder。StringBuffer效率比较低。ArrayList是非线程安全的。Vector是线程安全的。HashMap HashSet是非线程安全的。Hashtable是线程安全的。
    

9>synchronized用法

9.1、第一种

  • 同步代码块(灵活)
         synchronized(线程共享对象){同步代码块;}

9.2、第二种

  • 在实例方法上使用synchronized
  • 表示共享对象一定是this
  • 并且同步代码块是整个方法体。

9.3、第三种

  • 在静态方法上使用synchronized
  • 表示找类锁。
  • 类锁永远只有1把。
  • 就算创建了100个对象,那类锁也只有一把。
  • 对象锁:1个对象1把锁,100个对象100把锁。
  • 类锁:100个对象,也可能只是1把类锁。

9.4、演示银行取钱系统

9.4.1、账户类

package com03;public class Account {private String name;private int balance;public Account(String name, int balance) {this.name = name;this.balance = balance;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getBalance() {return balance;}public void setBalance(int balance) {this.balance = balance;}/*在实例方法上可以使用synchronized吗?可以的。synchronized出现在实例方法上,一定锁的是this。没得挑。只能是this。不能是其他的对象了。所以这种方式不灵活。另外还有一个缺点:synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低。所以这种方式不常用。synchronized使用在实例方法上有什么优点?代码写的少了。节俭了。如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。*///public synchronized void withdraw(int money){public void withdraw(int money){//Object obj2 = new Object();//synchronized (obj) {//synchronized ("abc") { // "abc"在字符串常量池当中。//结果就是对一个账户进行操作时所有账户都等待//synchronized (null) { // 报错:空指针。//synchronized (obj2) { // 这样编写就不安全了。因为obj2不是共享对象//synchronized(this){// synchronized后面小括号中传的这个“数据”是相当关键的。//这个数据必须是多线程共享的数据。才能达到多线程排队。/*()中写什么?那要看你想让哪些线程同步。假设t1、t2、t3、t4、t5,有5个线程,你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。这里的共享对象是:账户对象。账户对象是共享的,那么this就是账户对象吧!!!不一定是this,这里只要是多线程共享的那个对象就行。1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。直到同步代码块代码结束,这把锁才会释放。3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后t2占有这把锁之后,进入同步代码块执行程序。这样就达到了线程排队执行。这里需要注意的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。
*/int before = this.getBalance();int after =  before - money;try {Thread.sleep(1);//模拟网络延迟} catch (InterruptedException e) {e.printStackTrace();}this.setBalance(after);System.out.println("取完" + money +"元后,剩余"+after+"元");//}}
}

9.4.2、账户线程类

package com03;public class AccountThread extends Thread{private Account account = null;public AccountThread(Account account) {this.account = account;}public void run(){account.withdraw(5000);}
}

9.4.3、测试案例

package com03;public class Test {public static void main(String[] args) {Account account = new Account("Account-1",10000);AccountThread accountThread1 = new AccountThread(account);AccountThread accountThread2 = new AccountThread(account);//给定两个取钱的用户对同一账户进行取钱//若中途遭遇网络延迟,用户A对账户取钱5000,但此时余额balance//没有立刻更新,导致用户B也对账户在原来基础上取钱5000,最终余额//也是5000accountThread1.start();accountThread2.start();}
}

9.4.4、测试结果

取完5000元后,剩余5000元
取完5000元后,剩余5000元

9.5、关于synchronized的面试题

9.5.1、第一题

package com.bjpowernode.java.exam1;// 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?//不需要,因为doOther()方法没有synchronized
public class Exam01 {public static void main(String[] args) throws InterruptedException {MyClass mc = new MyClass();Thread t1 = new MyThread(mc);Thread t2 = new MyThread(mc);t1.setName("t1");t2.setName("t2");t1.start();Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。t2.start();}
}class MyThread extends Thread {private MyClass mc;public MyThread(MyClass mc){this.mc = mc;}public void run(){if(Thread.currentThread().getName().equals("t1")){mc.doSome();}if(Thread.currentThread().getName().equals("t2")){mc.doOther();}}
}class MyClass {public synchronized void doSome(){System.out.println("doSome begin");try {Thread.sleep(1000 * 10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("doSome over");}public void doOther(){System.out.println("doOther begin");System.out.println("doOther over");}
}

9.5.2、第二题

package com.bjpowernode.java.exam2;// 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?//需要//简单的说,在一个类中被synchronized修饰的同属于一个对象锁//所以需要等待,而一个类中一个被修饰一个没有被修饰//则未被修饰的无法被锁
public class Exam01 {public static void main(String[] args) throws InterruptedException {MyClass mc = new MyClass();Thread t1 = new MyThread(mc);Thread t2 = new MyThread(mc);t1.setName("t1");t2.setName("t2");t1.start();Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。t2.start();}
}class MyThread extends Thread {private MyClass mc;public MyThread(MyClass mc){this.mc = mc;}public void run(){if(Thread.currentThread().getName().equals("t1")){mc.doSome();}if(Thread.currentThread().getName().equals("t2")){mc.doOther();}}
}class MyClass {public synchronized void doSome(){System.out.println("doSome begin");try {Thread.sleep(1000 * 10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("doSome over");}public synchronized void doOther(){System.out.println("doOther begin");System.out.println("doOther over");}
}

9.5.3、第三题

package com.bjpowernode.java.exam3;// 面试题:doOther方法执行的时候需要等待doSome方法的结束吗?//不需要,因为MyClass对象是两个,两把锁。
public class Exam01 {public static void main(String[] args) throws InterruptedException {MyClass mc1 = new MyClass();MyClass mc2 = new MyClass();Thread t1 = new MyThread(mc1);Thread t2 = new MyThread(mc2);t1.setName("t1");t2.setName("t2");t1.start();Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。t2.start();}
}class MyThread extends Thread {private MyClass mc;public MyThread(MyClass mc){this.mc = mc;}public void run(){if(Thread.currentThread().getName().equals("t1")){mc.doSome();}if(Thread.currentThread().getName().equals("t2")){mc.doOther();}}
}class MyClass {public synchronized void doSome(){System.out.println("doSome begin");try {Thread.sleep(1000 * 10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("doSome over");}public synchronized void doOther(){System.out.println("doOther begin");System.out.println("doOther over");}
}

9.6、死锁

package com.bjpowernode.java.deadlock;
/*
死锁代码要会写。
一般面试官要求你会写。
只有会写的,才会在以后的开发中注意这个事儿。
因为死锁很难调试。*/
public class DeadLock {public static void main(String[] args) {Object o1 = new Object();Object o2 = new Object();// t1和t2两个线程共享o1,o2Thread t1 = new MyThread1(o1,o2);Thread t2 = new MyThread2(o1,o2);t1.start();t2.start();}
}class MyThread1 extends Thread{Object o1;Object o2;public MyThread1(Object o1,Object o2){this.o1 = o1;this.o2 = o2;}public void run(){synchronized (o1){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o2){}}}
}class MyThread2 extends Thread {Object o1;Object o2;public MyThread2(Object o1,Object o2){this.o1 = o1;this.o2 = o2;}public void run(){synchronized (o2){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o1){}}}
}

10>开发中解决线程安全问题

是一上来就选择线程同步吗?synchronized不是,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。第一种方案:尽量使用局部变量代替“实例变量和静态变量”。第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,
对象不共享,就没有数据安全问题了。)第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候
就只能选择synchronized了。线程同步机制。

11>第三种实现线程的方式

package com.bjpowernode.java.thread;import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask; // JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。/*
实现线程的第三种方式:实现Callable接口这种方式的优点:可以获取到线程的执行结果。这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。*/
public class ThreadTest15 {public static void main(String[] args) throws Exception {// 第一步:创建一个“未来任务类”对象。// 参数非常重要,需要给一个Callable接口实现类对象。FutureTask task = new FutureTask(new Callable() {public Object call() throws Exception {// call()方法就相当于run方法。只不过这个有返回值// 线程执行一个任务,执行之后可能会有一个执行结果// 模拟执行System.out.println("call method begin");Thread.sleep(1000 * 10);System.out.println("call method end!");int a = 100;int b = 200;return a + b; //自动装箱(300结果变成Integer)}});// 创建线程对象Thread t = new Thread(task);// 启动线程t.start();// 这里是main方法,这是在主线程中。// 在主线程中,怎么获取t线程的返回结果?// get()方法的执行会导致“当前线程阻塞”Object obj = task.get();System.out.println("线程执行结果:" + obj);// main方法这里的程序要想执行必须等待get()方法的结束// 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果// 另一个线程执行是需要时间的。System.out.println("hello world!");}
}

12>守护线程

java语言中线程分为两大类:一类是:用户线程一类是:守护线程(后台线程)其中具有代表性的就是:垃圾回收线程(守护线程)。守护线程的特点:一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。注意:主线程main方法是一个用户线程。守护线程用在什么地方呢?每天00:00的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,没到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。
package com.bjpowernode.java.thread;
/*
守护线程*/
public class ThreadTest14 {public static void main(String[] args) {Thread t = new BakDataThread();t.setName("备份数据的线程");// 启动线程之前,将线程设置为守护线程t.setDaemon(true);//这样用户线程结束时,该线程才会跟着结束t.start();// 主线程:主线程是用户线程for(int i = 0; i < 10; i++){System.out.println(Thread.currentThread().getName() + "--->" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}class BakDataThread extends Thread {public void run(){int i = 0;// 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。while(true){System.out.println(Thread.currentThread().getName() + "--->" + (++i));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

13>定时器

     定时器的作用:间隔特定的时间,执行特定的程序。每周要进行银行账户的总账操作。每天要进行数据的备份操作。在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
package com.bjpowernode.java.thread;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;/*
使用定时器指定定时任务。*/
public class TimerTest {public static void main(String[] args) throws Exception {// 创建定时器对象Timer timer = new Timer();//Timer timer = new Timer(true); //守护线程的方式// 指定定时任务//timer.schedule(定时任务, 第一次执行时间, 间隔多久执行一次);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date firstTime = sdf.parse("2020-03-14 09:34:30");//timer.schedule(new LogTimerTask() , firstTime, 1000 * 10);// 每年执行一次。//timer.schedule(new LogTimerTask() , firstTime, 1000 * 60 * 60 * 24 * 365);//匿名内部类方式timer.schedule(new TimerTask(){@Overridepublic void run() {// code....}} , firstTime, 1000 * 10);}
}// 编写一个定时任务类
// 假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask {@Overridepublic void run() {// 编写你需要执行的任务就行了。SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String strTime = sdf.format(new Date());System.out.println(strTime + ":成功完成了一次数据备份!");}
}

14>wait和notify方法

package com.bjpowernode.java.thread;
import java.util.ArrayList;
import java.util.List;/*
1、使用wait方法和notify方法实现“生产者和消费者模式”2、什么是“生产者和消费者模式”?生产线程负责生产,消费线程负责消费。生产线程和消费线程要达到均衡。这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。3、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。4、wait方法和notify方法建立在线程同步的基础之上。
因为多线程要同时操作一个仓库。有线程安全问题。5、wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,
并且释放掉t线程之前占有的o对象的锁。6、notify方法作用:o.notify()让正在o对象上等待的线程唤醒,
只是通知,不会释放o对象上之前占有的锁。7、模拟这样一个需求:仓库我们采用List集合。List集合中假设只能存储1个元素。1个元素就表示仓库满了。如果List集合中元素个数是0,就表示仓库空了。保证List集合中永远都是最多存储1个元素。必须做到这种效果:生产1个消费1个。*/
public class ThreadTest16 {public static void main(String[] args) {// 创建1个仓库对象,共享的。List list = new ArrayList();// 创建两个线程对象// 生产者线程Thread t1 = new Thread(new Producer(list));// 消费者线程Thread t2 = new Thread(new Consumer(list));t1.setName("生产者线程");t2.setName("消费者线程");t1.start();t2.start();}
}// 生产线程
class Producer implements Runnable {// 仓库private List list;public Producer(List list) {this.list = list;}@Overridepublic void run() {// 一直生产(使用死循环来模拟一直生产)while(true){// 给仓库对象list加锁。synchronized (list){if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。try {// 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 程序能够执行到这里说明仓库是空的,可以生产Object obj = new Object();list.add(obj);System.out.println(Thread.currentThread().getName() + "--->" + obj);// 唤醒消费者进行消费list.notifyAll();}}}
}// 消费线程
class Consumer implements Runnable {// 仓库private List list;public Consumer(List list) {this.list = list;}@Overridepublic void run() {// 一直消费while(true){synchronized (list) {if(list.size() == 0){try {// 仓库已经空了。// 消费者线程等待,释放掉list集合的锁list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 程序能够执行到此处说明仓库中有数据,进行消费。Object obj = list.remove(0);System.out.println(Thread.currentThread().getName() + "--->" + obj);// 唤醒生产者生产。list.notifyAll();}}}
}

Java进阶之多线程相关推荐

  1. 【Java进阶】多线程(一)

    文章目录 [Java进阶]多线程 第一章:线程 1 并发与并行 2 进程与线程 3.Java 创建线程类 4.多线程原理 5 .Thread类 6 创建线程方式二 7 Thread和Runnable区 ...

  2. Java进阶知识 - 多线程与线程间通信

    CountdownLatch, CyclicBarrier 分别适合什么场景呢? 大部分情况下, 子线程只需要关心自身执行的任务. 但在某些复杂的情况下, 需要使用多个线程来协同完成某个任务, 这就涉 ...

  3. java进阶之多线程二线程池

    单线程线程池 class threadPool{public static void main(String[] args) {long f = System.currentTimeMillis(); ...

  4. Java进阶:多线程Lock管理多个Condition的实践

    概述 本篇主要讲解Lock管理多个Condition的情况,对Condition基础还不了解的童鞋,请先看下这篇文章:ReentrantLock和Condition基本使用 通过一项需求来实践 假设, ...

  5. Java 自学路线图之 Java 进阶自学

    文章目录 Java 自学路线图的第二阶段是 Java 语言进阶自学,在自学了第一阶段的 Java 基础自学后,大家对 Java 语言编程有了初步的了解和认识,建议在第一部分自学后整理一下自己的自学思路 ...

  6. java进阶-day32-序列化、多线程

    p749 序列化与反序列化 p750 序列化的实现 序列化 package com.bjpowernode.java.io;import com.bjpowernode.java.io.bean.St ...

  7. Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式

    本文介绍了Java中的四种I/O模型,同步阻塞,同步非阻塞,多路复用,异步阻塞.同时将NIO和BIO进行了对比,并详细分析了基于NIO的Reactor模式,包括经典单线程模型以及多线程模式和多Reac ...

  8. 《java 进阶之路》 上--推荐书籍

    整整一月没有更新博客了,因为十月份和双十一新入手的6本技术相关的书,这个月看完了3本,后面的3本还得慢慢肯. 下面我就介绍下进阶高级工程师必须也是我自己都看的一些书和知识点. 1.深入理解Java虚拟 ...

  9. 【进阶】 --- 多线程、多进程、异步IO实用例子

    [进阶] --- 多线程.多进程.异步IO实用例子:https://blog.csdn.net/lu8000/article/details/82315576 python之爬虫_并发(串行.多线程. ...

最新文章

  1. JNDI概述(转载)
  2. Java的基础方法Java的对象_java基础之 创建对象的几种方式
  3. python导入包库的两种语句import 和 from … import
  4. 蓝牙耳机测试用例_移动端测试用例设计总结,这些你得懂!
  5. 问题类像程序员一样思考
  6. 关系网络实战|设备关联信息定位团伙欺诈
  7. 2004-8-23+ 我的小论坛
  8. 未来的 AI 芯片将提升百倍性能!
  9. 简书吐槽大会|山东被曝大量不合格疫苗
  10. Android 如何通过Retrofit提交Json格式数据
  11. 纯干货!如何做一个成功的大数据项目
  12. Android编码规范05
  13. linux内核编译串口驱动,ARM Linux下安装CH341串口驱动
  14. 第一章 前缀和与差分
  15. 山东科技大学计算机控制系统期末考试试卷,山东科技大学 计算机操作系统试题b...
  16. #LeetCode15. 三数之和 @FDDLC
  17. c语言源程序自动评判系统,源程序的自动评判系统.PDF
  18. Android 中view的解释
  19. 数据库实验六---存储过程
  20. 华为p30鸿蒙系统内测在哪申请,华为自研操作系统:已注册“华为鸿蒙”商标

热门文章

  1. 详解ReentrantLock之Condition原理
  2. java 封装表单数据类型_ActionForm封装字段的数据类型 | 学步园
  3. 电机与拖动基础(上)
  4. led显示屏如何防老化
  5. IBM Cognos - 绿色配置开发环境
  6. currentstyle 织梦_dede currentstyle属性完美解决方案
  7. 【转】所有DNS服务器一览表
  8. 锐浪报表 Grid++Report 导出其它格式文件
  9. Android native 层使用opengl渲染YUV420p和NV12
  10. [AC]技术贴:开源飞控那些事