前言
本期任务:毕向东老师Java视频教程学习笔记(共计25天)
- 原视频链接:黑马程序员_毕向东_Java基础视频教程
- day01:编写HelloWorld程序
- day02:操作符与条件选择语句
- day03:循环语句与函数
- day04:数组
- day07:继承、抽象类与接口
- day08:多态
- day09:异常处理
- day11:多线程
- day12:线程安全与同步机制
- day13:String类
- day14:集合(ArrayList,LinkedList,HashSet)
- day15:集合(TreeSet)和泛型)
- day16:集合(HashMap、TreeMap)
- day17:集合框架的工具类(Arrays、Collections)
- day18:IO流(字符流读写)
- day19:IO流(字节流、转换流读写)
- day20:IO流(File对象)
代码
/*
java多线程的几种实现方式
*/
public class ThreadTest {
public static void main(String[] args) {
// 新写法
new Thread(() -> {
for (int x = 0; x < 200; x++) {
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}).start();
/*
创建线程的第一种方式是:继承Thread类。原因:要覆盖run方法,定义线程要运行的代码。
步骤:
1,继承Thread类。
2,覆盖run方法。将线程要运行的代码定义其中。
3,创建Thread类的子类对象,其实就是在创建线程,调用start方法。
*/
// 旧写法
new Test1().start();
// 匿名内部类
new Thread() {
public void run() {
for (int x = 0; x < 200; x++) {
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}
}.start();
// 实现Runable接口
/*
创建线程的第二种方式:实现Runnable接口。
步骤:
1,定义了实现Runnable接口。
2,覆盖接口的run方法。将多线程要运行的代码存入其中。
3,创建Thread类的对象(创建线程),并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
为什么要传递?因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象
传递给Thread。让Thread线程去使用该对象调用其run方法。
4,调用Thread对象的start方法。开启线程。
*/
Runnable r = new Runnable() {
public void run() {
for (int x = 0; x < 200; x++) {
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}
};
new Thread(r).start();
// 主线程运行
for (int x = 0; x < 1000; x++) {
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}
}
class Test1 extends Thread {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + "......" + x);
}
}
}
/*
需求:简单的卖票程序
多个窗口同时卖票
创建线程的第一种方式:继承Thread类
步骤:
1. 定义类继承Thread
2. 复写Thread类中的run方法
3. 调用线程的start方法
该方法两个作用,启动线程,调用run方法
创建线程的第二种方式:实现Runable接口
步骤:
1. 定义类实现Runable接口
2. 覆盖Runable接口中的run方法
将线程要运行的代码放在该run方法中
3. 通过Thread类建立线程对象
4. 将Runable接口的子类对象作为实际参数传递给Thread类的构造函数
为什么要将Runable接口的子类对象传递给Thread的构造函数:
因为,自定义的run方法所属的对象是Runable接口的子类对象
所以要让线程去指定队形的run方法,就必须明确该run方法所属对象
5. 调用Thread类的start方法开启线程并调用Runable接口子类的run方法
实现方式与继承方式有什么区别呢?
实现方式好处:避免了单继承的局限性
在定义线程时,建议使用实现方式
两种方式区别:
继承Thread:线程代码存放在Thread子类的run方法中
实现Runable:线程代码存放在接口的子类的run方法中
使用Thread.sleep()制造线程不安全,使用这个方法,会抛出InterruptException异常
由于存在线程10ms的休眠时间,使得出现tick为0,甚至-1、-2的错票情形,多线程运行存在安全问题
问题原因:
当多条语句在操作同一个线程共享数据的时候,一个线程对多条语句只执行了一部分,还没执行完,
另一个线程参与进来执行,导致共享数据出现错误。
解决方法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式
就是同步代码块
synchronized(对象){
需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁
火车上的卫生间 --- 经典。
同步的前提:
1. 必须要有两个或两个以上的线程。
2. 必须是多个线程使用同一个锁
3. 必须保证同步中之恶能有一个线程在运行。
好处:解决了线程安全问题
弊端:多个线程需要判断锁,较为消耗资源
同步函数也是可以解决线程安全的,直接在函数中加入synchronized限制符
同步函数用的是哪个锁呢?
函数需要被对象调用,那么函数都有一个所属对象引用,就是this
所以同步函数使用的锁是this
通过程序进行验证
使用两个线程来买票
一个线程在同步代码块中
一个线程在同步函数中
都在执行买票动作
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不是this,因为静态方法中不可以定义this
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class 该对象的类型是class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象:类名.class
*/
// 实现Runable
class Ticket implements Runnable {
private static int tick = 1000;
Object obj = new Object();
// 同步函数
public synchronized void show() {
if (tick > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ":" + tick--);
}
}
public void run1() {
while (true)
show();
}
// 同步代码块
public void run() {
while (true) {
synchronized (this) {
if (tick > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ":" + tick--);
}
}
}
}
}
// 继承Thread
class Ticket1 extends Thread {
private static int tick = 100;
public void run() {
while (tick > 0) {
System.out.println(Thread.currentThread().getName() + ":" + tick--);
}
}
}
public class TicketDemo {
public static void main(String[] args) {
// 实现Runable
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
/*
// 继承Thread
Ticket1 t1 = new Ticket1();
Ticket1 t2 = new Ticket1();
Ticket1 t3 = new Ticket1();
Ticket1 t4 = new Ticket1();
t1.start();
t2.start();
t3.start();
t4.start();
*/
}
}
/*
单例设计模式
1. 饿汉式
class Single{
private static final Single s = new Single();
private Single(){}
public static Single getInstance(){
return s;
}
}
2. 懒汉式
延迟加载,
多线程访问时会出现线程安全问题,
可使用同步锁解决, 同步函数和同步代码块都可,
使用双重判断可解决效率问题,
加同步的使用,使用的锁是该类所属的字节码文件对象
*/
class Single {
private static Single s = null;
private Single() {
}
public static Single getInstance() {
if (s == null) {
synchronized (Single.class) {
if (s == null) {
s = new Single();
}
}
}
return s;
}
}
/*
死锁
同步中嵌套同步。
以下为死锁的一个实例
*/
class Test implements Runnable {
private boolean flag;
Test(boolean flag) {
this.flag = flag;
}
public void run() {
if (flag) {
synchronized (MyLock.locka) {
System.out.println("if locka");
synchronized (MyLock.lockb) {
System.out.println("if lockb");
}
}
} else {
synchronized (MyLock.lockb) {
System.out.println("else lockb");
synchronized (MyLock.locka) {
System.out.println("else locka");
}
}
}
}
}
class MyLock {
static Object locka = new Object();
static Object lockb = new Object();
}
public class DeadLockDemo {
public static void main(String[] args) {
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}