主要内容:
- 多线程基础和原理
- Thread类和Runnable接口
- Thread常见方法
- 线程同步
补充Properties两个问题:
1、是Hashtable的子类,可以直接使用父类的put(K key,V value)方法,但是一般不推荐
使用。因为我们在使用Properties的对象时,属性列表中每个键及其对应值都是一个字符串。一般尽量使用(String key, String value)。
2、Properties本身内部是无序的,但是在执行store()方法时,方法内部使用了枚举,最终生成的文件是有序的。
一、多线程基础和原理:
1.1 多线程的引入:
线程:是程序执行的一条路径,一个进程可以包含多个线程。进程是一个动态的概念,线程是一个静态的概念。
多线程并发执行可以提高程序的效率,可以同时完成多项工作。
多线程的应用场景:QQ同时与多人视频或共享桌面、服务器同时处理多个客户端的请求、使用迅雷开启多个线程一起下载......
1.2 多线程并行和并发:
多线程并发:多个任务都要请求运行,而处理器只能接受一个任务,就把这些任务分时段轮流进行,由于时间非常短暂,使人感觉是多个任务同时运行。
多线程并行:就是两个(或以上)的任务同时运行,在某一个进程运行过程中,有其他的进程也在同时进行。(前提是需要多个CPU)
1.3 JVM的运行:
Java程序运行原理:Java命令会开启JVM,启动JVM进程,相当于启动了一个应用程序。该进程会启动一个“主线程”,然后主线程去调用某个类中main方法。
JVM本身是不是多线程?
public class Demo01_Thread { /* * 判断JVM是不是多线程?——JVM是多线程的。 */ public static void main(String[] args) { // main方法本身是由主线程调用的。 for (int i = 0; i < 1000000; i++) { // 开启了自定义的一个线程 new Demo(); } for (int j = 0; j < 10; j++) { System.out.println("我是主线程的执行代码..."); } } } class Demo { // Object类finalize() @Override public void finalize() { System.out.println("垃圾被成功地清除了!!!!!"); } } |
1.4 多线程的实现方法(Thread):
public class Demo02_Thread { /** * 使用Thread类实现多线程 */ public static void main(String[] args) { // 4.创建一个Thread的子类对象(线程) MyThread thread = new MyThread(); // 5.开启线程 thread.start(); for (int i = 0; i < 1000; i++) { // 主线程的代码 System.out.println("Nature & Peace"); } } } class MyThread extends Thread { // 1.继承Thread @Override // 注解 public void run() { // 2.重写run方法 for (int i = 0; i < 1000; i++) { // 3.将要执行的代码,写在run中 System.out.println("相信自己"); } } } |
1.5 多线程的实现方式(Runnable):
public class Demo03_Thread { /** * 继承Runnable接口,来实现多线程 */ public static void main(String[] args) { // 4.创建MyRunnable的对象 MyRunnable myRunnable = new MyRunnable(); // 5.开启线程 Thread thread = new Thread(myRunnable); thread.start(); // 主线程的代码 for (int i = 0; i < 1000; i++) { System.out.println("Nature & Peace."); } } } class MyRunnable implements Runnable { // 1.定义一个类实现Runnable接口 @Override public void run() { // 2.实现接口中的run()方法 for (int i = 0; i < 1000; i++) { // 3.将要执行的代码写在run方法中 System.out.println("相信自己"); } } } |
1.6 两种实现方式的区别:
1、继承Thread类:由于子类重写Thread类的run()方法,当我们执行start()方法时,直接找子类run()方法。编译的时候,调用Runnable接口中的run()方法声明。
2、实现Runnable接口:Thread构造函数中传入Runnable接口对应的一个实例对象的引用,成员变量获得该引用,start()方法调用run()方法时内部判断成员变量Runnable的引用是否为空,不空编译时看Runnable中的run()方法声明,运行时执行的是实现类中的run()方法。
1.7 匿名内部类实现多线程:
public class Demo04_Thread { /** * 使用内部类实现多线程 */ public static void main(String[] args) { new Thread() { // 1.继承Thread类(使用的是匿名类) @Override public void run() { // 2.重写run()方法 for (int i = 0; i < 1000; i++) { // 3.将要执行的代码,写在run中 System.out.println("相信自己"); } } }.start(); // 4.开启线程 // 使用内部类+Runnable接口实现多线程 new Thread(new Runnable() { // 1.使用匿名类写一个Runnable接口的实例对象,传给Thread的构造方法 @Override public void run() { // 2.实现run()方法 for (int i = 0; i < 1000; i++) { // 3.要执行的代码放在run()方法中 System.out.println("Nature & Peace."); } } }).start(); // 4.开启线程 // for (int i = 0; i < 1000; i++) { // 主线程的代码 // System.out.println("Nature & Peace."); // } } } |
- Thread类的常用方法:
2.1 获取名字和设置名字:
public class Demo01_Name { /** * 线程的名称:getName()/setName(String name) */ public static void main(String[] args) { // demo01(); Thread thread1 = new Thread() { // 1.继承Thread(Thread类的对象thread1指向Thread类的一个子类匿名对象的引用) @Override public void run() { // 2.重写run()方法 for (int i = 0; i < 1000; i++) { // 3.run方法中的代码 // 此处的this代表当前的这Thread子类,使用getName()方法获取线程名称 System.out.println(this.getName() + "----------Do trust me!"); } } }; Thread thread2 = new Thread() { // 1.继承Thread @Override public void run() { // 2.重写run()方法 for (int i = 0; i < 1000; i++) { // 3.run方法中的代码 System.out.println(this.getName() + "..........请相信我!"); } } }; thread1.setName("铁拐李"); // 4.设置线程的名称 thread2.setName("猪悟能"); thread1.start(); // 5.启动线程 thread2.start(); } /** * */ private static void demo01() { new Thread("铁拐李") { // 1.继承Thread(调用构造方法设置线程的名称) @Override public void run() { // 2.重写run()方法 for (int i = 0; i < 1000; i++) { // 3.run方法中的代码 // 此处的this代表当前的这Thread子类,使用getName()方法获取线程名称 System.out.println(this.getName() + "----------Do trust me!"); } } }.start(); // 4.启动线程 new Thread("二师兄") { // 1.继承Thread @Override public void run() { // 2.重写run()方法 for (int i = 0; i < 1000; i++) { // 3.run方法中的代码 System.out.println(this.getName() + "..........请相信我!"); } } }.start(); // 4.启动线程 } } |
2.2 获取当前线程的对象:
public class Demo02_CurrentThread { /** * public static Thread currentThread():返回对当前正在执行的线程对象的引用。 */ public static void main(String[] args) { new Thread() { public void run() { System.out.println(getName() + ".......Just do it!"); } }.start(); new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "--------干就完了。"); } }).start(); // Thread.currentThread().setName("我是主线程"); System.out.println(Thread.currentThread().getName()); } } |
2.3 休眠线程:
public static void sleep(long millis) throws InterruptedException:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
public class Demo03_Sleep { /** * sleep(long millis)方法,线程休眠 */ public static void main(String[] args) throws InterruptedException { // demo01(); new Thread() { // 继承了Thread类 public void run() { for (int i = 0; i < 1000; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "--------Just do it."); } } }.start(); new Thread() { public void run() { for (int i = 0; i < 1000; i++) { try { Thread.sleep(450); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + ".........干就完了!"); } } }.start(); } /** * sleep方法实现倒计时 */ private static void demo01() throws InterruptedException { for (int i = 20; i > 0; i--) { Thread.sleep(1000); // 1000ms=1s System.out.println("敌军将在" + i + "秒内到达战场!"); } } } |
2.4 守护线程:
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
public class Demo04_Daemon { /** * 守护线程setDaemon(boolean on) */ public static void main(String[] args) { Thread thread1 = new Thread() { public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + "........你愁啥?" + i); } } }; Thread thread2 = new Thread() { public void run() { for (int i = 0; i < 1000; i++) { System.out.println(getName() + "........瞅你咋滴?" + i); } } }; thread2.setDaemon(true); // 设置为守护线程,被守护的是thread1 // 当thread1线程完全执行结束之后,thread2应该立即结束;实际上thread2的结束会有非常小的时间延迟 thread1.start(); thread2.start(); } } |
2.5 加入线程:
public final void join(long millis) throws InterruptedException:等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。
public class Demo05_Join { /** * 线程加入:join(long millis) */ public static void main(String[] args) { // 匿名内部类要访问方法的局部变量,该变量必须使用final修饰。 final Thread thread1 = new Thread() { public void run() { for (int i = 0; i < 15; i++) { System.out.println(getName() + "--------Just do IT."); } } }; Thread thread2 = new Thread() { public void run() { for (int i = 0; i < 15; i++) { if (i == 5) { try { thread1.join(1); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + ".........学好IT!"); } } }; thread1.start(); thread2.start(); } } |
2.6 礼让线程:
public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
public class Demo06_Yield { /** * yield()方法让出CPU资源,交给其他线程 */ public static void main(String[] args) { new MyThread().start(); new MyThread().start(); } } class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 1000; i++) { if (i % 10 == 0) { Thread.yield(); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(getName() + "-------" + i); } } } |
2.7 线程优先级:
public final static int MIN_PRIORITY = 1; 最低优先级
public final static int NORM_PRIORITY = 5; 默认优先级
public final static int MAX_PRIORITY = 10; 最高优先级
public class Demo07_Priority { /** * 线程的优先级 */ public static void main(String[] args) { Thread thread1 = new Thread() { public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + ".......二师兄,你好!"); } } }; Thread thread2 = new Thread() { public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName() + ".......沙师弟!"); } } }; thread1.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级(10) thread2.setPriority(Thread.MIN_PRIORITY); // 设置最低优先级(1) thread1.start(); thread2.start(); } } |
三、线程同步:
3.1 同步代码块:
同步代码块,根据锁机制,可以是任意对象,必须是同一个对象。
不能使用匿名对象,因为匿名对象不是同一个对象
锁对象可以直接使用this关键字,前提是这两个方法必须是同一个类的成员方法(非static)。
public class Demo01_Synchronized { /** * 同步代码块 */ public static void main(String[] args) { final Printer printer = new Printer(); new Thread() { public void run() { while (true) { printer.print01(); } } }.start(); new Thread() { public void run() { while (true) { printer.print02(); } } }.start(); System.out.println("aaa"); } } class Printer { Demo demo = new Demo(); public void print01() { // 同步代码块,根据锁机制,可以是任意对象,必须是同一个对象。 // 不能使用匿名对象,因为匿名对象不是同一个对象 // 锁对象可以直接使用this关键字,前提是这两个方法必须是同一个类的成员方法(非static) // synchronized (demo) { synchronized (this) { System.out.print("相"); System.out.print("信"); System.out.print("自"); System.out.print("已"); System.out.print("\r\n"); } } public void print02() { // synchronized (demo) { synchronized (this) { System.out.print("坚"); System.out.print("持"); System.out.print("努"); System.out.print("力"); System.out.print("吧"); System.out.print("\r\n"); } } } class Demo {} |
3.2 同步方法:
非静态的同步方法的锁对象是this
静态的同步方法的锁对象是该类的字节码对象
public class Demo02_SynchronizedFunc { /** * 同步代码块 */ public static void main(String[] args) { final Print prt = new Print(); new Thread() { public void run() { while (true) { prt.print01(); } } }.start(); new Thread() { public void run() { while (true) { prt.print03(); } } }.start(); System.out.println("aaa"); } } class Print { // 非静态的同步方法的锁对象就是this // 静态的同步方法的锁对象是字节码文件(Print.class) public static synchronized void print01() { System.out.print("相"); System.out.print("信"); System.out.print("自"); System.out.print("已"); System.out.print("\r\n"); } // public static synchronized void print02() { // System.out.print("坚"); // System.out.print("持"); // System.out.print("努"); // System.out.print("力"); // System.out.print("吧"); // System.out.print("\r\n"); // } // 如果print01()方法被定义为非静态的同步方法,那么此时应该使用this作为同步锁 // 如果print01()方法被定义为静态的同步方法,那么此时应该使用该类的字节码(Print.class)作为同步锁 public void print03() { synchronized (Print.class) { System.out.print("坚"); System.out.print("持"); System.out.print("努"); System.out.print("力"); System.out.print("吧"); System.out.print("\r\n"); } } } |
3.3 线程安全问题:
1、通过集成Thread类实现:
public class Demo03_SafetyTicket { /** * 线程安全问题——火车票 */ public static void main(String[] args) { new TicketThread().start(); new TicketThread().start(); new TicketThread().start(); new TicketThread().start(); } } class TicketThread extends Thread { // 多个线程中使用同一个变量时,该变量必须使用static private static int number = 100; @Override public void run() { while (true) { synchronized (TicketThread.class) { if (number == 0) { break; } try { Thread.sleep(10); // 线程0睡、线程1睡、线程2睡、线程3睡 } catch (InterruptedException e) { e.printStackTrace(); } System.out .println(getName() + "------这是第" + number-- + "号火车票!"); } } } } |
2、通过实现Runnable接口来实现
public class Demo04_SafetyTicket { /** * @param args */ 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 number = 100; @Override public void run() { while (true) { synchronized (this) { if (number == 0) { break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "......这是第" + number-- + "号火车票!"); } } } } |
3.4 多线程的死锁:
public class Demo05_DeadLock { private static String str1 = "左筷子"; private static String str2 = "右筷子"; // 做开发时,千万不要使用锁嵌套 public static void main(String[] args) { new Thread() { public void run() { while (true) { synchronized (str1) { System.out.println(getName() + "......获取到【" + str1 + "】,等待【" + str2 + "】"); synchronized (str2) { System.out.println(getName() + "------只要拿到了【" + str2 + "】就开吃!!!!!"); } } } } }.start(); new Thread() { // 第二个线程 public void run() { while (true) { synchronized (str2) { System.out.println(getName() + "......获取到【" + str2 + "】,等待【" + str1 + "】"); synchronized (str1) { System.out.println(getName() + "------只要拿到了【" + str1 + "】就开吃!!!!!"); } } } } }.start(); } } |