一、线程同步
(一)同步方法
1、同步代码块:在某段代码执行的时候,希望CPU不要执行那些会对当前线程数据产生干扰的线程上,所以给这段代码加上了同步代码块
2、如果某个方法中,所有的代码都要加上同步代码块,使用同步方法就可以让代码格式变得更加简洁
3、同步方法的格式
修饰符 【static】 synchronized 返回值类型 方法名称(参数列表) {
需要保证同步的方法体
}
4、同步锁对象
(1)非静态方法:同步锁对象是this,即当前调用者,谁来调用这个方法,同步锁对象就是谁
(2)静态方法:同步方法所在类的字节码对象,类名.class,哪个类调用这个同步方法,同步方法的锁对象就是哪个类的字节码对象
(二)线程安全类型的总结
1、线程不安全的类型:
StringBuilder、ArrayList、HashMap
2、线程安全的类型:
StringBuffer、Vector、Hashtable
(三)死锁
1、A线程具有甲资源,继续执行需要乙资源;B线程具有乙资源,继续执行需要甲资源。两条线成同时拥有对方所具有的资源,两条线成都不肯释放自己拥有的资源,所以谁也不能继续执行,就形成“死锁”的现象。
2、代码表现:如果有了同步代码块的嵌套,就可能出现死锁。所以我们需要避免同步代码块的嵌套,以防死锁。
public class Demo03_DeadLock {
public static void main(String[] args) {
Thread t1 = new Thread("家") {
@Override
public void run() {
synchronized ("车钥匙") {
System.out.println("车钥匙在家里");
synchronized ("家钥匙") {
System.out.println("进去家得到车钥匙,既能进去家,也能进去车");
}
}
}
};
Thread t2 = new Thread("车") {
@Override
public void run() {
synchronized ("家钥匙") {
System.out.println("家钥匙在车里");
synchronized ("车钥匙") {
System.out.println("进去车得到家钥匙,既能进去车,也能进去家");
}
}
}
};
t1.start();
t2.start();
}
}
(四)线程安全火车票案例
三个窗口,同时售卖100张火车票
打印某个窗口卖出了1张票,还剩几张
/**
* 三个窗口,同时售卖100张火车票 打印某个窗口卖出了1张票,还剩几张
*
* 三个窗口:三条线程
*
* @author Zihuatanejo
*
*/
public class Demo04_Exercise {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t, "窗口1");
Thread t2 = new Thread(t, "窗口2");
Thread t3 = new Thread(t, "窗口3");
t1.start();
t2.start();
t3.start();
}
public static void test1() {
Window w1 = new Window("窗口1");
Window w2 = new Window("窗口2");
Window w3 = new Window("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Ticket implements Runnable {
private int tickets = 100;
@Override
public void run() {
while(true) {
synchronized (this) {
if (tickets == 0) {
break;
}
tickets--;
System.out.println(Thread.currentThread().getName() + "卖出了1张票,还剩" + tickets + "张");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Window extends Thread {
private static int tickets = 100;
public Window() {
super();
}
public Window(Runnable target, String name) {
super(target, name);
}
public Window(Runnable target) {
super(target);
}
public Window(String name) {
super(name);
}
@Override
public void run() {
while (true) {
synchronized (Window.class) {
if (tickets == 0) {
break;
}
tickets--;
System.out.println(getName() + "卖出了1张票,还剩" + tickets + "张");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
二、线程中的其他内容
(一)其他方法
1、join() 使当前线程挂起,等待加入的线程执行完毕后,再恢复执行(在哪个线程里执行,就挂起哪条线程)
2、yield() 线程让步。和sleep方法很像,和sleep不同。它只是短暂的挂起当前线程,让别的线程先运行,而自己进入到准备运行的状态(就绪态)。在大多数视线中,线程只是让步于优先级相同或者跟高的线程。
(二)线程协作
1、案例:让两条线程交替打印1-100的数字
2、所用方法:
(1)wait() 让当前线程进入无限期的等待,并释放同步锁对象
(2)notify() 唤醒一个被wait()的线程,如果有多条线程被wait(),换线优先级最高的那一个
(3)notifyAll() 唤醒所有被wait()的线程
3、注意事项:
(1)wait()、notify()、notifyAll() 必须使用同步代码块或者同步方法的同步锁对象来调用
(2)wait()、notify()、notifyAll() 必须在同步代码块或者同步方法中才能使用
(3)wait()、notify()、notifyAll()属于Object类,因为任何一个类型的实例都有可能成为同步锁对象,所以将这些方法放入Object中,任意类型的对象都能调用
public class Demo06 {
public static void main(String[] args) throws InterruptedException {
//13579
//248 10
MyTask mt = new MyTask();
Thread t1 = new Thread(mt, "------线程1111");
Thread t2 = new Thread(mt, "==线程2222");
t1.start();
t2.start();
Thread.sleep(100);
}
}
class MyTask implements Runnable {
private int num = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
this.notify();
if (num > 100) {
break;
}
System.out.println(num + Thread.currentThread().getName());
num++;
try {
Thread.sleep(30);//让线程休眠,但是不释放同步锁对象
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
this.wait();//让线程进入无限期等待并且释放同步锁对象
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
(三)sleep(long millis)方法和wait()方法的区别
1、相同点:都是让线程进入阻塞态
2、不同点:
(1)方法所属类型不同:sleep属于Thread类,wait属于Object类
(2)调用要求不同:sleep方法任何场景下都能调用,wait方法必须在同步方法或者同步代码块中调用
(3)关于释放同步锁对象:sleep方法让线程进入休眠但是不会释放同步锁对象,wait方法让线程进入无限期等待并且释放同步锁对象
三、单例设计模式
1、模式:在生产实践中,积累下来的经验、办事的套路
2、设计模式:在开发中,针对类、接口、方法等的设计套路,就是设计模式
3、在软件开发中,有23种设计模式,在不同的场景下,不同的需求中,可以使用不同的设计模式良好的解决问题
4、单例设计模式:单:一个、单个;例:实例;在某些情况下,设计一个类,这个类有且仅有一个对象。
5、单例设计模式的原则:
(1)构造方法私有化
(2)在类中创建好对象
(3)在类中对外提供获取对象的方式
饿汉式
1、在加载类的同时,就要初始化静态成员变量,所以就同时将本类对象创建出来
2、饿汉式:一给食物就吃,类一加载就要去创建对象
public class Demo07_SingletonHungry {
public static void main(String[] args) {
SingletonHungry sh1 = SingletonHungry.getInstance();
SingletonHungry sh2 = SingletonHungry.getInstance();
System.out.println(sh1);
System.out.println(sh2);
}
}
class SingletonHungry {
//1.构造方法私有化:限制外界创建对象
private SingletonHungry() {}
//2.在类中创建好对象
private static SingletonHungry sh = new SingletonHungry();
//3.在类中对外提供获取对象的方式
public static SingletonHungry getInstance() {
return sh;
}
}
懒汉式
1、懒汉式:在加载类的时候不着急创建对象,等到调用方法经过好几层判断后,非得创建不可才去创建,就像懒汉,能不做就不做,能拖就拖
public class Demo08_SingletonLazy {
public static void main(String[] args) {
}
}
class SingletonLazy {
//1.构造方法私有化限制外界创建对象
private SingletonLazy() {}
//2.在类中提前创建好对象
private static SingletonLazy sl;
//A B
//3.对外提供公开的获取方式
public static SingletonLazy getInstance() {
//内外层的判断一个都不能少,外层主要提高效率,但是如果将内层if去掉,会重新出现线程安全问题
//3.多线程环境下如果每一次都获取同步锁对象再去判断效率低下,外层加上一个判断,能尽可能的提高效率
if (sl == null) {
//2.多线程环境下,无法保证1的线程安全,所以加上同步代码块保证操作的完整性
synchronized (SingletonLazy.class) {
//1.判断当前对象是否存在,如果存在就不创建不存在才创建
if (sl == null) {
sl = new SingletonLazy();//12345
}
}
}
return sl;
}
}
老汉式
public class Demo09_SingletonOld {
public static void main(String[] args) {
SingletonOld so1 = SingletonOld.so;
SingletonOld so2 = SingletonOld.so;
System.out.println(so1 == so2);
System.out.println(so1);
System.out.println(so2);
}
}
class SingletonOld {
//构造方法私有化
private SingletonOld() {}
//提前在类中创建好对象
public static final SingletonOld so = new SingletonOld();
}
四、线程的生命周期
1、别名:线程的状态图、线程的生命周期图、线程的状态周期
2、状态罗列:
(1)新建态:线程对象创建出来之后,从未start
(2)就绪态:线程start了,但是CPU没有来临
(3)运行态:正在运行的线程处于这种状态
(4)阻塞态:线程主动休息、或者缺少一些执行所必须的资源,即使CPU来临也无法执行
(5)死亡态:线程完成了业务逻辑,或者出现了异常打断了执行,或者线程被破坏
Java中关于线程状态的描述
1、Java语言中,有可以获取线程状态的方法:getState()
2、Thread.state:Java语言中描述线程状态的枚举类型
3、因为线程的状态是有限的,所以该类型的对象,不需要手动创建,在类中已经创建好了,我们直接获取即可。每个对象,称为枚举项。
(1)NEW:尚未start的线程
(2)RUNNABLE:正在运行的线程
(3)BLOCKED:等待锁对象
(4)WAITING:被wait()的线程处于这种状态
(5)TIMED_WAITING:有时限的等待,例如线程sleep
(6)TERMINATED:死亡态线程
五、线程池
1、没有线程池:
(1)需要手动创建线程,还需要手动启动线程
(2)在线程运行的过程中,如果出现了业务逻辑破坏力大的情况,线程有可能被破坏,业务逻辑执行到一半就无法执行了,并且,业务逻辑也不能用新的线程继续执行
(3)当业务逻辑非常简单的时候,也要创建线程对象,只是简单地使用线程之后,线程就会变成系统垃圾无法再用,浪费系统资源
2、有线程池:
(1)不需要手动创建线程,线程会自动完成线程创建
(2)在业务逻辑破坏力大的时候,线程被破坏,线程池会安排其他线程继续执行没有执行完的业务逻辑
(3)当业务非常简单,只需要将业务逻辑提交给线程池,线程会自动安排线程执行任务,任务执行完毕后,线程不会变成系统垃圾,而是被线程池回收,继续在线程池中活跃,等待下一次提交任务
线程池的使用
1、步骤:
(1)获取线程池对象
(2)创建任务对象
(3)将任务提交到线程池中
2、获取线程对象:
(1)工具类:Executors:用于生成线程池的工具类,根据需求可以指定线程池中线程的数量
(2)ExecutorService newFixedThreadPool(int nThreads) 创建一个线程池,线程数量由参数指定
(3)ExecutorService newSingleThreadExecutor() 创建一个只具有一条线程的线程池
3、创建任务对象:创建Runnable接口的实现类对象即可
4、将任务提交到线程池中:
(1)submit(Runnable task) 将任务提交到线程池。当提交的任务数量大于池中线程数,现有的线程先执行任务,当第一个线程将任务执行完毕后,紧接着执行后续排队的任务
(2)shutdown() 结束线程池。执行已经提交的任务,不接受新任务
(3)shutdownNow() 结束线程池。试图结束正在执行的任务,不执行已经提交的任务,不接受新任务
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo11_Executors {
public static void main(String[] args) {
//1.获取线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//2.创建任务对象
Runnable task1 = new Runnable() {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i + "-----" + Thread.currentThread().getName());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Runnable task2 = new Runnable() {
public void run() {
for (int i = 1000; i < 2000; i++) {
System.out.println(i + "-----" + Thread.currentThread().getName());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Runnable task3 = new Runnable() {
public void run() {
for (int i = 9000; i < 10000; i++) {
System.out.println(i + "-----" + Thread.currentThread().getName());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//3.将任务提交到线程池
es.submit(task1);
es.submit(task2);
es.submit(task3);
//es.shutdown();
es.shutdownNow();
}
}